//Cell-Classifier macroName = "Cell-Classifier"; macroShortDescription = "With this Macro you can speed up clessification of cell on many images."; macroDescription = "This macro helps to classify cells or objects." + "
Images will be opened in groups as stack and user can count object in the defined classes." + "
- Select input folder" + "
- Select ouput folder for saving files (class model, classification results)" + "
- Follow the instructions and read the Wiki page for help!" + "
- HINT: user + and - keys to zoom in and out, use < and > keys to move stack slices forth and back."; macroRelease = "fourth release 26-10-2020 by Martin Stöter (stoeter(at)mpi-cbg.de)"; generalHelpURL = "https://github.com/stoeter/Fiji-Tools-for-HCS/wiki"; macroHelpURL = generalHelpURL + "/" + macroName; macroHtml = "" +"" + macroName + "\n" + macroRelease + "

" +"" + macroDescription + "

" +"Check for more help on this web page:
" +"" + macroHelpURL + "
" +"General info:
" +"" + generalHelpURL + "
" +"...get these URLs from Log window!
" +""; //print macro name and current time to Log window getDateAndTime(year, month, dayOfWeek, dayOfMonth, hour, minute, second, msec); month++; print("\\Clear"); print(macroName, "(" + macroRelease + ")", "\nStart:",year+"-"+month+"-"+dayOfMonth+", "+hour+"-"+minute+"-"+second); print(macroHelpURL); print(generalHelpURL); //start macro Dialog.create("Fiji macro: " + macroName); Dialog.addMessage("Fiji macro: " + macroName + " (Fiji-Tools-for-HCS by TDS@MPI-CBG)\n \n" + macroShortDescription + "\n \nClick 'OK' to go on, 'Cancel' to quit or 'Help' for online description."); Dialog.addHelp(macroHtml); Dialog.show; //choose folders inputPath = getDirectory("Choose image folder... "); outputPath = getDirectory("Choose result image folder... or create a folder"); printPaths = "inputPath = \"" + inputPath + "\";\noutputPath = \"" + outputPath + "\";"; print(printPaths); tempLogFileNumber = 1; while (File.exists(outputPath + "Log_temp_" + tempLogFileNumber +".txt")) tempLogFileNumber ++; //find last tempLog file to prevent overwriting of log //initialize => default settings run("Set Measurements...", "area mean standard min integrated median stack display redirect=None decimal=3"); run("Input/Output...", "jpeg=90 gif=-1 file=.txt copy_column copy_row save_column save_row"); run("Close All"); //////////////////////////////// M A C R O C O D E /////////////////////////////// //set variables var fileExtension = ".jpg"; //pre-definition of extension var filterStrings = newArray("_T0001","",""); //pre-definition of strings to filter var availableFilterTerms = newArray("no filtering", "include", "exclude"); //dont change this var filterTerms = newArray("include", "no filtering", "no filtering"); //pre-definition of filter types var displayFileList = false; //shall array window be shown? setDialogImageFileFilter(); print("Image file filter:", filterTerms[0],filterStrings[0] + ";",filterTerms[1],filterStrings[1] + ";",filterTerms[2],filterStrings[2]); //get file list ALL fileList = getFileListSubfolder(inputPath, displayFileList); //read all files in subfolders fileList = getFileType(fileList, fileExtension, displayFileList); fileList = getFilteredFileList(fileList, false, displayFileList); //filter for strings groupingImagesArray = newArray("per well", "per field", "per well-field", "batch of images"); Dialog.create("Setting for analysis"); Dialog.addMessage("Define the grouping of images:"); Dialog.addChoice("Load one group of", groupingImagesArray, groupingImagesArray[3]); Dialog.addCheckbox("Display unique values for group?", false); Dialog.addCheckbox("Make montage from group of images?", false); Dialog.show(); groupingImages = Dialog.getChoice(); displayGroup = Dialog.getCheckbox(); makeMontage = Dialog.getCheckbox(); if (groupingImages == groupingImagesArray[3]) { Dialog.create("Setting for batch of images:"); Dialog.addMessage("Define the grouping of images:"); Dialog.addMessage(" - there are " + fileList.length + " files..."); Dialog.addChoice("Set batches by:", newArray("images per batch", "number of batches")); Dialog.addNumber("How many images per batch ...?", 10); Dialog.addNumber("or how many batches?", 10); Dialog.show(); batchDefinition = Dialog.getChoice(); filesPerBatch = Dialog.getNumber(); numberOfBatches = Dialog.getNumber(); } if (groupingImages == groupingImagesArray[0]) { groupList = getUniqueWellListCV7000(fileList, displayGroup); print("Number of unique wells:", groupList.length); } if (groupingImages == groupingImagesArray[1]) { groupList = getUniqueFieldListCV7000(fileList, displayGroup); print("Number of unique fields:", groupList.length); } if (groupingImages == groupingImagesArray[2]) { groupList = getUniqueWellFieldListCV7000(fileList, displayGroup); print("Number of unique well-fields:", groupList.length); } if (groupingImages == groupingImagesArray[3] && batchDefinition == "number of batches") { filesPerBatch = Math.ceil(fileList.length / numberOfBatches); groupList = Array.getSequence(numberOfBatches); print("Batches:", batchDefinition, "Running " + numberOfBatches + " batches with " + filesPerBatch + " files per batch."); } if (groupingImages == groupingImagesArray[3] && batchDefinition == "images per batch") { numberOfBatches = Math.ceil(fileList.length / filesPerBatch); groupList = Array.getSequence(numberOfBatches); print("Batches:", batchDefinition, "Running " + numberOfBatches + " batches with " + filesPerBatch + " files per batch."); } if (groupList.length > 1) { Dialog.create("Which batch to start with?"); Dialog.addMessage("Select the starting batch (will skip batche before) :"); Dialog.addMessage(" - there are " + numberOfBatches + " batches... (first batch = 0 )"); Dialog.addChoice("Set starting batches:", Array.getSequence(numberOfBatches), 0); Dialog.show(); batchStart = Dialog.getChoice(); } else { batchStart = 0; } textWindowName = "Interactive Instructions"; textWindow = "["+textWindowName+"]"; if (isOpen(textWindowName)) print(textWindow, "\\Update:"); // clears the window else run("Text Window...", "name=" + textWindow + " width=72 height=10"); print(textWindow, "See follow macro instructions\nand see instructions in Log window..."); /*if (true) {//isOpen("Cell Counter") //this doesnt seem to work selectWindow("Cell Counter"); print("cell counter open"); run("Close"); IJ.closeWindow("Cell Counter"); //close("Cell Counter"); }*/ for (currentGroup = batchStart; currentGroup < groupList.length; currentGroup++) { firstImage = "NOT A STACK"; if (groupingImages == groupingImagesArray[3]) { // = batch of images groupFileList = Array.slice(fileList, currentGroup * filesPerBatch, currentGroup * filesPerBatch + filesPerBatch); print("opening file batch:", currentGroup + 1, "- set:", currentGroup * filesPerBatch + 1, currentGroup * filesPerBatch + filesPerBatch); } else { groupFileList = getFilteredList(fileList, groupList[currentGroup], false); //filter for strings of unique group } //write names of opened files into text file groupFileListTextFileName = "FileListGroup_" + groupList[currentGroup] + ".txt"; // file name of text file (groupFileListTextFile) which stores image paths if (File.exists(outputPath + groupFileListTextFileName)) showMessage("Warning !!!", "" // warn user that file list .txt file will be overwritten +"

