ImageJ get value from an array - arrays

I am trying to match image files to a specific row in an array.
In a first step I am selecting a folder that contain the tif files.
Then I am selecting a csv file with the information I would like to use.
I am opening each image one after the other. For an opened image I would like to retrieve the values from the line that correspond to the image name in the csv file. The header of the CSV file is FileName, XValue, YValue.
Here's my code so far... Any help would be appreciated.
// Make a pop up to select the input directory
InputDirPath=getDirectory("Select Input directory");
// Get the list of files in the input input directory selected above as an array
InputFileList=getFileList(InputDirPath);
// Defines cell separator and line separator
CellSeparator=",";
LineSeparator="\n";
// open the csv file as a string
FileCCValuesPath=File.openDialog("Select the file containing the coordinates");
FileCCValuesString=File.openAsString(FileCCValuesPath);
// Split each row into an array
FileCCValuesRows=split(FileCCValuesString, LineSeparator);
// Create new arrays for the content of column and for each row
FileNameArray=newArray(FileCCValuesRows.length);
XValueArray=newArray(FileCCValuesRows.length);
YValueArray =newArray(FileCCValuesRows.length);
// Start of the loop going through the list of image files in the input folder selected above
for (Filei = 0; Filei < InputFileList.length; Filei++)
{
InputFileNamei=InputFileList[Filei];
InputFilePathi = InputDirPath+InputFileNamei;
if(endsWith(InputFilePathi, ".tif"))
{
open(InputFilePathi);
//////////This is where I am stuck
//////////Get the XValue and Value from the CSV file for the row in which
//////////FileName=InputFileNamei
run("Translate...", "x=XValue y=YValue interpolation=None");
}//end if
}//end for File i loop
// Notice of end of process
waitForUser("Process is done");

You can add another for loop (after opening the current image) that goes through all lines and checks if the line starts with the current image name and then split the current line to get the x and y values:
for (i=0; i < FileCCValuesRows.length; i++) {
if (startsWith(FileCCValuesRows[i], InputFileNamei)) {
values = split(FileCCValuesRows[i], CellSeparator);
xValue = parseInt(values[1]);
yValue = parseInt(values[2]);
run("Translate...", "x=" + xValue+ " y=" + yValue + " interpolation=None");
}
}
Note that the "ImageJ" way of doing it would be to open your CSV file in a Results table and use the getResultString and getResult macro functions to get the required values. Here's a version of your macro using these:
// Make a pop up to select the input directory
InputDirPath=getDirectory("Select Input directory");
// Get the list of files in the input input directory selected above as an array
InputFileList=getFileList(InputDirPath);
// open the csv file in a Results table
FileCCValuesPath=File.openDialog("Select the file containing the coordinates");
open(FileCCValuesPath);
// Start of the loop going through the list of image files in the input folder selected above
for (Filei = 0; Filei < InputFileList.length; Filei++)
{
InputFileNamei=InputFileList[Filei];
InputFilePathi = InputDirPath+InputFileNamei;
if(endsWith(InputFilePathi, ".tif"))
{
open(InputFilePathi);
for (i=0; i < nResults; i++) {
if (getResultString("FileName", i) == InputFileNamei) {
xValue = getResult("XValue", i);
yValue = getResult("YValue", i);
run("Translate...", "x=" + xValue+ " y=" + yValue + " interpolation=None");
}
}
}//end if
}//end for File i loop
// Notice of end of process
waitForUser("Process is done");

