# this is a python rewrite of the original ijm published at # https://github.com/Hyojung-Choo/Myosoft/blob/Myosoft-hub/Scripts/central%20nuclei%20counter.ijm # IJ imports # TODO: are the imports RoiManager and ResultsTable needed when using the services? from ij import IJ, WindowManager as wm from ij.plugin import Duplicator, RoiEnlarger, RoiScaler from trainableSegmentation import WekaSegmentation from de.biovoxxel.toolbox import Extended_Particle_Analyzer # Bio-formats imports from loci.plugins import BF from loci.plugins.in import ImporterOptions # python imports import time import os #@ String (visibility=MESSAGE, value="<html><b> Welcome to Myosoft! </b></html>") msg1 #@ File (label="Select fiber-ROIs zip-file", style="file") roi_zip #@ File (label="Select image file", description="select your image") path_to_image #@ File (label="Select directory for output", style="directory") output_dir #@ Boolean (label="close image after processing", description="tick this box when using batch mode", value=False) close_raw #@ String (visibility=MESSAGE, value="<html><b> channel positions in the hyperstack </b></html>") msg5 #@ Integer (label="Fiber staining 1 channel number (0=n.a.)", style="slider", min=0, max=5, value=1) fiber_channel_1 #@ Integer (label="Fiber staining 2 channel number (0=n.a.)", style="slider", min=0, max=5, value=2) fiber_channel_2 #@ Integer (label="Fiber staining 3 channel number (0=n.a.)", style="slider", min=0, max=5, value=3) fiber_channel_3 #@ Integer (label="minimum fiber intensity (0=auto)", description="0 = automatic threshold detection", value=0) min_fiber_intensity_1 #@ Integer (label="minimum fiber intensity (0=auto)", description="0 = automatic threshold detection", value=0) min_fiber_intensity_2 #@ Integer (label="minimum fiber intensity (0=auto)", description="0 = automatic threshold detection", value=0) min_fiber_intensity_3 #@ ResultsTable rt #@ RoiManager rm def fix_ij_options(): """Configure ImageJ (IJ) to a predefined state. This function sets various options in ImageJ to ensure a consistent environment for image processing tasks. It includes settings for appearance, color management, binary image options, and file output format. """ # Disable inverting LUT (Look-Up Table) for images IJ.run("Appearance...", " menu=0 16-bit=Automatic") # Set foreground color to be white and background color to be black, with red for selections IJ.run("Colors...", "foreground=white background=black selection=red") # Enable black background for binary images and pad edges when eroding IJ.run("Options...", "black pad") # Set saving format to .txt files, saving both columns and rows IJ.run("Input/Output...", "file=.txt save_column save_row") # Set "Black Background" option for binary operations IJ.run("Options...", "black") # Enable scaling when converting images between types IJ.run("Conversions...", "scale") def fix_ij_dirs(path): """Replace backslashes with forward slashes in directory paths. This function takes a directory path obtained from a dialogue or script parameter and returns a more robust path with forward slashes as separators. Parameters ---------- path : str A directory path Returns ------- str A directory path with forward slashes as separators """ fixed_path = str(path).replace("\\", "/") # fixed_path = fixed_path + "/" return fixed_path def open_image_with_BF(path_to_file): """Open the first image from a file using Bio-Formats. This function utilizes Bio-Formats to open an image file and returns the first image contained within the file. The image is opened in grayscale mode with autoscaling enabled. Parameters ---------- path_to_file : str The file path to the image file to be opened. Returns ------- ImagePlus The first image contained in the specified file. """ # Create an ImporterOptions object for configuring the import process options = ImporterOptions() # Set the color mode to grayscale options.setColorMode(ImporterOptions.COLOR_MODE_COMPOSITE ) # Enable autoscaling for the image options.setAutoscale(True) # Set the file path for the image to be opened options.setId(path_to_file) # Open the image(s) using Bio-Formats and store them in an array imps = BF.openImagePlus(options) # Return the first image in the array return imps[0] def fix_BF_czi_imagetitle(imp): """ Fix the title of an image read using the bio-formats importer. The title is modified to remove the ".czi" extension and replace spaces with underscores. Parameters ---------- imp : ij.ImagePlus The image to be processed. Returns ------- str The modified title of the image. """ # Get the short title of the image (without path) image_title = os.path.basename(imp.getShortTitle()) # Remove the ".czi" extension image_title = image_title.replace(".czi", "") # Replace spaces with underscores image_title = image_title.replace(" ", "_") # Remove any double underscores image_title = image_title.replace("_-_", "") # Remove any double underscores image_title = image_title.replace("__", "_") # Remove any "#" characters image_title = image_title.replace("#", "Series") return image_title def clear_ij_roi_manager(rm): """ Clear all ROIs from the RoiManager. Parameters ---------- rm : ij.plugin.frame.RoiManager a reference of the IJ-RoiManager """ # Run the "reset" command in the RoiManager to clear all ROIs rm.runCommand('reset') def get_threshold_from_method(imp, channel, method): """Get the threshold value of chosen IJ AutoThreshold method in desired channel. Parameters ---------- imp : ImagePlus The imp from which to get the threshold value. channel : int The channel in which to get the threshold. method : str The AutoThreshold method to use. Returns ------- list A list containing the upper and the lower threshold (integer values). """ # Set the channel of the imp to the desired channel imp.setC(channel) # starts at 1 # Get the processor of the imp ip = imp.getProcessor() # Set the AutoThreshold method to the desired method ip.setAutoThreshold(method + " dark") # Get the minimum and maximum threshold values lower_thr = ip.getMinThreshold() upper_thr = ip.getMaxThreshold() # Reset the threshold so that the imp is not affected by this function ip.resetThreshold() return [lower_thr, upper_thr] def measure_in_all_rois( imp, channel, rm ): """Measures in all ROIs on a given channel of imp all parameters that are set in IJ "Set Measurements". This function takes an ImagePlus (imp), a channel (integer, starts at 1), and a RoiManager (rm) as parameters. It then sets the channel of the imp to the desired channel, deselects all ROIs in the RoiManager, and measures in all ROIs of the RoiManager on the channel of the imp. The parameters that are measured are the ones that are set in IJ "Set Measurements". Parameters ---------- imp : ij.ImagePlus the imp to measure on channel : int the channel to measure in. starts at 1. rm : ij.plugin.frame.RoiManager a reference of the IJ-RoiManager """ # Set the channel of the imp to the desired channel imp.setC(channel) # Deselect all ROIs in the RoiManager rm.runCommand(imp,"Deselect") # Measure in all ROIs of the RoiManager on the channel of the imp rm.runCommand(imp,"Measure") def change_all_roi_color(rm, color): """ Change the color of all ROIs in the RoiManager. Parameters ---------- rm : ij.plugin.frame.RoiManager A reference to the IJ-RoiManager. color : str The desired color, e.g., "green", "red", "yellow", "magenta". Returns ------- None """ # Select all ROIs in the RoiManager #rm.setSelectedIndexes(range(rm.getCount())) rm.runCommand("Deselect") # Change the color of all ROIs to the desired color rm.runCommand("Set Color", color) # # Deselect all ROIs again to finalize changes # rm.runCommand("Deselect") def change_subset_roi_color(rm, selected_rois, color): """ Change the color of selected ROIs in the RoiManager. Parameters ---------- rm : ij.plugin.frame.RoiManager A reference to the IJ-RoiManager. selected_rois : list of int Indices of ROIs in the RoiManager to change. color : str The desired color, e.g., "green", "red", "yellow", "magenta", etc. """ # Deselect all currently selected ROIs in the RoiManager rm.runCommand("Deselect") # Select the specified ROIs by their indices rm.setSelectedIndexes(selected_rois) # Change the color of the selected ROIs to the specified color rm.runCommand("Set Color", color) # Deselect all ROIs again to finalize changes rm.runCommand("Deselect") def show_all_rois_on_image(rm, imp): """ Display all ROIs on the given image using the ROI Manager. Parameters ---------- rm : ij.plugin.frame.RoiManager A reference to the IJ-RoiManager. imp : ij.ImagePlus The image on which to display the ROIs. """ # Show the image in the ImageJ window imp.show() # Use the ROI Manager to show all ROIs on the image rm.runCommand(imp, "Show All") def save_all_rois(rm, target): """Save all ROIs in the RoiManager as a zip file to the given target path. Parameters ---------- rm : ij.plugin.frame.RoiManager A reference to the IJ-RoiManager. target : str The file path in which to save the ROIs, e.g., /my-images/resulting_rois.zip """ # Save all ROIs in the RoiManager to the given target path rm.runCommand("Save", target) def save_selected_rois(rm, selected_rois, target): """ Save selected ROIs in the RoiManager as a zip file to the given target path. Parameters ---------- rm : ij.plugin.frame.RoiManager A reference to the IJ-RoiManager. selected_rois : list of int Indices of ROIs in the RoiManager to save. target : str The file path in which to save the ROIs, e.g., /my-images/resulting_rois_subset.zip """ # Deselect all currently selected ROIs in the RoiManager to ensure a clean start rm.runCommand("Deselect") # Select the specified ROIs by their indices rm.setSelectedIndexes(selected_rois) # Save the selected ROIs to the given target path as a zip file rm.runCommand("save selected", target) # Deselect all ROIs again to finalize changes and maintain a clean state rm.runCommand("Deselect") def select_positive_fibers( imp, channel, rm, min_intensity ): """For all ROIs in the RoiManager, select ROIs based on intensity measurement in given channel of imp. See https://imagej.nih.gov/ij/developer/api/ij/process/ImageStatistics.html Parameters ---------- imp : ij.ImagePlus The ImagePlus on which to measure. channel : int The channel on which to measure. starts at 1. rm : ij.plugin.frame.RoiManager A reference of the IJ-RoiManager. min_intensity : int The selection criterion (here: intensity threshold). Returns ------- list A selection of ROIs which passed the selection criterion (are above the threshold). """ imp.setC(channel) all_rois = rm.getRoisAsArray() selected_rois = [] for i, roi in enumerate(all_rois): imp.setRoi(roi) stats = imp.getStatistics() if stats.mean > min_intensity: selected_rois.append(i) return selected_rois def open_rois_from_zip(rm, path): """ Open ROIs from a zip file and add them to the RoiManager. Parameters ---------- rm : ij.plugin.frame.RoiManager A reference to the IJ-RoiManager. path : str The file path to the ROI zip file. """ rm.runCommand("Open", path) def preset_results_column( rt, column, value): """Pre-set all rows in given column of the IJ-ResultsTable with desired value. Parameters ---------- rt : ij.measure.ResultsTable A reference of the IJ-ResultsTable column : str The desired column. Will be created if it does not yet exist value : str or float or int The value to be set """ for i in range( rt.size() ): rt.setValue(column, i, value) rt.show("Results") def add_results(rt, column, row, value): """Add a value in specified rows of a given column. Parameters ---------- rt : ij.measure.ResultsTable A reference to the IJ-ResultsTable. column : str The column in which to add the values. row : list of int The row numbers in which to add the values. value : str, float, or int The value to be set. """ # Iterate over each row index in the row list for i in range(len(row)): # Set the specified value in the given column and row rt.setValue(column, row[i], value) # Display the updated ResultsTable rt.show("Results") def enhance_contrast(imp): """Use "Auto" Contrast & Brightness settings in each channel of imp. Parameters ---------- imp : ij.ImagePlus The imp on which to change C&B. """ for channel in range(imp.getDimensions()[2]): imp.setC(channel + 1) # IJ channels start at 1 IJ.run(imp, "Enhance Contrast", "saturated=0.35") def renumber_rois(rm): """Rename all ROIs in the RoiManager according to their number. The RoiManager uses 0-based indexing, so the first ROI is at index 0. This function renames each ROI with its index (starting from 1). Parameters ---------- rm : ij.plugin.frame.RoiManager A reference to the IJ-RoiManager. """ number_of_rois = rm.getCount() for roi in range( number_of_rois ): rm.rename( roi, str(roi + 1) ) def setup_defined_ij(rm, rt): """Set up a clean and defined Fiji user environment. This function configures the ImageJ environment to a predefined state by resetting the RoiManager and ResultsTable, and clearing the IJ log. Parameters ---------- rm : ij.plugin.frame.RoiManager A reference to the IJ-RoiManager. rt : ij.measure.ResultsTable A reference to the IJ-ResultsTable. """ # Configure IJ options to a predefined state fix_ij_options() # Reset the RoiManager to remove all existing ROIs rm.runCommand('reset') # Reset the ResultsTable to clear all previous results rt.reset() # Clear the IJ log to ensure a fresh output window IJ.log("\\Clear") execution_start_time = time.time() setup_defined_ij(rm, rt) # open image using Bio-Formats path_to_image = fix_ij_dirs(path_to_image) raw = open_image_with_BF(path_to_image) raw.hide() # get image info raw_image_calibration = raw.getCalibration() raw_image_title = fix_BF_czi_imagetitle(raw) # take care of paths and directories input_rois_path = fix_ij_dirs( roi_zip ) output_dir = fix_ij_dirs(output_dir) + "/2c_fibertyping" if not os.path.exists( str(output_dir) ): os.makedirs( str(output_dir) ) # open ROIS and show on image open_rois_from_zip( rm, str(input_rois_path) ) change_all_roi_color(rm, "blue") # show_all_rois_on_image( rm, raw ) # update the log for the user IJ.log( "Now working on " + str(raw_image_title) ) if raw_image_calibration.scaled() == False: IJ.log("Your image is not spatially calibrated! Size measurements are only possible in [px].") IJ.log( " -- settings used -- ") IJ.log( "Selected fiber-ROIs zip-file = " + str(input_rois_path) ) IJ.log( "Fiber staining 1 channel number = " + str(fiber_channel_1) ) IJ.log( "Fiber staining 2 channel number = " + str(fiber_channel_2) ) IJ.log( "Fiber staining 3 channel number = " + str(fiber_channel_3) ) IJ.log( " -- settings used -- ") # measure size & shape, IJ.run("Set Measurements...", "area perimeter shape feret's redirect=None decimal=4") IJ.run("Clear Results", "") measure_in_all_rois( raw, fiber_channel_1, rm ) # loop through the fiber channels, check if positive, add info to results table all_fiber_channels = [fiber_channel_1, fiber_channel_2, fiber_channel_3] all_min_fiber_intensities = [min_fiber_intensity_1, min_fiber_intensity_2, min_fiber_intensity_3] roi_colors = ["green", "orange", "red"] all_fiber_subsets =[ [], [], [] ] for index, fiber_channel in enumerate(all_fiber_channels): if fiber_channel > 0: preset_results_column( rt, "channel " + str(fiber_channel) + " positive (" + roi_colors[index] + ")", "NO" ) if all_min_fiber_intensities[index] == 0: all_min_fiber_intensities[index] = get_threshold_from_method(raw, fiber_channel, "Mean")[0] IJ.log( "fiber channel " + str(fiber_channel) + " intensity threshold: " + str(all_min_fiber_intensities[index]) ) positive_fibers = select_positive_fibers( raw, fiber_channel, rm, all_min_fiber_intensities[index] ) all_fiber_subsets[index] = positive_fibers if len(positive_fibers) > 0: change_subset_roi_color(rm, positive_fibers, roi_colors[index]) save_selected_rois( rm, positive_fibers, output_dir + "/" + raw_image_title + "_positive_fiber_rois_c" + str( fiber_channel ) + ".zip") add_results( rt, "channel " + str(fiber_channel) + " positive (" + roi_colors[index] + ")", positive_fibers, "YES") # single positive positive_c1 = all_fiber_subsets[0] positive_c2 = all_fiber_subsets[1] positive_c3 = all_fiber_subsets[2] # double positive positive_c1_c2 = list( set(all_fiber_subsets[0]).intersection(all_fiber_subsets[1]) ) positive_c1_c3 = list( set(all_fiber_subsets[0]).intersection(all_fiber_subsets[2]) ) positive_c2_c3 = list( set(all_fiber_subsets[1]).intersection(all_fiber_subsets[2]) ) # triple positive positive_c1_c2_c3 = list( set(positive_c1_c2).intersection(all_fiber_subsets[2]) ) # update ROI color & results table for double and triple positives channels = [ (positive_c1_c2, [fiber_channel_1, fiber_channel_2], "magenta"), (positive_c1_c3, [fiber_channel_1, fiber_channel_3], "yellow"), (positive_c2_c3, [fiber_channel_2, fiber_channel_3], "cyan"), (positive_c1_c2_c3, [fiber_channel_1, fiber_channel_2, fiber_channel_3], "white") ] for positives, ch_nums, color in channels: if positives: ch_str = ",".join(map(str, ch_nums)) color_label = "channel %s positive (%s)" % (ch_str, color) preset_results_column(rt, color_label.replace(',', '-'), "NO") change_subset_roi_color(rm, positives, color) save_selected_rois(rm, positives, "%s/%s_positive_fiber_rois_c%s.zip" % (output_dir, raw_image_title, '_c'.join(map(str, ch_nums)))) add_results(rt, color_label.replace(',', '-'), positives, "YES") # save all results together save_all_rois( rm, output_dir + "/" + raw_image_title + "_all_fiber_type_rois_color-coded.zip" ) rt.save(output_dir + "/" + raw_image_title + "_fibertyping_results.csv") # dress up the original image, save a overlay-png, present original to the user raw.show() show_all_rois_on_image( rm, raw ) # raw.setDisplayMode(IJ.COMPOSITE) enhance_contrast( raw ) IJ.run("From ROI Manager", "") # ROIs -> overlays so they show up in the saved png qc_duplicate = raw.duplicate() IJ.saveAs(qc_duplicate, "PNG", output_dir + "/" + raw_image_title + "_fibertyping") qc_duplicate.close() wm.toFront( raw.getWindow() ) # IJ.run("Remove Overlay", "") # raw.setDisplayMode(IJ.GRAYSCALE) # show_all_rois_on_image( rm, raw ) total_execution_time_min = (time.time() - execution_start_time) / 60.0 IJ.log("total time in minutes: " + str(total_execution_time_min)) IJ.log( "~~ all done ~~" ) IJ.selectWindow("Log") IJ.saveAs("Text", str(output_dir + "/" + raw_image_title + "_fibertyping_Log")) if close_raw == True: raw.close()