This file exists and will be overwritten!

" +""); print("save list of opened files in text file: " + groupFileListTextFileName); groupFileListTextFile = File.open(outputPath + groupFileListTextFileName); print(groupFileListTextFile, "Group\tSlice\tFile name"); //write header in FileListGroup text file setBatchMode(true); setBatchMode(false); //this helped to load all the montage (Huch 004), otherwise make stack failed for (currentGroupFile = 0; currentGroupFile < groupFileList.length; currentGroupFile++) { // open files one after the other... IJ.redirectErrorMessages(); open(groupFileList[currentGroupFile]); currentImage = getTitle(); print("opened image", currentImage); print(groupFileListTextFile, groupList[currentGroup] + "\t" + (currentGroupFile+1) + "\t" + groupFileList[currentGroupFile]); //store FileListGroup as text file //showProgress(currentGroupFile / groupFileList.length); if (nSlices > 1) { numberOfChannels = nSlices; //waitForUser("sliches"); for (currentChannel = 1; currentChannel <= numberOfChannels; currentChannel++) { Property.setSliceLabel(currentImage + "_C0" + currentChannel, currentChannel); } if (nImages == 1) { firstImage = currentImage; print("found " + numberOfChannels +" channels..."); //fileName = File.nameWithoutExtension; //getTitle(); for saving later, name of stack } else { // join current image to stack if (nImages > 1) run("Concatenate...", " title=[" + firstImage + "] image1=[" + firstImage + "] image2=[" + currentImage + "] image3=[-- None --]"); } } } setBatchMode(false); File.close(groupFileListTextFile); //save text file with file names if (firstImage == "NOT A STACK") { run("Images to Stack"); // make stack from single images } else { run("Stack to Hyperstack...", "order=xyczt(default) channels=" + numberOfChannels + " slices=1 frames=" + groupFileList.length + " display=Grayscale"); run("Channels Tool..."); Stack.setDisplayMode("composite"); } if (makeMontage) { tempImageName = getTitle(); montageColumns = Math.ceil(Math.sqrt(groupFileList.length)); montageRows = Math.ceil(groupFileList.length / montageColumns); print("make montage of " + groupFileList.length + " images as " + montageColumns + " by " + montageRows + " matrix"); run("Make Montage...", "columns=" + montageColumns + " rows=" + montageRows + " scale=1"); print("montage has image dimensions of width: " + getWidth() + " and height: " + getHeight()); close(tempImageName); } rename("ImageGroup_" + groupList[currentGroup]); //now ready for Cell Counter if (currentGroup == batchStart) { run("Cell Counter"); waitForUser("Please make sure only one 'Cell Counter' windows is open..."); printInstructionsToLog(); // see function below... waitForUser("- press [Initialize]\n- set up the classes in [Counter]\n- see details & instructions in the [Log] window Chapter 1 ..."); } else { waitForUser("- press [Initialize]"); } waitForUser("- do your classifications...\n- see details & instructions in the [Log] window Chapter 2 ...\n- when you are done press 'ok'"); currentImageStack = getTitle(); print(textWindow, "\\Update:"); // clears the window print(textWindow, "\n" + currentImageStack + ".xml\n" + "- use this file name with copy/paste (is already in clipboard)" + "\n" + "- press [Save markers] to save the classification points"); print(textWindow, "\n\n" + currentImageStack + ".txt\n" + "- use this file name with copy/paste" + "\n" + "- press [Measure] to save the classification results"); String.copy(currentImageStack + ".xml"); waitForUser("Follow instructions in window 'Interactive Instructions'\n- ...save markers\n- ...save measurements\n- see details & instructions in the [Log] window Chapter 3 ..."); saveLog(outputPath + "Log_temp_" + tempLogFileNumber +".txt"); close(currentImageStack); } //print current time to Log window and save log getDateAndTime(year, month, dayOfWeek, dayOfMonth, hour, minute, second, msec); month++; print("Macro executed successfully.\nEnd:",year+"-"+month+"-"+dayOfMonth+", "+hour+"-"+minute+"-"+second); selectWindow("Log"); saveAs("Text", outputPath + "Log_"+year+"-"+month+"-"+dayOfMonth+", "+hour+"-"+minute+"-"+second+".txt"); if (File.exists(outputPath + "Log_temp_" + tempLogFileNumber +".txt")) File.delete(outputPath + "Log_temp_" + tempLogFileNumber + ".txt"); //delete current tempLog file ///////////////////////////////////////////////////////////////////////////////////////////// //////// F U N C T I O N S ///////////// ///////////////////////////////////////////////////////////////////////////////////////////// //prit macro instructions to log window function printInstructionsToLog() { print("\nInstructions of running the Cell Counter:"); print("-----------------------------------------"); print("- press [Initialize] => you need to do this for each loaded image group..."); print("=> before you start classifing think about the number of classes needed and clear class names"); print("=> dont use more than 8 classes, macro will work but Cell Counter has bugs / limitations"); print("\nChapter 1: Set up the Classes / [Counters]:"); print("- select a class (e.g. Type 1)"); print("- click [Rename] and type in a class name"); print(" - do this for all classes you want to have..."); print(" - you can [Add] and [Remove] classes (try to uses no more than 8 classes)"); print(" - document elsewhere class number & name (e.g. .txt), because the class name will not be saved anywhere!"); print(" - once the Cell Counter is closed your class names will be lost... -> make new with [Rename]!"); print(" - use [Options] to set the color for each class (also wont be saved in .xml!)"); print("\nChapter 2: Classify your objects:"); print("- make sure the multi-point tool is selected in the Fiji tool bar (7th icon)"); print("- select a class (e.g. Class normal)"); print("- right click on image into center of object => class number appears"); print("- classify all objects you want in all images..."); print("- delete the last click by pressing [Delete]"); print("- delete any class point by checking [Delete Mode] => dont forget to uncheck afterwards!"); print(" - HINT: user + and - keys to zoom in and out"); print(" - HINT: use < and > keys or mouse wheel to move stack slices forth and back"); print("\nChapter 3: Save your classification:"); print("- follow instructions in the window 'Interactive Instructions' => you can copy/paste from this window!"); print("- copy .xml file name from that window (apple/CTRL + C)"); print("- click [Save Markers] and paste .xml file name"); print("- copy .txt file name from that window (apple/CTRL + C)"); print("- click [Measurement], select 'Result' window and save results using pasted .txt file name"); print("-----------------------------------------"); } //function filters a file list for a certain strings //example: myFileList = getFilteredFileList(myFileList, false, true); //if filterOnInputList = true, then additional filtering is possible (e.g. file names containing "H08" and "D04" => H08 and D04 in list) //if filterOnInputList = false, then subsequent filtering is possible (e.g. file names containing "controls" and "positive" => positive controls, but not negative controls in list!) //this function needs global variables (see function setDialogImageFileFilter) //var filterStrings = newArray("","",""); //pre-definition of strings to filter //var availableFilterTerms = newArray("no filtering", "include", "exclude"); //dont change this //var filterTerms = newArray("no filtering", "no filtering", "no filtering"); function getFilteredFileList(fileListFunction, filterOnInputList, displayList) { skippedFilter = 0; for (i = 0; i < filterStrings.length; i++) { if (filterTerms[i] != availableFilterTerms[0]) { returnedFileList = newArray(0); //this list stores all files found to have the extension and is returned at the end of the function for (j = 0; j < fileListFunction.length; j++) { if (filterTerms[i] == "include" && indexOf(fileListFunction[j],filterStrings[i]) != -1) returnedFileList = Array.concat(returnedFileList,fileListFunction[j]); if (filterTerms[i] == "exclude" && indexOf(fileListFunction[j],filterStrings[i]) <= 0) returnedFileList = Array.concat(returnedFileList,fileListFunction[j]); } print(returnedFileList.length + " file(s) found after filter: " + filterTerms[i] + " text " + filterStrings[i] + "."); if (displayList) {Array.show("List of files - after filtering for " + filterStrings[i], returnedFileList);} //see description above! default: filterOnInputList = false if(!filterOnInputList) fileListFunction = returnedFileList; } else skippedFilter++; } if (skippedFilter == filterStrings.length) returnedFileList = fileListFunction; //if no filter condition is selected return returnedFileList; } //function filters a list for a certain string (filter) //example: myList = getFilteredList(myList, "myText", true); function getFilteredList(inputList, filterStringFunction, displayList) { skippedFilter = 0; returnedList = newArray(0); //this list stores all items of the input list that were found to contain the filter string and is returned at the end of the function for (i = 0; i < inputList.length; i++) { if (indexOf(inputList[i],filterStringFunction) != -1) returnedList = Array.concat(returnedList,inputList[i]); } print(returnedList.length + " file(s) found after filtering: " + filterStringFunction); if (displayList) {Array.show("List after filtering for " + filterStringFunction, returnedFileList);} return returnedList; } //function gets all files from folders and subfolders //example: myFileList = getFileListSubfolder("/home/myFolder/", true); function getFileListSubfolder(inputPathFunction, displayList) { fileListFunction = getFileList(inputPathFunction); //read file list Array.sort(fileListFunction); returnedFileList = newArray(0); //this list stores all found files and is returned at the end of the function for (i = 0; i < fileListFunction.length; i++) { if ((File.separator == "\\") && (endsWith(fileListFunction[i], "/"))) fileListFunction[i] = replace(fileListFunction[i],"/",File.separator); //fix windows/Fiji File.separator bug if (endsWith(fileListFunction[i], File.separator)) { //if it is a folder returnedFileListTemp = newArray(0); returnedFileListTemp = getFileListSubfolder(inputPathFunction + fileListFunction[i],displayList); returnedFileList = Array.concat(returnedFileList, returnedFileListTemp); } else { //if it is a file returnedFileList = Array.concat(returnedFileList, inputPathFunction + fileListFunction[i]); //print(i, inputPath + fileList[i]); //to log window } } if(inputPathFunction == inputPath) { //if local variable is equal to global path variable = if path is folder and NOT subfolder print(returnedFileList.length + " file(s) found in selected folder and subfolders."); if (displayList) {Array.show("All files - all", returnedFileList);} } return returnedFileList; } //function filters all files with certain extension //example: myFileList = getFileType(myFileList, ".tif", true); function getFileType(fileListFunction, fileExtension, displayList) { returnedFileList = newArray(0); //this list stores all files found to have the extension and is returned at the end of the function if(lengthOf(fileExtension) > 0) { for (i = 0; i < fileListFunction.length; i++) { if (endsWith(fileListFunction[i],fileExtension)) returnedFileList = Array.concat(returnedFileList,fileListFunction[i]); } print(returnedFileList.length + " file(s) found with extension " + fileExtension + "."); if (displayList) {Array.show("All files - filtered for " + fileExtension, returnedFileList);} } else { returnedFileList = fileListFunction; } return returnedFileList; } //function returns a number in specific string format, e.g 2.5 => 02.500 //example: myStringNumber = getNumberToString(2.5, 3, 6); function getNumberToString(number, decimalPlaces, lengthNumberString) { numberString = "000000000000" + toString(number, decimalPlaces); //convert to number to string and add zeros in the front numberString = substring(numberString, lengthOf(numberString) - lengthNumberString, lengthOf(numberString)); //shorten string to lengthNumberString return numberString; } //function opens a dialog to set text list for filtering a list //example: setDialogImageFileFilter(); //this function set interactively the global variables used by the function getFilteredFileList //this function needs global variables! (see below) /* var fileExtension = ".tif"; //default definition of extension var filterStrings = newArray("","",""); //default definition of strings to filter var availableFilterTerms = newArray("no filtering", "include", "exclude"); //dont change this var filterTerms = newArray(filterStrings.length); for (i = 0; i < filterStrings.length; i++) {filterTerms[i] = "no filtering";} //default definition of filter types (automatic) //var filterTerms = newArray("no filtering", "no filtering", "no filtering"); //default definition of filter types (manual) var displayFileList = false; //shall array window be shown? */ function setDialogImageFileFilter() { Dialog.create("Image file filter..."); //enable use inveractivity Dialog.addMessage("Define the files to be processed:"); Dialog.addString("Files should have this extension:", fileExtension); //add extension Dialog.addMessage("Define filter for files:"); for (i = 0; i < filterStrings.length; i++) { Dialog.addString((i + 1) + ") Filter this text from file list: ", filterStrings[i]); Dialog.addChoice((i + 1) + ") Files with text are included/excluded?", availableFilterTerms, filterTerms[i]); } Dialog.addCheckbox("Check file lists?", displayFileList); //if check file lists will be displayed Dialog.show(); fileExtension = Dialog.getString(); for (i = 0; i < filterStrings.length; i++) { filterStrings[i] = Dialog.getString(); filterTerms[i] = Dialog.getChoice(); } displayFileList = Dialog.getCheckbox(); } //function saves the log window in the given path //example: saveLog("C:\\Temp\\Log_temp.txt"); function saveLog(logPath) { if (nImages > 0) currentWindow = getTitle(); selectWindow("Log"); saveAs("Text", logPath); if (nImages > 0) selectWindow(currentWindow); } //function returnes the unique fields (all fields of all wells, e.g. F001, F002,...) of an array of CV7000 files //example: myUniqueFields = getUniqueFieldListCV7000(myList, true); function getUniqueFieldListCV7000(inputArray, displayList) { if(inputArray.length < 1) { print("No fields found!"); return newArray(0); } currentField = substring(inputArray[0],lastIndexOf(inputArray[0],"_T00")+6,lastIndexOf(inputArray[0],"_T00")+10); //first field found returnedFieldList = Array.concat(currentField); //this list stores all unique fields found and is returned at the end of the function for (i = 0; i < inputArray.length; i++) { j = 0; //counter for returned field list valueUnique = true; //as long as value was not found in array of unique values while (valueUnique && (returnedFieldList.length > j)) { //as long as value was not found in array of unique values and end of array is not reached currentField = substring(inputArray[i], lastIndexOf(inputArray[i],"_T00")+6, lastIndexOf(inputArray[i],"_T00")+10); if(returnedFieldList[j] == currentField) { valueUnique = false; //if value was found in array of unique values stop while loop } else { j++; } } //end while if (valueUnique) returnedFieldList = Array.concat(returnedFieldList, currentField); //if value was not found in array of unique values add it to the end of the array of unique values } print(returnedFieldList.length + " field(s) found."); Array.sort(returnedFieldList); if (displayList) {Array.show("List of " + returnedFieldList.length + " unique fields", returnedFieldList);} return returnedFieldList; } //function returns the unique well fields (all fields of all wells, e.g. G10_T0001F001) of an array of CV7000 files //example: myUniqueWellFields = getUniqueWellFieldListCV7000(myList, true); function getUniqueWellFieldListCV7000(inputArray, displayList) { if(inputArray.length < 1) { print("No well fields found!"); return newArray(0); } currentWellField = substring(inputArray[0],lastIndexOf(inputArray[0],"_T00")-3,lastIndexOf(inputArray[0],"_T00")+10); //first well field found returnedWellFieldList = Array.concat(currentWellField); //this list stores all unique well fields found and is returned at the end of the function for (i = 0; i < inputArray.length; i++) { j = 0; //counter for returned well field list valueUnique = true; //as long as value was not found in array of unique values while (valueUnique && (returnedWellFieldList.length > j)) { //as long as value was not found in array of unique values and end of array is not reached currentWellField = substring(inputArray[i],lastIndexOf(inputArray[i],"_T00")-3,lastIndexOf(inputArray[i],"_T00")+10); if(returnedWellFieldList[j] == currentWellField) { valueUnique = false; //if value was found in array of unique values stop while loop } else { j++; } } //end while if (valueUnique) returnedWellFieldList = Array.concat(returnedWellFieldList, currentWellField); //if value was not found in array of unique values add it to the end of the array of unique values } print(returnedWellFieldList.length + " well field(s) found."); Array.sort(returnedWellFieldList); if (displayList) {Array.show("List of " + returnedWellFieldList.length + " unique well fields", returnedWellFieldList);} return returnedWellFieldList; } //function returnes the unique wells of an array of CV7000 files //example: myUniqueWells = getUniqueWellListCV7000(myList, true); function getUniqueWellListCV7000(inputArray, displayList) { if(inputArray.length < 1) { print("No wells found!"); return newArray(0); } currentWell = substring(inputArray[0],lastIndexOf(inputArray[0],"_T00")-3,lastIndexOf(inputArray[0],"_T00")); //first well found returnedWellList = Array.concat(currentWell); //this list stores all unique wells found and is returned at the end of the function for (i = 1; i < inputArray.length; i++) { j = 0; //counter for returned well list valueUnique = true; //as long as value was not found in array of unique values while (valueUnique && (returnedWellList.length > j)) { //as long as value was not found in array of unique values and end of array is not reached currentWell = substring(inputArray[i],lastIndexOf(inputArray[i],"_T00")-3,lastIndexOf(inputArray[i],"_T00")); if(returnedWellList[j] == currentWell) { valueUnique = false; //if value was found in array of unique values stop while loop } else { j++; } } //end while if (valueUnique) returnedWellList = Array.concat(returnedWellList, currentWell); //if value was not found in array of unique values add it to the end of the array of unique values } print(returnedWellList.length + " well(s) found."); Array.sort(returnedWellList); if (displayList) {Array.show("List of " + returnedWellList.length + " unique wells", returnedWellList);} return returnedWellList; }