Thank you very much for your replies
This is what I end up doing:
1-Split the array by row using
FileValuesRows=split(FileValuesString, LineSeparator);
2- Create new array for each column (I removed one line because of the header)
Column1Array=newArray(FileValuesRows.length-1);
Column2Array=newArray(FileValuesRows.length-1);
3- Create a for loop that screen each row and break it into each individual column (Starting at 1 because of the header)
for (Rowi=1;Rowi<FileCCValuesRows.length; Rowi++){
FileCCValuesRowi=split(FileCCValuesRows[Rowi], CellSeparator);
//Array.show(FileCCValuesRowi);
4- Add the content content of each row into the previously created array (-1 because of the header)
Column1Array[Rowi-1]=FileCCValuesRowi[0];
Column2Array[Rowi-1]=FileCCValuesRowi[1];
}
//end if for Rowi
5- In the next step the aim is to find the row number corresponding to current open image. This is done in two steps:
5.1- Screen the csv file for the number of occurrence of the string (in this case the filename)
5.2 if occurrence is non null add them to an Indices Array
5.3 Use this indice to get the value corresponding to that row in the array created before
//Returns the indices at which a value occurs within an array
Occurence=0;
// Screen the FileName Array row by row and count the number of occurence
for (Rowi=0; Rowi<lengthOf(FileNameArray); Rowi++) {
if (FileNameArray[Rowi]==InputFileName) {
Occurence++;
//print(Occurence);
} //end of if
} // end of for Rowi
// If found
if (Occurence>0) {
// Create an array of length the number of occurence
IndicesArray=newArray(Occurence);
Occurence=0;
// Screen the FileName Array row by row and add the row of occurence into the Indices Array
for (Rowi=0; Rowi<lengthOf(FileNameArray); Rowi++) {
if (FileNameArray[Rowi]==InputFileName) {
IndicesArray[Occurence]=Rowi;
Occurence++;
}
}
//Array.show(IndicesArray);
}
Final step
//Get the X and Y translation value for the File being processed
In this case I have only 1 occurrence so I will take the first line of the Indice array which is the line 0
XValue=Column1Array[(IndicesArray[0])];
YValue=Column2Array[IndicesArray[0]];
//Translate the picture
run("Translate...", "x=XValue y=YValue interpolation=None");

Related

Google Apps Script - Optimizing File Creation/Modification

