diff --git a/2c_fibertyping.py b/2c_fibertyping.py old mode 100755 new mode 100644 index bb58eabf403e58b9af11bc106e2aea127b398e06..cc43e624329573dd4b31fc13c2b918dbea98718a --- a/2c_fibertyping.py +++ b/2c_fibertyping.py @@ -1,4 +1,4 @@ -# this is a python rewrite of the original ijm published at +# 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 @@ -33,37 +33,47 @@ import os def fix_ij_options(): - """put IJ into a defined state + """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 + # Disable inverting LUT (Look-Up Table) for images IJ.run("Appearance...", " menu=0 16-bit=Automatic") - # set foreground color to be white, background black + + # 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") - # black BG for binary images and pad edges when eroding + + # Enable black background for binary images and pad edges when eroding IJ.run("Options...", "black pad") - # set saving format to .txt files + + # Set saving format to .txt files, saving both columns and rows IJ.run("Input/Output...", "file=.txt save_column save_row") - # ============= DON’T MOVE UPWARDS ============= - # set "Black Background" in "Binary Options" + + # Set "Black Background" option for binary operations IJ.run("Options...", "black") - # scale when converting = checked + + # Enable scaling when converting images between types IJ.run("Conversions...", "scale") def fix_ij_dirs(path): - """use forward slashes in directory paths + """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 : string - a directory path obtained from dialogue or script parameter + path : str + A directory path Returns ------- - string - a more robust path with forward slashes as separators + str + A directory path with forward slashes as separators """ - fixed_path = str(path).replace("\\", "/") # fixed_path = fixed_path + "/" @@ -71,169 +81,239 @@ def fix_ij_dirs(path): def open_image_with_BF(path_to_file): - """ use Bio-Formats to opens the first image from an image file path + """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 : string - path to the image file + path_to_file : str + The file path to the image file to be opened. Returns ------- ImagePlus - the first imp stored in a give file + The first image contained in the specified file. """ + # Create an ImporterOptions object for configuring the import process options = ImporterOptions() - options.setColorMode(ImporterOptions.COLOR_MODE_GRAYSCALE) + # 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) - imps = BF.openImagePlus(options) # is an array of ImagePlus + # 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): - image_title = os.path.basename( imp.getShortTitle() ) + """ + 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): - """delete all ROIs from the RoiManager + """ + Clear all ROIs from the RoiManager. Parameters ---------- - rm : RoiManager + 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): - """returns the threshold value of chosen IJ AutoThreshold method in desired channel + """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 : integer - the channel in which to get the treshold - method : string - the AutoThreshold method to use + 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 - the upper and the lower threshold (integer values) + 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 + 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" + """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 : ImagePlus + imp : ij.ImagePlus the imp to measure on - channel : integer + channel : int the channel to measure in. starts at 1. - rm : RoiManager + 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 +def change_all_roi_color(rm, color): + """ + Change the color of all ROIs in the RoiManager. Parameters ---------- - rm : RoiManager - a reference of the IJ-RoiManager - color : string - the desired color. e.g. "green", "red", "yellow", "magenta" ... + rm : ij.plugin.frame.RoiManager + A reference to the IJ-RoiManager. + color : str + The desired color, e.g., "green", "red", "yellow", "magenta". + + Returns + ------- + None """ - number_of_rois = rm.getCount() - for roi in range( number_of_rois ): - rm.select(roi) - rm.runCommand("Set Color", color) + # 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 +def change_subset_roi_color(rm, selected_rois, color): + """ + Change the color of selected ROIs in the RoiManager. Parameters ---------- - rm : RoiManager - a reference of the IJ-RoiManager - selected_rois : array - ROIs in the RoiManager to change - color : string - the desired color. e.g. "green", "red", "yellow", "magenta" ... + 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): - """shows all ROIs in the ROiManager on imp + """ + Display all ROIs on the given image using the ROI Manager. Parameters ---------- - rm : RoiManager - a reference of the IJ-RoiManager - imp : ImagePlus - the imp on which to show the ROIs + 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() - rm.runCommand(imp,"Show All") + # 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 zip to target path + """Save all ROIs in the RoiManager as a zip file to the given target path. Parameters ---------- - rm : RoiManager - a reference of the IJ-RoiManager - target : string - the path in to store the ROIs. e.g. /my-images/resulting_rois.zip + 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 zip to target path +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 : RoiManager - a reference of the IJ-RoiManager - selected_rois : array - ROIs in the RoiManager to save - target : string - the path in to store the ROIs. e.g. /my-images/resulting_rois_subset.zip + 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") @@ -243,19 +323,19 @@ def select_positive_fibers( imp, channel, rm, min_intensity ): Parameters ---------- - imp : ImagePlus - the imp on which to measure - channel : integer - the channel on which to measure. starts at 1 - rm : RoiManager - a reference of the IJ-RoiManager - min_intensity : integer - the selection criterion (here: intensity threshold) - + 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 ------- - array - a selection of ROIs which passed the selection criterion (are above the threshold) + list + A selection of ROIs which passed the selection criterion (are above the threshold). """ imp.setC(channel) all_rois = rm.getRoisAsArray() @@ -266,33 +346,34 @@ def select_positive_fibers( imp, channel, rm, min_intensity ): if stats.mean > min_intensity: selected_rois.append(i) - return selected_rois + return selected_rois -def open_rois_from_zip( rm, path ): - """open RoiManager ROIs from zip and adds them to the RoiManager +def open_rois_from_zip(rm, path): + """ + Open ROIs from a zip file and add them to the RoiManager. Parameters ---------- - rm : RoiManager - a reference of the IJ-RoiManager - path : string - path to the ROI zip file + 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 + """Pre-set all rows in given column of the IJ-ResultsTable with desired value. Parameters ---------- - rt : ResultsTable - a reference of the IJ-ResultsTable - column : string - the desired column. will be created if it does not yet exist - value : string or float or integer - the value to be set + 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) @@ -300,46 +381,52 @@ def preset_results_column( rt, column, value): rt.show("Results") -def add_results( rt, column, row, value ): - """adds a value in desired rows of a given column +def add_results(rt, column, row, value): + """Add a value in specified rows of a given column. Parameters ---------- - rt : ResultsTable - a reference of the IJ-ResultsTable - column : string - the column in which to add the values - row : array - the row numbers in which too add the values. - value : string or float or integer - the value to be set + 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. """ - for i in range( len( row ) ): + # 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 +def enhance_contrast(imp): + """Use "Auto" Contrast & Brightness settings in each channel of imp. Parameters ---------- - imp : ImagePlus - the imp on which to change C&B + 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 + 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 + """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 : RoiManager - a reference of the IJ-RoiManager + rm : ij.plugin.frame.RoiManager + A reference to the IJ-RoiManager. """ number_of_rois = rm.getCount() for roi in range( number_of_rois ): @@ -347,18 +434,28 @@ def renumber_rois(rm): def setup_defined_ij(rm, rt): - """set up a clean and defined Fiji user environment + """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 : RoiManager - a reference of the IJ-RoiManager - rt : ResultsTable - a reference of the IJ-ResultsTable + 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") @@ -368,6 +465,7 @@ 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() @@ -383,7 +481,7 @@ if not os.path.exists( 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 ) +# show_all_rois_on_image( rm, raw ) # update the log for the user IJ.log( "Now working on " + str(raw_image_title) ) @@ -412,7 +510,7 @@ for index, fiber_channel in enumerate(all_fiber_channels): 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]) ) + 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: @@ -432,29 +530,21 @@ positive_c2_c3 = list( set(all_fiber_subsets[1]).intersection(all_fiber_subsets[ positive_c1_c2_c3 = list( set(positive_c1_c2).intersection(all_fiber_subsets[2]) ) # update ROI color & results table for double and triple positives -if len(positive_c1_c2) > 0: - preset_results_column( rt, "channel " + str(fiber_channel_1) + "," + str(fiber_channel_2) + " positive (magenta)", "NO" ) - change_subset_roi_color(rm, positive_c1_c2, "magenta") - save_selected_rois( rm, positive_c1_c2, output_dir + "/" + raw_image_title + "_positive_fiber_rois_c" + str(fiber_channel_1) + "_c" + str(fiber_channel_2) + ".zip") - add_results( rt, "channel " + str(fiber_channel_1) + "," + str(fiber_channel_2) + " positive (magenta)", positive_c1_c2, "YES") - -if len(positive_c1_c3) > 0: - preset_results_column( rt, "channel " + str(fiber_channel_1) + "," + str(fiber_channel_3) + " positive (yellow)", "NO" ) - change_subset_roi_color(rm, positive_c1_c3, "yellow") - save_selected_rois( rm, positive_c1_c3, output_dir + "/" + raw_image_title + "_positive_fiber_rois_c" + str(fiber_channel_1) + "_c" + str(fiber_channel_3) + ".zip") - add_results( rt, "channel " + str(fiber_channel_1) + "," + str(fiber_channel_3) + " positive (yellow)", positive_c1_c3, "YES") - -if len(positive_c2_c3) > 0: - preset_results_column( rt, "channel " + str(fiber_channel_2) + "," + str(fiber_channel_3) + " positive (cyan)", "NO" ) - change_subset_roi_color(rm, positive_c2_c3, "cyan") - save_selected_rois( rm, positive_c2_c3, output_dir + "/" + raw_image_title + "_positive_fiber_rois_c" + str(fiber_channel_2) + "_c" + str(fiber_channel_3) + ".zip") - add_results( rt, "channel " + str(fiber_channel_2) + "," + str(fiber_channel_3) + " positive (cyan)", positive_c2_c3, "YES") - -if len(positive_c1_c2_c3) > 0: - preset_results_column( rt, "channel " + str(fiber_channel_1) + "," + str(fiber_channel_2) + "," + str(fiber_channel_3) + " positive(white)", "NO" ) - change_subset_roi_color(rm, positive_c1_c2_c3, "white") - save_selected_rois( rm, positive_c1_c2_c3, output_dir + "/" + raw_image_title + "_positive_fiber_rois_c" + str(fiber_channel_1) + "_c" + str(fiber_channel_2) + "_c" + str(fiber_channel_3) + ".zip") - add_results( rt, "channel " + str(fiber_channel_1) + "," + str(fiber_channel_2) + "," + str(fiber_channel_3) + " positive(white)", positive_c1_c2_c3, "YES") +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" ) @@ -463,16 +553,16 @@ 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) +# 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 ) +# 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 ~~" ) diff --git a/README.md b/README.md index 5b106e1f781e1988516ad1adad9ca4a199793ee0..850f04b4ea311ea8b0604bf833ae2ff87df96c0a 100644 --- a/README.md +++ b/README.md @@ -11,9 +11,10 @@ Original code: <https://github.com/Hyojung-Choo/Myosoft/tree/Myosoft-hub> ## [`1_identify_fibers.py`](1_identify_fibers.py) -- Will identify all fibers based on the membrane staining using [Cellpose](https://github.com/MouseLand/cellpose) segmentation, filter them according to the morphometric gates and save the - corresponding ROIs. - - Need to be installed ont the machine where the script is run. Follow [this guide](https://wiki.biozentrum.unibas.ch/display/IMCF/Cellpose+python+environment) to create the environment. +- Will identify all fibers based on the membrane staining using [Cellpose](https://github.com/MouseLand/cellpose) + segmentation, filter them according to the morphometric gates and save the corresponding ROIs. + - Need to be installed on the machine where the script is run. Follow + [this guide](https://wiki.biozentrum.unibas.ch/display/IMCF/Cellpose+python+environment) to create the environment. Please don't recreate the environment if it already exists! - Will now also save the Cellpose segmentation as a binary so it can be edited manually. If you do so, you need to run the "extended particle analyzer" manually as well to choose & apply the morphometric gates.