I reviewed the following documentation from Google on how to optimize existing Google scripts here:
https://developers.google.com/apps-script/guides/support/best-practices
In particular, the 'Use batch-operation' section seems more appropriate for my use case, where the optimal strategy is to 'batch' all the reading into one operation, and then writing in separate operation; not to cycle between read-and-write calls.
Here is an example of inefficient code, as given by the url above:
// DO NOT USE THIS CODE. It is an example of SLOW, INEFFICIENT code.
// FOR DEMONSTRATION ONLY
var cell = sheet.getRange('a1');
for (var y = 0; y < 100; y++) {
xcoord = xmin;
for (var x = 0; x < 100; x++) {
var c = getColorFromCoordinates(xcoord, ycoord);
cell.offset(y, x).setBackgroundColor(c);
xcoord += xincrement;
}
ycoord -= yincrement;
SpreadsheetApp.flush();
}
Here is an example of efficient and improved code:
// OKAY TO USE THIS EXAMPLE or code based on it.
var cell = sheet.getRange('a1');
var colors = new Array(100);
for (var y = 0; y < 100; y++) {
xcoord = xmin;
colors[y] = new Array(100);
for (var x = 0; x < 100; x++) {
colors[y][x] = getColorFromCoordinates(xcoord, ycoord);
xcoord += xincrement;
}
ycoord -= yincrement;
}
sheet.getRange(1, 1, 100, 100).setBackgroundColors(colors);
Now, for my particular use case:
Instead of storing values in an array, then writing/modifying them as a separate operation from reading them into an array, I want to create multiple Google documents that replaced placeholders within each document.
For context:
I'm writing a script that reads a spreadsheet of students with files to modify for each student, which is later sent as a mail merge. For example, there are 3 master files. Each student will have a copy of the 3 master files, which is used to .replaceText placeholder fields.
Here are my relevant snippets of code below:
function filesAndEmails() {
// Import the Spreadsheet application library.
const UI = SpreadsheetApp.getUi();
// Try calling the functions below; catch any error messages that occur to display as alert window.
try {
// Prompt and record user's email draft template.
// var emailLinkID = connectDocument(
// UI,
// title="Step 1/2: Google Document (Email Draft) Connection",
// dialog=`What email draft template are you referring to?
// This file should contain the subject line, name and body.
// Copy and paste the direct URL link to the Google Docs:`,
// isFile=true
// );
// TEST
var emailLinkID = "REMOVED FOR PRIVACY";
if (emailLinkID != -1) {
// Prompt and record user's desired folder location to store generated files.
// var fldrID = connectDocument(
// UI,
// title="Step 2/2: Google Folder (Storage) Connection",
// dialog=`Which folder would you like all the generated file(s) to be stored at?
// Copy and paste the direct URL link to the Google folder:`,
// isFile=false
// );
// TEST
var fldrID = DriveApp.getFolderById("REMOVED FOR PRIVACY");
// Retrieve data set from database.
var sheet = SpreadsheetApp.getActive().getSheetByName(SHEET_1);
// Range of data must include header row for proper key mapping.
var arrayOfStudentObj = objectify(sheet.getRange(3, 1, sheet.getLastRow()-2, 11).getValues());
// Establish array of attachment objects for filename and file url.
var arrayOfAttachObj = getAttachments();
// Opportunities for optimization begins here.
// Iterate through array of student Objects to extract each mapped key values for Google document insertion and emailing.
// Time Complexity: O(n^3)
arrayOfStudentObj.forEach(function(student) {
if (student[EMAIL_SENT_COL] == '') {
try {
arrayOfAttachObj.forEach(function(attachment) {
// All generated files will contain this filename format, followed by the attachment filename/description.
var filename = `${student[RYE_ID_COL]} ${student[FNAME_COL]} ${student[LNAME_COL]} ${attachment[ATTACH_FILENAME_COL]}`;
// Create a copy of the current iteration/file for the given student.
var file = DocumentApp.openById(DriveApp.getFileById(getID(attachment[ATTACH_FILEURL_COL], isFile=false)).makeCopy(filename, fldrID).getId())
// Replace and save all custom fields for the given student at this current iteration/file.
replaceCustomFields(file, student);
});
} catch(e) {
}
}
});
UI.alert("Script successfully completed!");
};
} catch(e) {
UI.alert("Error Detected", e.message + "\n\nContact a developer for help.", UI.ButtonSet.OK);
};
}
/**
* Replaces all fields specified by 'attributesArray' given student's file.
* #param {Object} file A single file object used to replace all custom fields with.
* #param {Object} student A single student object that contains all custom field attributes.
*/
function replaceCustomFields(file, student) {
// Iterate through each student's attribute (first name, last name, etc.) to change each field.
attributesArray.forEach(function(attribute) {
file.getBody()
.replaceText(attribute, student[attribute]);
});
// Must save and close file to finalize changes prior to moving onto next student object.
file.saveAndClose();
}
/**
* Processes the attachments sheet for filename and file ID.
* #return {Array} An array of attachment file objects.
*/
function getAttachments() {
var files = SpreadsheetApp.getActive().getSheetByName(SHEET_2);
return objectify(files.getRange(1, 1, files.getLastRow(), 2).getValues());
}
/**
* Creates student objects to contain the object attributes for each student based on
* the header row.
* #param {Array} array A 2D heterogeneous array includes the header row for attribute key mapping.
* #return {Array} An array of student objects.
*/
function objectify(array) {
var keys = array.shift();
var objects = array.map(function (values) {
return keys.reduce(function (o, k, i) {
o[k] = values[i];
return o;
}, {});
});
return objects;
}
To summarize my code, I read the Google spreadsheet of students as an array of objects, so each student has attributes like their first name, last name, email, etc. I have done the same for the file attachments that would be included for each student. Currently, the forEach loop iterates through each student object, creates copies of the master file(s), replaces placeholder text in each file, then saves them in a folder. Eventually, I will be sending these file(s) to each student with the MailApp. However, due to the repetitive external calls via creating file copies for each student, the execution time is understandably very slow...
TLDR
Is it still possible to optimize my code using "batch operations" when it is necessary for my use case to have multiple DriveApp calls to create said copies of the files for modification purposes? As opposed to reading raw values into an array and modifying them at a later operation, I don't think I could simply just store document objects into an array, then modify them at a later stage. Thoughts?
You could use batchUpdate of Google Docs API.
See Tanaike's answer just to have an idea on how the request object looks like.
All you need to do in your Apps Script now is build the object for multiple files.
Note:
You can also further optimize your code by updating:
var arrayOfStudentObj = objectify(sheet.getRange(3, 1, sheet.getLastRow()-2, 11).getValues();
into:
// what column your email confirmation is which is 0-index
// assuming column K contains the email confirmation (11 - 1 = 10)
var emailSentColumn = 10;
// filter data, don't include rows with blank values in column K
var arrayOfStudentObj = objectify(sheet.getRange(3, 1, sheet.getLastRow()-2, 11).getValues().filter(row=>row[emailSentColumn]));
This way, you can remove your condition if (student[EMAIL_SENT_COL] == '') { below and lessen the number of loops.
Resource:
Google Docs Apps Script Quickstart
Google Docs REST API

Swift 5 reading a text file into a 2d array of Int/Doubles

I hope this isn't too simple a question for the group. I am teaching myself Swift but I'm really struggling to get my head around a way to read a CSV text file containing a mixture of Int and Double values into a 2d array which sits inside a Class where each line in the array represents a line from the input file. I have successfully loaded an 1d array of strings, each element corresponding to a line of text in the CSV file. However I want to save time by going direct from input file to 2d array of decimals.
Read in the data. Separate the text into lines. Separate the lines into tokens. Convert each token to an Int or if it fails, a Double.
I assume you know how to read the files so here is an example with a static CSV string:
let text = """
1,2,3.0
4,5.0,6,z
"""
enum CustomError: Error {
case notAnItOrADouble(String)
}
do {
let numberRows = try text
.split(separator: "\n")
.map { line in
try line.split(separator: ",").map { substring -> Any in
let token = String(substring)
guard let value: Any = Int(token) ?? Double(token) else {
throw CustomError.notAnItOrADouble(token)
}
return value
}
}
numberRows.forEach { row in
row.forEach { number in
print("\(number) is \(type(of: number))")
}
}
} catch (let error) {
print(error)
}
Output:
1 is Int
2 is Int
3.0 is Double
4 is Int
5.0 is Double
6 is Int
Try putting in a string instead of an int or double and you will see that you get the error with he first unparsable token instead.

Adding numbers to each line in a file in haxe language

Need some help, writing a program that will read the text from a file, but will
put at the beginning of each line a number, in such a way that each line is numbered in ascending order
example:
file1
a
b
c
What I want to see:
1: a
2: b
3: c
Process:
Read contents of file into a String
Split by line ending into Array<String>
Iterate and mutate contents line by line
Join by line ending back into a String
Write back into file
Sample code for any sys target:
var arr = sys.File.getContent('file.txt').split("\n");
for(i in 0...arr.length) {
arr[i] = (i+1) + ": " + arr[i];
}
sys.File.saveContent('file.txt', arr.join("\n"));

How can I overwrite a word from an 2d array?

I have created a 2d array where a user can input words.
I need to create 2 functions:
1st function is called Add and an Admin can add a user and his data.
2nd function is called Delete and an Admin can remove a user by typing his username. If his username is found in the 2d array created in Add function then all of user's data should be replaced by zeros. The thing is that I cannot overwrite/replace the user's data to zero.
The 2d array:
char pin[50][7][100];
each row contains a single user and each column contains his info like name, surname etc.
Here is the delete function:
printf("Enter the username of the user you want to delete: \n");
scanf("%s",&key);
for(i=0;i<50;i++){
for(j=5;j<6;j++){
if (strcmp(pin[i][j],key)==0){
k=i;
flag=1;
break;
}
}}
if (flag==1){
do{
printf("Are you sure you want to delete this user?\t (Yes or No)\n");
scanf("%s",&api);
}while((strcmp(api,"Yes")!=0) && (strcmp(api,"No")!=0) );
if (strcmp(api,"Yes")==0){
/* Here I need to replace with 0s!*/
}
else if (strcmp(api,"No")==0) {
goto dlt;
}
}
else{
printf("Error!Username not found.Please try again. \n");
}
I presume that the column 5 contains the username, and you have found the user you want to delete at row k.
You can delete the data by using strcpy to replace all the data with 0s.
for (j = 0; j < 7; j++)
{
strcpy(pin[k][j],"0");
}

AS3 embedding CSV files and reading from them

So in a simple arcade/platformer game, I'm making it so I have a .csv text file set out like so:
660, 25, 0
720, 15, 1
etc..
The first number being the x coordinate, the next being the y coordinate and the last being whether the block kills you or not. Loading this data externally is not a problem and works fine but when it comes to actually running the .swf by itself obviously the .csv file is not embedded into it so I cannot access any values from it.
Therefore my question is: How can I embed a .csv file into my project and then read out 3 values per line into a multi dimensional array with each line denoting a different obstacle?
(The multi dimensional array being [obstacleID][0 for x coord/1 for y coord/2 for whether it kills or not])
How to embed a text file in Flash
then you can try:
var csv:embedded_csv = new embedded_csv();
var csvLines:Array = csv.toString().split("\n"); // \n or File.lineSeparator or \r\n
for(i=0; i<csvLines.length; i++)
{
line:Array = String(csvLines[i]).split(", ");
x = line[0];
y = line[1];
kills = line[2];
...
}

Resources