From fb7bde6a02260bdb028147ba2ad422a26796d940 Mon Sep 17 00:00:00 2001 From: Laurent Guerard Date: Wed, 16 Apr 2025 14:28:41 +0200 Subject: [PATCH 01/15] Use imcflibs methods instead of local methods and imports --- .../IMCF_Utilities/Analyze/PSF Inspector.py | 386 ++---------------- 1 file changed, 42 insertions(+), 344 deletions(-) diff --git a/src/main/resources/scripts/Plugins/IMCF_Utilities/Analyze/PSF Inspector.py b/src/main/resources/scripts/Plugins/IMCF_Utilities/Analyze/PSF Inspector.py index ff15684..9f20797 100644 --- a/src/main/resources/scripts/Plugins/IMCF_Utilities/Analyze/PSF Inspector.py +++ b/src/main/resources/scripts/Plugins/IMCF_Utilities/Analyze/PSF Inspector.py @@ -28,46 +28,17 @@ ZProjector, ) from ij.plugin.frame import RoiManager +from imcflibs.imagej import bioformats as bf +from imcflibs.imagej import misc, omerotools from java.awt import Color, Font # java imports from java.lang import Double, Long, String from java.util import ArrayList -from java.text import SimpleDateFormat - -from loci.plugins import BF, LociExporter -from loci.plugins.in import ImporterOptions -from loci.plugins.out import Exporter - -from fr.igred.omero import Client -from fr.igred.omero.roi import ROIWrapper -from fr.igred.omero.annotations import TableWrapper, MapAnnotationWrapper - -from loci.formats.in import DefaultMetadataOptions, MetadataLevel - -from omero.gateway.model import ImageData, TableData, TableDataColumn +from omero.gateway.model import ImageData from omero.model import NamedValue -from omero.cmd import OriginalMetadataRequest -# ─── FUNCTIONS ────────────────────────────────────────────────────────────────── - -def BFImport(indivFile): - """Import the file using BioFormats - - Parameters - ---------- - indivFile : str - Path of the file to open - - Returns - ------- - list(ij.ImagePlus) - Array of ImagePlus read from the file - """ - options = ImporterOptions() - options.setId(str(indivFile)) - options.setColorMode(ImporterOptions.COLOR_MODE_COMPOSITE) - return BF.openImagePlus(options) +# ─── FUNCTIONS ────────────────────────────────────────────────────────────────── def coord_brightest_point(input_imp, selected_roi, best_slice): @@ -158,221 +129,6 @@ def scan_for_best_slice(input_imp, selected_roi): return stack_stats -def parse_url(omero_str): - """Parse an OMERO URL with one or multiple images selected - - Parameters - ---------- - omero_str : str - String which is either the link gotten from OMERO or image IDs separated by commas - - Returns - ------- - list(str) - List of all the images IDs parsed from the string - """ - if omero_str.startswith("https"): - image_ids = omero_str.split("image-") - image_ids.pop(0) - image_ids = [s.split("%")[0].replace("|", "") for s in image_ids] - else: - image_ids = omero_str.split(",") - return image_ids - - -def upload_image_to_omero(user_client, path, dataset_id): - """Upload the image back to OMERO - - Parameters - ---------- - user_client : fr.igred.omero.Client - Client used for login to OMERO - path : str - Path of the file to upload back to OMERO - dataset_id : Long - ID of the dataset where to upload the file - - """ - - user_client.getDataset(dataset_id).importImage(user_client, path) - - -def BFExport(imp, savepath): - """Export using BioFormats - - Parameters - ---------- - imp : ij.ImagePlus - ImagePlus of the file to save - savepath : str - Path where to save the image - - """ - - # print('Savepath: ', savepath) - paramstring = ( - "outfile=[" - + savepath - + "] " - + "windowless=true compression=Uncompressed saveROI=true" - ) - plugin = LociExporter() - plugin.arg = paramstring - exporter = Exporter(plugin, imp) - exporter.run() - -def progress_bar(progress, total, line_number, prefix=""): - """Progress bar for the IJ log window - - Parameters - ---------- - progress : int - Current step of the loop - total : int - Total number of steps for the loop - line_number : int - Number of the line to be updated - prefix : str, optional - Text to use before the progress bar, by default '' - """ - - size = 30 - x = int(size * progress / total) - IJ.log( - "\\Update%i:%s\t[%s%s] %i/%i\r" - % (line_number, prefix, "#" * x, "." * (size - x), progress, total) - ) - - -def add_annotation(user_client, repository_wpr, dict, header): - """Add annotation to OMERO object - - Parameters - ---------- - user_client : fr.igred.omero.Client - Client used for login to OMERO - repository_wpr : fr.igred.omero.repositor.GenericRepositoryObjectWrapper - Wrapper to the object for the anotation - dict : dict - Dictionary with the annotation to add - header : str - Name for the annotation header - """ - # for pair in dict: - # result.add - map_annotation_wpr = MapAnnotationWrapper(dict) - map_annotation_wpr.setNameSpace(header) - repository_wpr.addMapAnnotation(user_client, map_annotation_wpr) - -def delete_annotation(user_client, repository_wpr): - """Delete annotations linked to object - - Parameters - ---------- - user_client : fr.igred.omero.Client - Client used for login to OMERO - repository_wpr : fr.igred.omero.repositor.GenericRepositoryObjectWrapper - Wrapper to the object for the anotation - - """ - kv_pairs = repository_wpr.getMapAnnotations(user_client) - user_client.delete(kv_pairs) - -def save_rois_to_omero(user_client, image_wpr, rm): - """Save ROIs to OMERO linked to the image - - Parameters - ---------- - user_client : fr.igred.omero.Client - Client used for login to OMERO - image_wpr : fr.igred.omero.repositor.ImageWrapper - Wrapper to the image for the ROIs - rm : ij.plugin.frame.RoiManager - ROI Manager containing the ROIs - - """ - rois_list = rm.getRoisAsArray() - rois_arraylist = ArrayList(len(rois_list)) - for roi in rois_list: - rois_arraylist.add(roi) - rois_to_upload = ROIWrapper.fromImageJ(rois_arraylist) - image_wpr.saveROIs(user_client, rois_to_upload) - - -def get_acquisition_metadata_from_imageid(user_client, image_wpr): - """Get acquisition metadata from OMERO based on an image ID - - Parameters - ---------- - user_client : fr.igred.omero.Client - Client used for login to OMERO - image_wpr : fr.igred.omero.repositor.ImageWrapper - Wrapper to the image for the ROIs - - Returns - ------- - tuple of (int, int, str, int) - List of info about the acquisition - """ - ctx = user_client.getCtx() - instrument_data = user_client.getGateway().getMetadataService(ctx).loadInstrument( - image_wpr.asDataObject().getInstrumentId() - ) - objective_data = instrument_data.copyObjective().get(0) - if objective_data.getNominalMagnification() is None: - obj_mag = 0 - else: - obj_mag = objective_data.getNominalMagnification().getValue() - if objective_data.getLensNA() is None: - obj_na = 0 - else: - obj_na = objective_data.getLensNA().getValue() - if image_wpr.getAcquisitionDate() is None: - if image_wpr.asDataObject().getFormat() == "ZeissCZI": - field = "Information|Document|CreationDate" - date_field = get_info_from_original_metadata(user_client, image_id, field) - acq_date = date_field.split("T")[0] - acq_date_number = int(acq_date.replace("-", "")) - else: - acq_date = "NA" - acq_date_number = 0 - - else: - sdf = SimpleDateFormat("yyyy-MM-dd") - acq_date = sdf.format(image_wpr.getAcquisitionDate()) # image_wpr.getAcquisitionDate() - acq_date_number = int(acq_date.replace("-", "")) - - return obj_mag, obj_na, acq_date, acq_date_number - - -def get_info_from_original_metadata(user_client, image_id, field): - """Recovers information from the original metadata - - In some cases, some information aren't parsed correctly by BF and have to - get recovered directly from the original metadata. This gets the value - based on the field string. - - Parameters - ---------- - user_client : fr.igred.omero.Client - Client used for login to OMERO - image_id : int - ID of the image to look. - field : str - Field to look for in the original metadata. Needs to be found beforehand. - - Returns - ------- - str - Value of the field - """ - omr = OriginalMetadataRequest(Long(image_id)) - cmd = user_client.getGateway().submit(user_client.getCtx(), omr) - rsp = cmd.loop(5, 500) - gm = rsp.globalMetadata - return gm.get(field).getValue() - - def duplicate_imp_and_calibrate( imp, specific_chnl=None, specific_z=None, specific_t=None, roi=None ): @@ -526,29 +282,6 @@ def extract(list, index): return [item[index] for item in list] -def mean_from_list(values_list, round_decimals=0): - """Calculate the mean from a list - - Parameters - ---------- - list : list - List with numbers - round_decimals : int, optional - Rounding decimal to use for the result, by default 0 - - Returns - ------- - float - Mean value of the list - """ - filtered_list = filter(None, values_list) - try: - result = round(float(sum(filtered_list)) / len(filtered_list), round_decimals) - except ZeroDivisionError: - result = 0 - return result - - def bg_subtraction(imp, slice_number=None, roi=None, stat_to_use="mean"): """Subtract the background from an image based on stats @@ -648,53 +381,6 @@ def rescale_image(imp, width, height): return imp2 -def upload_array_as_omero_table(user_client, data, columns, image_wpr): - """Upload a table to OMERO plus from a list of lists - - Parameters - ---------- - user_client : fr.igred.omero.Client - Client used for login to OMERO - data : list(list()) - List of lists of results to upload - columns : list(str) - List of columns names - image_wpr : fr.igred.omero.repositor.ImageWrapper - Wrapper to the image to be uploaded - """ - dataset_wpr = image_wpr.getDatasets(user_client)[0] - - table_data = TableData(columns, data) - table_wpr = TableWrapper(table_data) - table_wpr.setName("PSF Inspector Table") - dataset_wpr.addTable(user_client, table_wpr) - -def create_table_columns(headings): - """Create the table headings from the ImageJ results table - - Parameters - ---------- - headings : list(str) - List of columns names - - Returns - ------- - list(omero.gateway.model.TableDataColumn) - List of columns formatted to be uploaded to OMERO - """ - table_columns = [] - # populate the headings - for h in range(len(headings)): - heading = headings.keys()[h] - type = headings.values()[h] - # OMERO.tables queries don't handle whitespace well - heading = heading.replace(" ", "_") - # title_heading = ["Slice", "Label"] - table_columns.append(TableDataColumn(heading, h, type)) - # table_columns.append(TableDataColumn("Image", size, ImageData)) - return table_columns - - # ─── VARIABLES ────────────────────────────────────────────────────────────────── # OMERO server info @@ -723,11 +409,10 @@ def create_table_columns(headings): rm.reset() try: - user_client = Client() - user_client.connect(HOST, PORT, USERNAME, PASSWORD) + user_client = omerotools.connect(HOST, PORT, USERNAME, PASSWORD) - if OMERO_link: - image_ids_array = parse_url(OMERO_link) + image_wrappers = omerotools.parse_url(user_client, OMERO_link) + image_wrappers.sort() image_ids_array.sort() else: image_ids_array = [] @@ -767,6 +452,9 @@ def create_table_columns(headings): else: imp = image_ids_array[0] + acq_metadata_dict = omerotools.get_acquisition_metadata( + user_client, image_wpr + ) # Set calibration in nm @@ -822,7 +510,9 @@ def create_table_columns(headings): omero_avg_columns["Image Name"] = String for region_index, region_roi in enumerate(rm.getRoisAsArray()): - progress_bar(region_index + 1, rm.getCount(), 3, "Processing ROI : ") + misc.progressbar( + region_index + 1, rm.getCount(), 3, "Processing ROI : " + ) concat_array = [] if region_index == 0: @@ -830,8 +520,11 @@ def create_table_columns(headings): channel_order.insert(0, channel_order.pop(ref_chnl - 1)) for channel_index, channel in enumerate(channel_order): - progress_bar( - channel_index + 1, imp.getNChannels(), 4, "Processing channel : " + misc.progressbar( + channel_index + 1, + imp.getNChannels(), + 4, + "Processing channel : ", ) # if region_index == 0: @@ -1472,46 +1165,49 @@ def create_table_columns(headings): ) IJ.log("\\Update5:Exporting image to temp folder...") - BFExport(concat_imp, out_path) + bf.export(concat_imp, out_path) if OMERO_link: IJ.log("\\Update5:Uploading image to OMERO...") - upload_image_to_omero(user_client, out_path, dataset_id) + _ = omerotools.upload_image_to_omero( + user_client, out_path, dataset_id + ) os.remove(out_path) if not omero_roi: IJ.log("\\Update5:Uploading ROI to OMERO...") - roivec = save_rois_to_omero(user_client, image_wpr, rm) + roivec = omerotools.save_rois_to_omero( + user_client, image_wpr, rm + ) concat_imp.close() else: IJ.log("\\Update5:Image is saved : " + out_path) - for i in range(imp.getNChannels()): if rm.getCount() > 1: kv_dict.add( NamedValue( "AVERAGE_FWHM_X_All_ROIS_C" + str(i + 1), - str(mean_from_list(avg_FWHM_X[i])), + str(misc.calculate_mean_and_stdv(avg_FWHM_X[i])[0]), ) ) kv_dict.add( NamedValue( "AVERAGE_FWHM_Y_All_ROIS_C" + str(i + 1), - str(mean_from_list(avg_FWHM_Y[i])), + str(misc.calculate_mean_and_stdv(avg_FWHM_Y[i])[0]), ) ) kv_dict.add( NamedValue( "AVERAGE_FWHM_Z_All_ROIS_C" + str(i + 1), - str(mean_from_list(avg_FWHM_Z[i])), + str(misc.calculate_mean_and_stdv(avg_FWHM_Z[i])[0]), ) ) average_values.extend( [ - Double(mean_from_list(avg_FWHM_X[i])), - Double(mean_from_list(avg_FWHM_Y[i])), - Double(mean_from_list(avg_FWHM_Z[i])), + Double(misc.calculate_mean_and_stdv(avg_FWHM_X[i])[0]), + Double(misc.calculate_mean_and_stdv(avg_FWHM_Y[i])[0]), + Double(misc.calculate_mean_and_stdv(avg_FWHM_Z[i])[0]), ] ) @@ -1544,25 +1240,27 @@ def create_table_columns(headings): kv_dict.add(NamedValue("OBJECTIVE_NA", str(obj_na))) kv_dict.add(NamedValue("ACQUISITION_DATE_NUMBER", str(acq_date_number))) if delete_previous_kv: - delete_annotation(user_client, image_wpr) - delete_annotation(user_client, dataset_wpr) - add_annotation(user_client, image_wpr, kv_dict, "PSF Inspector") + omerotools.delete_annotation(user_client, image_wpr) + omerotools.delete_annotation(user_client, dataset_wpr) + omerotools.add_keyvalue_annotation( + user_client, image_wpr, kv_dict, "PSF Inspector" + ) imp.close() omero_avg_table.append(average_values) # omero_columns = create_table_columns(omero_columns) - omero_avg_columns = create_table_columns(omero_avg_columns) # upload_array_as_omero_table(ctx, gateway, map(list, zip(*omero_table)), omero_columns, image_id) - upload_array_as_omero_table( - user_client, map(list, zip(*omero_avg_table)), omero_avg_columns, image_wpr + omerotools.upload_array_as_omero_table( + user_client, + "PSF Inspector results", + map(list, zip(*omero_avg_table)), + omero_avg_columns, + image_wpr, ) - finally: user_client.disconnect() - IJ.log("Script finished.") - From 83fe501fdc777c00010f7024f512392d0083c7a9 Mon Sep 17 00:00:00 2001 From: Laurent Guerard Date: Wed, 16 Apr 2025 14:29:35 +0200 Subject: [PATCH 02/15] Adapt code to directly use `ImageWrapper` instead of ID This unfortunately means dropping open image support for now --- .../IMCF_Utilities/Analyze/PSF Inspector.py | 36 +++++++------------ 1 file changed, 12 insertions(+), 24 deletions(-) diff --git a/src/main/resources/scripts/Plugins/IMCF_Utilities/Analyze/PSF Inspector.py b/src/main/resources/scripts/Plugins/IMCF_Utilities/Analyze/PSF Inspector.py index 9f20797..790148e 100644 --- a/src/main/resources/scripts/Plugins/IMCF_Utilities/Analyze/PSF Inspector.py +++ b/src/main/resources/scripts/Plugins/IMCF_Utilities/Analyze/PSF Inspector.py @@ -413,49 +413,37 @@ def rescale_image(imp, width, height): image_wrappers = omerotools.parse_url(user_client, OMERO_link) image_wrappers.sort() - image_ids_array.sort() - else: - image_ids_array = [] - image_ids_array.append(wm.getCurrentImage()) + # else: + # image_ids_array = [] + # image_ids_array.append(wm.getCurrentImage()) omero_avg_table = [] omero_avg_columns = OrderedDict() # imps = BFImport(file_to_open) - for image_index, image_id in enumerate(image_ids_array): + for image_index, image_wpr in enumerate(image_wrappers): kv_dict = ArrayList() kv_dict.clear() average_values = [] - progress_bar(image_index + 1, len(image_ids_array), 2, "Processing : ") + misc.progressbar(image_index + 1, len(image_wrappers), 2, "Processing : ") rm = RoiManager.getInstance() rm.reset() - if OMERO_link: - image_wpr = user_client.getImage(Long(image_id)) - dataset_wpr = image_wpr.getDatasets(user_client)[0] - dataset_id = dataset_wpr.getId() - dataset_name = dataset_wpr.getName() - project_name = dataset_wpr.getProjects(user_client)[0].getName() - - ( - obj_mag, - obj_na, - acq_date, - acq_date_number, - ) = get_acquisition_metadata_from_imageid(user_client, image_wpr) - - IJ.log("\\Update5:Fetching image from OMERO...") - imp = image_wpr.toImagePlus(user_client) - else: - imp = image_ids_array[0] + # image_wpr = image_wrapper.toImagePlus() + dataset_wpr = image_wpr.getDatasets(user_client)[0] + dataset_id = dataset_wpr.getId() + dataset_name = dataset_wpr.getName() + project_name = dataset_wpr.getProjects(user_client)[0].getName() acq_metadata_dict = omerotools.get_acquisition_metadata( user_client, image_wpr ) + IJ.log("\\Update5:Fetching image from OMERO...") + imp = image_wpr.toImagePlus(user_client) # Set calibration in nm average_values.extend([imp.getTitle()]) From 9b0abcdfa69294d4248d5fd29380f2d8974f4a68 Mon Sep 17 00:00:00 2001 From: Laurent Guerard Date: Wed, 16 Apr 2025 14:29:59 +0200 Subject: [PATCH 03/15] Adapt code to new output from the method to get acquisition metadata --- .../IMCF_Utilities/Analyze/PSF Inspector.py | 32 ++++++++++++++----- 1 file changed, 24 insertions(+), 8 deletions(-) diff --git a/src/main/resources/scripts/Plugins/IMCF_Utilities/Analyze/PSF Inspector.py b/src/main/resources/scripts/Plugins/IMCF_Utilities/Analyze/PSF Inspector.py index 790148e..6a8a160 100644 --- a/src/main/resources/scripts/Plugins/IMCF_Utilities/Analyze/PSF Inspector.py +++ b/src/main/resources/scripts/Plugins/IMCF_Utilities/Analyze/PSF Inspector.py @@ -1205,6 +1205,7 @@ def rescale_image(imp, width, height): omero_avg_columns["Acquisition Date"] = String + # Integer not supported, so have to use Long omero_avg_columns["Acquisition Date Number"] = Long omero_avg_columns["Microscope"] = String omero_avg_columns["Objective Magnification"] = String @@ -1213,20 +1214,35 @@ def rescale_image(imp, width, height): average_values.extend( [ - acq_date, - acq_date_number, + acq_metadata_dict["acquisition_date"], + # Integer not supported, so have to use Long + Long(acq_metadata_dict["acquisition_date_number"]), project_name, - str(int(obj_mag)) + "x", - str(obj_na), + str(int(acq_metadata_dict["objective_magnification"])) + "x", + str(acq_metadata_dict["objective_na"]), image_wpr.asImageData(), ] ) - kv_dict.add(NamedValue("ACQUISITION_DATE", acq_date)) + kv_dict.add( + NamedValue("ACQUISITION_DATE", acq_metadata_dict["acquisition_date"]) + ) kv_dict.add(NamedValue("MICROSCOPE", project_name)) - kv_dict.add(NamedValue("OBJECTIVE_MAGNIFICATION", str(int(obj_mag)) + "x")) - kv_dict.add(NamedValue("OBJECTIVE_NA", str(obj_na))) - kv_dict.add(NamedValue("ACQUISITION_DATE_NUMBER", str(acq_date_number))) + kv_dict.add( + NamedValue( + "OBJECTIVE_MAGNIFICATION", + str(int(acq_metadata_dict["objective_magnification"])) + "x", + ) + ) + kv_dict.add( + NamedValue("OBJECTIVE_NA", str(acq_metadata_dict["objective_na"])) + ) + kv_dict.add( + NamedValue( + "ACQUISITION_DATE_NUMBER", + str(acq_metadata_dict["acquisition_date_number"]), + ) + ) if delete_previous_kv: omerotools.delete_annotation(user_client, image_wpr) omerotools.delete_annotation(user_client, dataset_wpr) From a932f312515db58bc5f4c95fe4c58bbf47716439 Mon Sep 17 00:00:00 2001 From: Laurent Guerard Date: Wed, 16 Apr 2025 14:30:17 +0200 Subject: [PATCH 04/15] Ruff formatting --- .../IMCF_Utilities/Analyze/PSF Inspector.py | 66 ++++++++++++++----- 1 file changed, 50 insertions(+), 16 deletions(-) diff --git a/src/main/resources/scripts/Plugins/IMCF_Utilities/Analyze/PSF Inspector.py b/src/main/resources/scripts/Plugins/IMCF_Utilities/Analyze/PSF Inspector.py index 6a8a160..fa1a663 100644 --- a/src/main/resources/scripts/Plugins/IMCF_Utilities/Analyze/PSF Inspector.py +++ b/src/main/resources/scripts/Plugins/IMCF_Utilities/Analyze/PSF Inspector.py @@ -12,10 +12,10 @@ import os import re import sys -from datetime import date - from collections import OrderedDict +from datetime import date +from fr.igred.omero.roi import ROIWrapper from ij import IJ from ij import WindowManager as wm from ij.gui import Line, Overlay, Plot, Roi, TextRoi, WaitForUserDialog @@ -400,6 +400,7 @@ def rescale_image(imp, width, height): half_final_size = final_size / 2 # ─── CODE ─────────────────────────────────────────────────────────────────────── + if __name__ == "__main__": IJ.log("\\Clear") IJ.log("Script started") @@ -558,7 +559,9 @@ def rescale_image(imp, width, height): bg_ROI = Roi(ROI_size_bg, ROI_size_bg, ROI_size_bg, ROI_size_bg) bg_subtraction( - imp_centered_ROI_current_channel, stack_stats["best_slice"], bg_ROI + imp_centered_ROI_current_channel, + stack_stats["best_slice"], + bg_ROI, ) imp_centered_ROI_current_channel.show() @@ -624,7 +627,9 @@ def rescale_image(imp, width, height): ) bg_subtraction( - imp_centered_ROI_current_channel_proj, roi=bg_ROI, stat_to_use="min" + imp_centered_ROI_current_channel_proj, + roi=bg_ROI, + stat_to_use="min", ) imp_centered_ROI_current_channel_proj.setTitle("Project") @@ -639,10 +644,20 @@ def rescale_image(imp, width, height): ) imp_y_proj = change_canvas_size( - imp_y_proj, project_width, project_width, "Top-Right", True, True + imp_y_proj, + project_width, + project_width, + "Top-Right", + True, + True, ) imp_x_proj = change_canvas_size( - imp_x_proj, project_width, project_width, "Bottom-Left", True, True + imp_x_proj, + project_width, + project_width, + "Bottom-Left", + True, + True, ) # scale_value = half_final_size / ROI_size @@ -716,7 +731,9 @@ def rescale_image(imp, width, height): y_plot_ax_real = [] for i in range(amplitude): x_plot_ax_real.append((i - amplitude / 2) * z_voxel) - y_plot_ax_real.append(z_profile_y[best_slice - amplitude / 2 + i]) + y_plot_ax_real.append( + z_profile_y[best_slice - amplitude / 2 + i] + ) if y_plot_ax_real[i] >= max_graph: max_graph = y_plot_ax_real[i] @@ -750,7 +767,8 @@ def rescale_image(imp, width, height): * fit_results[3] * fit_results[3] * math.log( - (HM - fit_results[0]) / (fit_results[1] - fit_results[0]) + (HM - fit_results[0]) + / (fit_results[1] - fit_results[0]) ) ) except (ValueError, ZeroDivisionError): @@ -768,7 +786,9 @@ def rescale_image(imp, width, height): imp_montage_2.getHeight(), 2, ) - stack_imp = ImagesToStack().run([imp_montage_2, temp_imp, temp_imp]) + stack_imp = ImagesToStack().run( + [imp_montage_2, temp_imp, temp_imp] + ) concat_array.append(stack_imp) avg_FWHM_X[channel - 1].append(None) @@ -865,7 +885,11 @@ def rescale_image(imp, width, height): -(x - fit_results_lateral_1[2]) * (x - fit_results_lateral_1[2]) ) - / (2 * fit_results_lateral_1[3] * fit_results_lateral_1[3]) + / ( + 2 + * fit_results_lateral_1[3] + * fit_results_lateral_1[3] + ) ) ) yy_plot_lat_fit.append( @@ -876,7 +900,11 @@ def rescale_image(imp, width, height): -(x - fit_results_lateral_2[2]) * (x - fit_results_lateral_2[2]) ) - / (2 * fit_results_lateral_2[3] * fit_results_lateral_2[3]) + / ( + 2 + * fit_results_lateral_2[3] + * fit_results_lateral_2[3] + ) ) ) @@ -995,7 +1023,9 @@ def rescale_image(imp, width, height): fwhml_avg_text = TextRoi( text_position_start + 20, text_position_start + 100, - "FWHM lateral average = " + str(int((FWHMl + FWHMly) / 2)) + "nm", + "FWHM lateral average = " + + str(int((FWHMl + FWHMly) / 2)) + + "nm", ) fwhma_text = TextRoi( text_position_start + 20, @@ -1028,7 +1058,10 @@ def rescale_image(imp, width, height): shift_z_text = TextRoi( text_position_start + 20, text_position_start + 160, - "Z Shift : " + str(z_shift) + "plane(s) from C" + str(ref_chnl), + "Z Shift : " + + str(z_shift) + + "plane(s) from C" + + str(ref_chnl), ) set_roi_color_and_position( shift_z_text, Color.red, position_frame=channel_index + 1 @@ -1127,12 +1160,14 @@ def rescale_image(imp, width, height): ) kv_dict.add( NamedValue( - "C" + str(channel) + "_FWHM_Z_ROI_" + str(region_roi.getName()), + "C" + + str(channel) + + "_FWHM_Z_ROI_" + + str(region_roi.getName()), str(int(FWHMa)), ) ) - concat_imp = Concatenator.run(concat_array) concat_imp.setTitle(imp.getTitle() + "_maintenance") @@ -1203,7 +1238,6 @@ def rescale_image(imp, width, height): omero_avg_columns["C" + str(i) + " FWHM Axial Y"] = Double omero_avg_columns["C" + str(i) + " FWHM Z"] = Double - omero_avg_columns["Acquisition Date"] = String # Integer not supported, so have to use Long omero_avg_columns["Acquisition Date Number"] = Long From b14577dc18f604307258b94a0e98e947fd1ce50c Mon Sep 17 00:00:00 2001 From: Laurent Guerard Date: Wed, 16 Apr 2025 14:43:03 +0200 Subject: [PATCH 05/15] Use `imcflibs` methods instead --- .../Convert/Batch_Convert_X_To_Y.py | 313 +++--------------- 1 file changed, 42 insertions(+), 271 deletions(-) diff --git a/src/main/resources/scripts/Plugins/IMCF_Utilities/Convert/Batch_Convert_X_To_Y.py b/src/main/resources/scripts/Plugins/IMCF_Utilities/Convert/Batch_Convert_X_To_Y.py index 91c37f2..963ff6a 100644 --- a/src/main/resources/scripts/Plugins/IMCF_Utilities/Convert/Batch_Convert_X_To_Y.py +++ b/src/main/resources/scripts/Plugins/IMCF_Utilities/Convert/Batch_Convert_X_To_Y.py @@ -20,299 +20,70 @@ from loci.formats import ImageReader from loci.formats import MetadataTools -# ─── FUNCTIONS ────────────────────────────────────────────────────────────────── - -def getFileList(directory, filteringString): - """Get a list of files with the extension - - Parameters - ---------- - directory : str - Path of the files to look at - filteringString : str - Extension to look for - - Returns - ------- - list - List of files with the extension in the folder - """ - - files = [] - for (dirpath, dirnames, filenames) in os.walk(directory): - # if out_dir in dirnames: # Ignore destination directory - # dirnames.remove(OUT_SUBDIR) - for f in fnmatch.filter(filenames,'*' + filteringString): - files.append(os.path.join(dirpath, f)) - return (files) - -def BFImport(indivFile): - """Import using BioFormats - - Parameters - ---------- - indivFile : str - Path of the file to open - - Returns - ------- - imps : ImagePlus - Image opened via BF - """ - options = ImporterOptions() - options.setId(str(indivFile)) - options.setColorMode(ImporterOptions.COLOR_MODE_GRAYSCALE) - return BF.openImagePlus(options) - -def BFExport(imp, savepath): - """Export using BioFormats - - Parameters - ---------- - imp : ImagePlus - ImagePlus of the file to save - savepath : str - Path where to save the image - - """ - paramstring = "outfile=[" + savepath + "] windowless=true compression=Uncompressed saveROI=false" +from imcflibs import pathtools +from imcflibs.imagej import bioformats as bf, misc +# ─── FUNCTIONS ────────────────────────────────────────────────────────────────── - print('Savepath: ', savepath) - plugin = LociExporter() - plugin.arg = paramstring - exporter = Exporter(plugin, imp) - exporter.run() - -def progress_bar(progress, total, line_number, prefix=''): - """Progress bar for the IJ log window - Parameters - ---------- - progress : int - Current step of the loop - total : int - Total number of steps for the loop - line_number : int - Number of the line to be updated - prefix : str, optional - Text to use before the progress bar, by default '' - """ - size = 30 - x = int(size*progress/total) - IJ.log("\\Update%i:%s\t[%s%s] %i/%i\r" % (line_number, prefix, "#"*x, "."*(size-x), progress, total)) +# ─── MAIN CODE ────────────────────────────────────────────────────────────────── -def get_series_info_from_ome_metadata(path_to_file): - """Get the number of series from a file +if __name__ == "__main__": - Parameters - ---------- - path_to_file : str - Path to the file + IJ.log("\\Clear") + IJ.log("Script starting") - Returns - ------- - int - Number of series for the file - """ - reader = ImageReader() - reader.setFlattenedResolutions(False) - omeMeta = MetadataTools.createOMEXMLMetadata() - reader.setMetadataStore(omeMeta) - reader.setId(path_to_file) - series_count = reader.getSeriesCount() + # Retrieve list of files + src_info = pathtools.parse_path(src_dir) - series_index = [] - for i in range(series_count): - if i == 0: - resolution_count = 0 - series_index.append(resolution_count) - else: - reader.setSeries(i-1) - resolution_count += reader.getResolutionCount() - series_index.append(resolution_count) + if out_file_extension == "BMP": + split_channels = False - reader.close() + if out_dir is None: + out_dir = pathtools.join2(src_info["full"], "out") + if not os.path.exists(out_dir): + os.makedirs(out_dir) - return series_count, series_index + files = pathtools.listdir_matching(src_info["full"], filename_filter, fullpath=True, sort=True) + pad_number = 0 -def open_single_series_with_BF(path_to_file, series_number): - """Open a single serie for a file using Bio-Formats + # # If the list of files is not empty + if files: - Parameters - ---------- - path_to_file : str - Path to the file - series_number : int - Number of the serie to open + # For each file finishing with the filtered string + for file_id, file in enumerate(sorted(files)): - Returns - ------- - ImagePlus - ImagePlus of the serie - """ - options = ImporterOptions() - options.setColorMode(ImporterOptions.COLOR_MODE_COMPOSITE) - options.setSeriesOn(series_number, True) # python starts at 0 - # options.setSpecifyRanges(True) - # options.setCBegin(series_number-1, channel_number-1) # python starts at 0 - # options.setCEnd(series_number-1, channel_number-1) - # options.setCStep(series_number-1, 1) - options.setId(path_to_file) - imps = BF.openImagePlus(options) # is an array of imp with one entry + file_info = pathtools.parse_path(file) - return imps[0] + # Import the file with BioFormats + misc.progressbar(file_id + 1, len(files), 1, "Processing: " + str(file_id)) + # IJ.log("\\Update3:Currently opening " + basename + "...") -def save_as(imageplus, extension, out_dir, series, pad_number, split_channels): - """Function to save an image + series_count, series_index = bf.get_series_info_from_ome_metadata(file) + if not pad_number: + pad_number = len(str(series_count)) - Parameters - ---------- - imageplus : ImagePlus - ImagePlus to save - extension : str - Extension to use for the output - out_dir : str - Path for the output - series : int - Series to open - pad_number : int - Number of 0 to use for padding - split_channels : bool - Bool to split or not the channels - """ + for series in range(series_count): + misc.progressbar(series + 1, series_count, 2, "Opening series : ") - out_ext = {} - out_ext["ImageJ-TIF"] = ".tif" - out_ext["ICS-1"] = ".ids" - out_ext["ICS-2"] = ".ics" - out_ext["OME-TIFF"] = ".ome.tif" - out_ext["CellH5"] = ".ch5" - out_ext["BMP"] = ".bmp" + imp = bf.import_image(file, series_number = series_index[series])[0] - imp_to_use = [] - dir_to_save = [] + if "macro image" in imp.getTitle(): + print("Skipping macro image...") + imp.close() + continue - if split_channels: - for channel in range(1, imageplus.getNChannels() + 1): - imp_to_use.append( - Duplicator().run( - imageplus, - channel, channel, - 1, imageplus.getNSlices(), - 1, imageplus.getNFrames() - ) - ) - dir_to_save.append( - os.path.join( + misc.save_image_in_format( + imp, + out_file_extension, out_dir, - "C" + str(channel) + series, + pad_number, + split_channels ) - ) - else: - imp_to_use.append(imageplus) - dir_to_save.append(out_dir) - - for index, current_imp in enumerate(imp_to_use): - - basename = imageplus.getShortTitle() - - out_path = os.path.join( - dir_to_save[index], - basename + "_series_" - + str(series).zfill(pad_number) - ) - - if extension == "ImageJ-TIF": - - check_folder(dir_to_save[index]) - IJ.saveAs( - current_imp, - "Tiff", - out_path + ".tif" - ) - - elif extension == "BMP": - out_folder = os.path.join(out_dir, basename + os.path.sep) - check_folder(out_folder) - StackWriter.save(current_imp, out_folder, "format=bmp") - - else: - BFExport( - current_imp, - out_path + out_ext[extension] - ) - current_imp.close() - -def check_folder(path): - - if not os.path.exists(path): - os.makedirs(path) - -# ─── MAIN CODE ────────────────────────────────────────────────────────────────── - -IJ.log("\\Clear") -IJ.log("Script starting") - -# Retrieve list of files -src_dir = str(src_dir) - -if out_file_extension == "BMP": - split_channels = False - -temp_out_dir = os.path.join(src_dir, "out") -if out_dir is None: - out_dir = temp_out_dir - if not os.path.exists(temp_out_dir): - os.makedirs(out_dir) - - -out_dir = str(out_dir) -files = getFileList(src_dir, filename_filter) - -pad_number = 0 - -# # If the list of files is not empty -if files: - - # For each file finishing with the filtered string - for file_id, file in enumerate(sorted(files)): - - # Get info for the files - folder = os.path.dirname(file) - basename = os.path.basename(file) - basename = os.path.splitext(basename)[0] - - # Import the file with BioFormats - progress_bar(file_id + 1, len(files), 1, "Processing: " + str(file_id)) - # IJ.log("\\Update3:Currently opening " + basename + "...") - - series_count, series_index = get_series_info_from_ome_metadata(file) - if not pad_number: - pad_number = len(str(series_count)) - - for series in range(series_count): - progress_bar(series + 1, series_count, 2, "Opening series : ") - - imp = open_single_series_with_BF(file, series_index[series]) - - if "macro image" in imp.getTitle(): - print("Skipping macro image...") imp.close() - continue - - save_as( - imp, - out_file_extension, - out_dir, - series, - pad_number, - split_channels - ) - - imp.close() - IJ.log("\\Update3:Script finished !") \ No newline at end of file + IJ.log("\\Update3:Script finished !") \ No newline at end of file From 4be9197b9643e1dabdfd56423b09670bd3faad01 Mon Sep 17 00:00:00 2001 From: Laurent Guerard Date: Wed, 16 Apr 2025 14:51:48 +0200 Subject: [PATCH 06/15] Use `imcflibs` methods instead --- .../Convert/make_plate_xml_from_filenames.py | 193 ++---------------- 1 file changed, 14 insertions(+), 179 deletions(-) diff --git a/src/main/resources/scripts/Plugins/IMCF_Utilities/Convert/make_plate_xml_from_filenames.py b/src/main/resources/scripts/Plugins/IMCF_Utilities/Convert/make_plate_xml_from_filenames.py index bcf1c41..156ca60 100644 --- a/src/main/resources/scripts/Plugins/IMCF_Utilities/Convert/make_plate_xml_from_filenames.py +++ b/src/main/resources/scripts/Plugins/IMCF_Utilities/Convert/make_plate_xml_from_filenames.py @@ -16,9 +16,8 @@ from ij import IJ -from loci.formats import ImageReader, MetadataTools - -from imcflibs import pathtools +from imcflibs import pathtools, strtools +from imcflibs.imagej import misc from java.lang import Integer from java.io import File @@ -29,27 +28,6 @@ # ─── FUNCTIONS ────────────────────────────────────────────────────────────────── -def list_all_filenames(source, filetype): - """Get a sorted list of all files of specified filetype in a given directory - - Parameters - ---------- - source : str - Path to source dir - filetype : str - File extension to specify filetype - - Returns - ------- - List - List of all files of the given type in the source dir - """ - - # os.chdir(str(source)) - return sorted_alphanumeric( - glob.glob(os.path.join(source, "*" + filetype)) - ) # sorted by name - def get_ome_reader_from_image(path_to_image): """get the whole OME_metadata from a given image using Bio-Formats @@ -73,155 +51,6 @@ def get_ome_reader_from_image(path_to_image): return reader -def get_metadata_from_image(path_to_image): - """get image info from a given image using Bio-Formats - - Parameters - ---------- - path_to_image : str - full path to the input image - - Returns - ------- - TODO - """ - - reader = ImageReader() - ome_meta = MetadataTools.createOMEXMLMetadata() - reader.setMetadataStore(ome_meta) - reader.setId(str(path_to_image)) - - phys_size_x = ome_meta.getPixelsPhysicalSizeX(0) - phys_size_y = ome_meta.getPixelsPhysicalSizeY(0) - # phys_size_z = ome_meta.getPixelsPhysicalSizeZ(0) - pixel_size_x = ome_meta.getPixelsSizeX(0) - pixel_size_y = ome_meta.getPixelsSizeY(0) - pixel_size_z = ome_meta.getPixelsSizeZ(0) - channel_count = ome_meta.getPixelsSizeC(0) - timepoints_count = ome_meta.getPixelsSizeT(0) - dimension_order = ome_meta.getPixelsDimensionOrder(0) - pixel_type = ome_meta.getPixelsType(0) - - image_calibration = { - "unit_width": phys_size_x.value(), - "unit_height": phys_size_y.value(), - # "unit_depth": phys_size_z.value(), - "pixel_width": pixel_size_x.getNumberValue(), - "pixel_height": pixel_size_y.getNumberValue(), - "slice_count": pixel_size_z.getNumberValue(), - "channel_count": channel_count.getNumberValue(), - "timepoints_count": timepoints_count.getNumberValue(), - "dimension_order": dimension_order, - "pixel_type": pixel_type, - } - - reader.close() - - return image_calibration - - -def sorted_alphanumeric(data): - """Sort a list alphanumerically - - Parameters - ---------- - data : list - List containing all the files to sort - - Returns - ------- - list - List with filenames sorted - """ - - def convert(text): - return int(text) if text.isdigit() else text.lower() - - def alphanum_key(key): - return [convert(c) for c in re.split("([0-9]+)", key)] - - return sorted(data, key=alphanum_key) - - -def pad_number(index, pad_length=2): - return str(index).zfill(pad_length) - - -# def get_files_information(files, ome): -# well_dict = {} -# image_dict = {} - -# for file_index, file in enumerate(files): -# padded_index = pad_number(file_index, len(str(len(files)))) - -# file_name = pathtools.parse_path(file)["basename"] -# well_name = file_name[4 : file_name.find("_")] - -# image_info = get_metadata_from_image(file) -# ome_meta = get_ome_metadata_from_image(file) - -# image = Image() -# image.setID("Image:%s" % padded_index) - -# pixels = Pixels() -# pixels.setID("Pixels:%s" % padded_index) -# pixels.setSizeX(ome_meta.getPixelsSizeX(0).getNumberValue()) -# pixels.setSizeY(ome_meta.getPixelsSizeY(0).getNumberValue()) -# pixels.setSizeZ(ome_meta.getPixelsSizeZ(0).getNumberValue()) -# pixels.setSizeC(ome_meta.getPixelsSizeC(0).getNumberValue()) -# pixels.setSizeT(ome_meta.getPixelsSizeT(0).getNumberValue()) -# pixels.setDimensionOrder(ome_meta.getPixelsDimensionOrder(0).getNumberValue()) -# pixels.setType(ome_meta.getPixelsType(0).getNumberValue()) - -# for channel_index, current_channel in enumerate(image_info["channel_count"]): -# channel = Channel() -# channel.setID("Channel:%s:0" % channel_index) -# channel.setAcquisitionMode( -# AcquisitionMode().fromString( -# image_info["channel_" + str(channel_index) + "_acquisition_mode"] -# ) -# ) - -# well = Well() -# well.setID("Well:0:%s" % file_index) -# well.setRow(string.ascii_lowercase.index(well_name[0].lower())) -# well.setColumn(well_name[1:]) - -# well_sample = WellSample() -# well_sample.setID("WellSample:0:%s:0" % padded_index) -# well_sample.setIndex(pad_number(file_index, len(str(len(files) + 1)))) -# well_dict["ImageRef"].append("Image:%s" % padded_index) - -# image_dict["ID"].append("Image:%s" % padded_index) - -# image_dict["PixelsID"].append("Pixels:%s" % padded_index) -# image_dict[""] - - -# return well_dict, image_dict - - -def progressbar(progress, total, line_number, prefix=""): - """Progress bar for the IJ log window - - Parameters - ---------- - progress : int - Current step of the loop - total : int - Total number of steps for the loop - line_number : int - Number of the line to be updated - prefix : str, optional - Text to use before the progress bar, by default '' - """ - - size = 30 - x = int(size * progress / total) - IJ.log( - "\\Update%i:%s[%s%s] %i/%i\r" - % (line_number, prefix, "#" * x, "." * (size - x), progress, total) - ) # ─── VARIABLES ────────────────────────────────────────────────────────────────── @@ -235,7 +64,9 @@ def progressbar(progress, total, line_number, prefix=""): # Retrieve list of files path_info = pathtools.parse_path(src_dir) -files = list_all_filenames(path_info["orig"], filename_filter) + files = pathtools.listdir_matching( + path_info["orig"], filename_filter, fullpath=True, sort=True + ) # ome_xml = set_files_information(files, ome_xml) new_ome = OME() @@ -245,8 +76,8 @@ def progressbar(progress, total, line_number, prefix=""): new_plate.setName(plate_name) new_plate.setID("Plate:0") -if filename_filter.startswith("tif"): - out_xml = pathtools.join2(path_info["orig"], plate_name + ".companion.ome") + if filename_filter.startswith("tif"): + out_xml = pathtools.join2(path_info["orig"], plate_name + ".companion.ome") else: out_xml = pathtools.join2(path_info["orig"], plate_name + ".ome.xml") @@ -286,21 +117,25 @@ def progressbar(progress, total, line_number, prefix=""): new_plate.addWell(new_well) well_index = well_index + 1 else: - new_well = new_plate.getWell(well_list.index(well_name)) + out_xml = pathtools.join2(path_info["orig"], plate_name + ".ome.xml") for image_index in range(metadata_root.sizeOfImageList()): current_padded_index = pad_number( image_index, len(str(metadata_root.sizeOfImageList())) ) - + misc.progressbar(file_index + 1, len(files), 1, "Working on : ") new_image = metadata_root.getImage(image_index) new_image.setID("Image:%s" % current_padded_index) new_image.setDescription("") if metadata_root.sizeOfImageList() > 1: - image_name = new_image.getName() + padded_index = strtools.pad_number(file_index, len(str(len(files)))) + file_info = pathtools.parse_path(file) else: image_name = file_info["fname"] new_image.setName(image_name) + current_padded_index = strtools.pad_number( + image_index, len(str(metadata_root.sizeOfImageList())) + ) new_pxls = new_image.getPixels() try: From 303b754b320421f43221c3f596f201e431046689 Mon Sep 17 00:00:00 2001 From: Laurent Guerard Date: Wed, 16 Apr 2025 14:52:10 +0200 Subject: [PATCH 07/15] Ruff formatting --- .../Convert/make_plate_xml_from_filenames.py | 19 +++++-------------- 1 file changed, 5 insertions(+), 14 deletions(-) diff --git a/src/main/resources/scripts/Plugins/IMCF_Utilities/Convert/make_plate_xml_from_filenames.py b/src/main/resources/scripts/Plugins/IMCF_Utilities/Convert/make_plate_xml_from_filenames.py index 156ca60..eef53b0 100644 --- a/src/main/resources/scripts/Plugins/IMCF_Utilities/Convert/make_plate_xml_from_filenames.py +++ b/src/main/resources/scripts/Plugins/IMCF_Utilities/Convert/make_plate_xml_from_filenames.py @@ -4,31 +4,22 @@ # ─── IMPORTS ──────────────────────────────────────────────────────────────────── -import glob -import os -import re import string -from ome.xml.model import OME, Plate, TiffData, Well, WellSample, UUID -from ome.xml.model.primitives import NonNegativeInteger - -from ome.specification import XMLWriter - - from ij import IJ from imcflibs import pathtools, strtools from imcflibs.imagej import misc - -from java.lang import Integer from java.io import File +from java.lang import Integer from java.util import UUID as juuid - -# Bioformats imports +from loci.formats import ImageReader, MetadataTools +from ome.specification import XMLWriter +from ome.xml.model import OME, UUID, Plate, TiffData, Well, WellSample +from ome.xml.model.primitives import NonNegativeInteger # ─── FUNCTIONS ────────────────────────────────────────────────────────────────── - def get_ome_reader_from_image(path_to_image): """get the whole OME_metadata from a given image using Bio-Formats From ad74b73b4567049e01be318d3608a15e063dfb7e Mon Sep 17 00:00:00 2001 From: Laurent Guerard Date: Wed, 16 Apr 2025 14:53:47 +0200 Subject: [PATCH 08/15] Switch to use python module approach --- .../Convert/make_plate_xml_from_filenames.py | 161 +++++++++--------- 1 file changed, 80 insertions(+), 81 deletions(-) diff --git a/src/main/resources/scripts/Plugins/IMCF_Utilities/Convert/make_plate_xml_from_filenames.py b/src/main/resources/scripts/Plugins/IMCF_Utilities/Convert/make_plate_xml_from_filenames.py index eef53b0..ffddd52 100644 --- a/src/main/resources/scripts/Plugins/IMCF_Utilities/Convert/make_plate_xml_from_filenames.py +++ b/src/main/resources/scripts/Plugins/IMCF_Utilities/Convert/make_plate_xml_from_filenames.py @@ -42,123 +42,122 @@ def get_ome_reader_from_image(path_to_image): return reader - - # ─── VARIABLES ────────────────────────────────────────────────────────────────── # ─── MAIN CODE ────────────────────────────────────────────────────────────────── -IJ.log("\\Clear") -IJ.log("Script starting...") +if __name__ == "__main__": + IJ.log("\\Clear") + IJ.log("Script starting...") -ome_xml = OME() + ome_xml = OME() -# Retrieve list of files -path_info = pathtools.parse_path(src_dir) + # Retrieve list of files + path_info = pathtools.parse_path(src_dir) files = pathtools.listdir_matching( path_info["orig"], filename_filter, fullpath=True, sort=True ) -# ome_xml = set_files_information(files, ome_xml) -new_ome = OME() + # ome_xml = set_files_information(files, ome_xml) + new_ome = OME() -# Set info about plate -new_plate = Plate() -new_plate.setName(plate_name) -new_plate.setID("Plate:0") + # Set info about plate + new_plate = Plate() + new_plate.setName(plate_name) + new_plate.setID("Plate:0") if filename_filter.startswith("tif"): out_xml = pathtools.join2(path_info["orig"], plate_name + ".companion.ome") -else: - out_xml = pathtools.join2(path_info["orig"], plate_name + ".ome.xml") - -well_list = [] -well_index = 0 - -for file_index, file in enumerate(files): - progressbar(file_index + 1, len(files), 1, "Working on : ") - - current_reader = get_ome_reader_from_image(file) - metadata_root = current_reader.getMetadataStoreRoot() - - padded_index = pad_number(file_index, len(str(len(files)))) - - file_info = pathtools.parse_path(file) - file_name = file_info["basename"] - well_name = file_name[4 : file_name.find("_")] - - if file_index == 0: - for experimenter_index in range(metadata_root.sizeOfExperimenterList()): - current_experimenter = metadata_root.getExperimenter(experimenter_index) - new_ome.addExperimenter(current_experimenter) - for instrument_index in range(metadata_root.sizeOfInstrumentList()): - current_instrument = metadata_root.getInstrument(instrument_index) - new_ome.addInstrument(current_instrument) - - if well_name not in well_list: - well_list.append(well_name) - new_well = Well() - new_well.setID("Well:0:%s" % well_index) - new_well.setRow( - NonNegativeInteger( - Integer(string.ascii_lowercase.index(well_name[0].lower())) - ) - ) - new_well.setColumn(NonNegativeInteger(Integer(well_name[1:]))) - new_plate.addWell(new_well) - well_index = well_index + 1 else: out_xml = pathtools.join2(path_info["orig"], plate_name + ".ome.xml") - for image_index in range(metadata_root.sizeOfImageList()): - current_padded_index = pad_number( - image_index, len(str(metadata_root.sizeOfImageList())) - ) + well_list = [] + well_index = 0 + + for file_index, file in enumerate(files): misc.progressbar(file_index + 1, len(files), 1, "Working on : ") - new_image = metadata_root.getImage(image_index) - new_image.setID("Image:%s" % current_padded_index) - new_image.setDescription("") - if metadata_root.sizeOfImageList() > 1: + + current_reader = get_ome_reader_from_image(file) + metadata_root = current_reader.getMetadataStoreRoot() + padded_index = strtools.pad_number(file_index, len(str(len(files)))) + file_info = pathtools.parse_path(file) + file_name = file_info["basename"] + well_name = file_name[4 : file_name.find("_")] + + if file_index == 0: + for experimenter_index in range(metadata_root.sizeOfExperimenterList()): + current_experimenter = metadata_root.getExperimenter(experimenter_index) + new_ome.addExperimenter(current_experimenter) + for instrument_index in range(metadata_root.sizeOfInstrumentList()): + current_instrument = metadata_root.getInstrument(instrument_index) + new_ome.addInstrument(current_instrument) + + if well_name not in well_list: + well_list.append(well_name) + new_well = Well() + new_well.setID("Well:0:%s" % well_index) + new_well.setRow( + NonNegativeInteger( + Integer(string.ascii_lowercase.index(well_name[0].lower())) + ) + ) + new_well.setColumn(NonNegativeInteger(Integer(well_name[1:]))) + new_plate.addWell(new_well) + well_index = well_index + 1 else: - image_name = file_info["fname"] - new_image.setName(image_name) + new_well = new_plate.getWell(well_list.index(well_name)) + + for image_index in range(metadata_root.sizeOfImageList()): current_padded_index = strtools.pad_number( image_index, len(str(metadata_root.sizeOfImageList())) ) - new_pxls = new_image.getPixels() - try: - new_tiffdata = new_pxls.getTiffData(0) + new_image = metadata_root.getImage(image_index) + new_image.setID("Image:%s" % current_padded_index) + new_image.setDescription("") + if metadata_root.sizeOfImageList() > 1: + image_name = new_image.getName() + else: + image_name = file_info["fname"] + new_image.setName(image_name) + + new_pxls = new_image.getPixels() + # Try to get existing TiffData or create a new one + new_tiffdata = None + try: + # Attempt to get existing TiffData + new_tiffdata = new_pxls.getTiffData(0) + except Exception as e: + IJ.log("Creating new TiffData (no existing data found): %s" % e) + new_tiffdata = TiffData() + + # Set UUID information for the TiffData new_uuid = UUID() new_uuid.setFileName(image_name) uuid_str = juuid.randomUUID().toString() new_uuid.setValue("urn:uuid:%s" % uuid_str) new_tiffdata.setUUID(new_uuid) - except: - new_tiffdata = TiffData() # new_tiffdata.setIFD(NonNegativeInteger(0)) - new_pxls.addTiffData(new_tiffdata) - new_image.setPixels(new_pxls) + new_pxls.addTiffData(new_tiffdata) + new_image.setPixels(new_pxls) - new_well_sample = WellSample() - new_well_sample.setID( - "WellSample:0:%s:%s" % (well_index, new_well.sizeOfWellSampleList()) - ) - - new_well_sample.linkImage(new_image) - new_well.addWellSample(new_well_sample) - - new_ome.addImage(new_image) + new_well_sample = WellSample() + new_well_sample.setID( + "WellSample:0:%s:%s" % (well_index, new_well.sizeOfWellSampleList()) + ) -new_ome.addPlate(new_plate) + new_well_sample.linkImage(new_image) + new_well.addWellSample(new_well_sample) -new_ome_str = new_ome.toString() + new_ome.addImage(new_image) + new_ome.addPlate(new_plate) -XMLWriter().writeFile(File(out_xml), new_ome, False) + new_ome_str = new_ome.toString() + XMLWriter().writeFile(File(out_xml), new_ome, False) -IJ.log("FINISHED") + IJ.log("FINISHED") From b7053ca67e324fb0e63175d754d1b27bee9be412 Mon Sep 17 00:00:00 2001 From: Laurent Guerard Date: Tue, 22 Apr 2025 11:03:39 +0200 Subject: [PATCH 09/15] Refactor to use `imcflibs` instead --- .../Stitch_Files_In_Directories.py | 540 ++++-------------- 1 file changed, 97 insertions(+), 443 deletions(-) diff --git a/src/main/resources/scripts/Plugins/IMCF_Utilities/Stitching_Registration/Stitch_Files_In_Directories.py b/src/main/resources/scripts/Plugins/IMCF_Utilities/Stitching_Registration/Stitch_Files_In_Directories.py index 6e8eeac..6e6ef9c 100644 --- a/src/main/resources/scripts/Plugins/IMCF_Utilities/Stitching_Registration/Stitch_Files_In_Directories.py +++ b/src/main/resources/scripts/Plugins/IMCF_Utilities/Stitching_Registration/Stitch_Files_In_Directories.py @@ -16,11 +16,8 @@ # ─── Imports ────────────────────────────────────────────────────────────────── -import glob import os import shutil -import smtplib -import subprocess import time from io.scif.util import MemoryTools @@ -29,11 +26,9 @@ from ij import WindowManager as wm from ij.plugin import FolderOpener, HyperStackConverter from imcflibs import pathtools +from imcflibs.imagej import bioformats as bf from imcflibs.imagej import misc -# ome imports to parse metadata -from loci.formats import ImageReader, MetadataTools - # requirements: # BigStitcher # faim-imagej-imaris-tools-0.0.1.jar (https://maven.scijava.org/service/local/repositories/releases/content/org/scijava/faim-imagej-imaris-tools/0.0.1/faim-imagej-imaris-tools-0.0.1.jar) @@ -61,216 +56,6 @@ def fix_ij_dirs(path): return fixed_path -def list_dirs_containing_filetype(source, filetype): - """Recur through the source dir and return all dirs - and subdirs that contain the specified filetype - - Parameters - ---------- - source : str - Path to source dir - filetype : str - File extension to specify filetype - - Returns - ------- - list - List of all dirs that contain filetype - """ - - dirs_containing_filetype = [] - - # walk recursively through all directories - # list their paths and all files inside (=os.walk) - for dirname, _, filenames in os.walk(source): - # stop when encountering a directory that contains "filetype" - # and store the directory path - for filename in filenames: - if filetype in filename: - dirs_containing_filetype.append(dirname + "/") - break - - return dirs_containing_filetype - - -def get_ome_metadata(source, imagenames): - """Get the stage coordinates and calibration from the ome-xml for a given list of images - - Parameters - ---------- - source : str - Path to the images - imagenames : list of str - List of images filenames - - Returns - ------- - tuple - Contains - dimensions : int - Number of dimensions (2D or 3D) - stage_coordinates_x : list - The absolute stage x-coordinated from ome-xml metadata - stage_coordinates_y : list - The absolute stage y-coordinated from ome-xml metadata - stage_coordinates_z : list - The absolute stage z-coordinated from ome-xml metadata - relative_coordinates_x : list - The relative stage x-coordinates in px - relative_coordinates_y : list - The relative stage y-coordinates in px - relative_coordinates_z : list - The relative stage z-coordinates in px - image_calibration : list - x,y,z image calibration in unit/px - calibration_unit : str - Image calibration unit - image_dimensions_czt : list - Number of images in dimensions c,z,t - series_names : list of str - Names of all series contained in the files - max_size : list of int - Maximum size across all files in dimensions x,y,z - """ - - # open an array to store the abosolute stage coordinates from metadata - stage_coordinates_x = [] - stage_coordinates_y = [] - stage_coordinates_z = [] - series_names = [] - - for counter, image in enumerate(imagenames): - # parse metadata - reader = ImageReader() - reader.setFlattenedResolutions(False) - omeMeta = MetadataTools.createOMEXMLMetadata() - reader.setMetadataStore(omeMeta) - reader.setId(source + str(image)) - series_count = reader.getSeriesCount() - - # get hyperstack dimensions from the first image - if counter == 0: - frame_size_x = reader.getSizeX() - frame_size_y = reader.getSizeY() - frame_size_z = reader.getSizeZ() - frame_size_c = reader.getSizeC() - frame_size_t = reader.getSizeT() - - # note the dimensions - if frame_size_z == 1: - dimensions = 2 - if frame_size_z > 1: - dimensions = 3 - - # get the physical calibration for the first image series - physSizeX = omeMeta.getPixelsPhysicalSizeX(0) - physSizeY = omeMeta.getPixelsPhysicalSizeY(0) - physSizeZ = omeMeta.getPixelsPhysicalSizeZ(0) - - # workaround to get the z-interval if physSizeZ.value() returns None. - z_interval = 1 - if physSizeZ is not None: - z_interval = physSizeZ.value() - - if frame_size_z > 1 and physSizeZ is None: - print("no z calibration found, trying to recover") - first_plane = omeMeta.getPlanePositionZ(0, 0) - next_plane_imagenumber = frame_size_c + frame_size_t - 1 - second_plane = omeMeta.getPlanePositionZ(0, next_plane_imagenumber) - z_interval = abs(abs(first_plane.value()) - abs(second_plane.value())) - print("z-interval seems to be: " + str(z_interval)) - - # create an image calibration - image_calibration = [physSizeX.value(), physSizeY.value(), z_interval] - calibration_unit = physSizeX.unit().getSymbol() - image_dimensions_czt = [frame_size_c, frame_size_z, frame_size_t] - - reader.close() - - for series in range(series_count): - if omeMeta.getImageName(series) == "macro image": - continue - - if series_count > 1 and not str(image).endswith(".vsi"): - series_names.append(omeMeta.getImageName(series)) - else: - series_names.append(str(image)) - # get the plane position in calibrated units - current_position_x = omeMeta.getPlanePositionX(series, 0) - current_position_y = omeMeta.getPlanePositionY(series, 0) - current_position_z = omeMeta.getPlanePositionZ(series, 0) - - physSizeX_max = ( - physSizeX.value() - if physSizeX.value() >= omeMeta.getPixelsPhysicalSizeX(series).value() - else omeMeta.getPixelsPhysicalSizeX(series).value() - ) - physSizeY_max = ( - physSizeY.value() - if physSizeY.value() >= omeMeta.getPixelsPhysicalSizeY(series).value() - else omeMeta.getPixelsPhysicalSizeY(series).value() - ) - if omeMeta.getPixelsPhysicalSizeZ(series): - physSizeZ_max = ( - physSizeZ.value() - if physSizeZ.value() - >= omeMeta.getPixelsPhysicalSizeZ(series).value() - else omeMeta.getPixelsPhysicalSizeZ(series).value() - ) - - else: - physSizeZ_max = 1.0 - - # get the absolute stage positions and store them - pos_x = current_position_x.value() - pos_y = current_position_y.value() - - if current_position_z is None: - print("the z-position is missing in the ome-xml metadata.") - pos_z = 1.0 - else: - pos_z = current_position_z.value() - - stage_coordinates_x.append(pos_x) - stage_coordinates_y.append(pos_y) - stage_coordinates_z.append(pos_z) - - max_size = [physSizeX_max, physSizeY_max, physSizeZ_max] - - # calculate the store the relative stage movements in px (for the grid/collection stitcher) - relative_coordinates_x_px = [] - relative_coordinates_y_px = [] - relative_coordinates_z_px = [] - - for i in range(len(stage_coordinates_x)): - rel_pos_x = ( - stage_coordinates_x[i] - stage_coordinates_x[0] - ) / physSizeX.value() - rel_pos_y = ( - stage_coordinates_y[i] - stage_coordinates_y[0] - ) / physSizeY.value() - rel_pos_z = (stage_coordinates_z[i] - stage_coordinates_z[0]) / z_interval - - relative_coordinates_x_px.append(rel_pos_x) - relative_coordinates_y_px.append(rel_pos_y) - relative_coordinates_z_px.append(rel_pos_z) - - return ( - dimensions, - stage_coordinates_x, - stage_coordinates_y, - stage_coordinates_z, - relative_coordinates_x_px, - relative_coordinates_y_px, - relative_coordinates_z_px, - image_calibration, - calibration_unit, - image_dimensions_czt, - series_names, - max_size, - ) - - def write_tileconfig( source, dimensions, imagenames, x_coordinates, y_coordinates, z_coordinates ): @@ -292,6 +77,8 @@ def write_tileconfig( The relative stage z-coordinates in px """ + image_filenames = [os.path.basename(i) for i in imagenames] + outCSV = str(source) + "TileConfiguration.txt" row_1 = "# Define the number of dimensions we are working on" @@ -314,7 +101,7 @@ def write_tileconfig( empty_column = list(str(" ") * len(imagenames)) - final_line = [";".join(i) for i in zip(imagenames, empty_column, coordinates_xyz)] + final_line = [";".join(i) for i in zip(image_filenames, empty_column, coordinates_xyz)] with open(outCSV, "wb") as f: f.write(row_1 + "\n") @@ -394,35 +181,6 @@ def calibrate_current_image(xyz_calibration, unit): imp.getCalibration().setUnit(unit) -def save_current_image_as_tiff(filename, filetype, target): - """Save the currently active image as ImageJ-Tiff - - Parameters - ---------- - filename : str - Filename of the image - filetype : str - The original filetype of the image - target : str - Directory where the image will be saved - - Returns - ------- - str - Path to save the data - """ - - imp = wm.getCurrentImage() - savename = filename.replace(filetype, "_stitched.tif") - savepath = target + savename - IJ.log("now saving: " + str(savepath)) - print("now saving " + savepath) - IJ.saveAs(imp, "Tiff", savepath) - imp.close() - - return savepath - - def save_current_image_as_bdv(filename, filetype, target): """Save the currently active image as BigDataViewer hdf5/xml @@ -455,35 +213,6 @@ def save_current_image_as_bdv(filename, filetype, target): return savepath -def save_current_image_as_ics1(filename, filetype, target): - """Save the currently active image as ICS/IDS using scifio - - Parameters - ---------- - filename : str - Filename of the image - filetype : str - The original filetype of the image - target : str - Directory where the image will be saved - - Returns - ------- - str - Path to save the data - """ - - img = ImageDisplayService.getActiveDataset() - savename = filename.replace(filetype, "_stitched.ics") - savepath = target + savename - IJ.log("now saving: " + str(savepath)) - print("now saving " + savepath) - io.save(img, savepath) - IJ.run("Close") - - return savepath - - def save_current_image_with_BF_as_ics1(filename, filetype, target): """Save the currently active image as ICS1 using BF @@ -514,15 +243,30 @@ def save_current_image_with_BF_as_ics1(filename, filetype, target): def open_sequential_gcimages_withBF(source, image_dimensions_czt): - """Use Bio-formats to open all sequential images written by the Grid/collection stitcher - in a folder as virtual stack + """Open sequential grid/collection stitcher images as a virtual stack. + + This function imports a sequence of images created by the Grid/Collection + stitcher plugin that follow the naming convention "img_t{t}_z{z}_c{c}". + It determines the range of indices for each dimension (channel, z-stack, + time) and creates a virtual stack that spans all images in the specified + directory. Parameters ---------- source : str - Directory to the image files - image_dimensions_czt : list - Number of images in dimensions c,z,t + Directory path containing the image files following the Grid/Collection + stitcher naming format + image_dimensions_czt : list of int + Number of images in each dimension [channels, z-planes, timepoints] + Example: [3, 10, 1] for a 3-channel z-stack with a single timepoint + + Notes + ----- + - The function creates a virtual stack, which means images are loaded + on-demand to reduce memory usage + - It uses the Bio-Formats Importer plugin with specific parameters to + properly handle multi-dimensional data + - Image indices start at 1, not 0 """ c_end = str(image_dimensions_czt[0]) @@ -561,15 +305,38 @@ def open_sequential_gcimages_withBF(source, image_dimensions_czt): def open_sequential_gcimages_from_folder(source, image_dimensions_czt): - """Use IJ "import image sequence" to open all sequential images written by the Grid/collection stitcher - in a folder as virtual stack. Bio-Formats seems to have a limit in XY size. + """Open sequential images produced by Grid/Collection stitcher. + + This function uses ImageJ's FolderOpener to open all sequential images from a + directory as a virtual stack. The function is specifically designed for images + generated by the Grid/Collection stitcher. It automatically calculates + Z-dimensions based on the total number of images and the provided C and T + dimensions, then converts the stack to a proper hyperstack that is displayed + to the user. + + Bio-Formats sometimes has limitations with very large XY dimensions, making this + approach necessary for large stitched datasets. Parameters ---------- source : str - Directory to the image files - image_dimensions_czt : list - Number of images in dimensions c,z,t + Directory path containing the sequential image files + image_dimensions_czt : list of int + Number of images in dimensions [c,z,t] where: + - c: number of channels + - z: number of z-slices (may be overridden based on actual files) + - t: number of timepoints + + Returns + ------- + None + The function displays the hyperstack but does not return it + + Notes + ----- + Z-dimension is automatically recalculated based on the total number of images + found and the provided C and T dimensions, as the Grid/Collection stitcher may + add Z-planes during processing. """ c_end = image_dimensions_czt[0] @@ -586,133 +353,6 @@ def open_sequential_gcimages_from_folder(source, image_dimensions_czt): imp2.show() -def get_folder_size(source): - """Determines the size of a given directory and its subdirectories in bytes - - Parameters - ---------- - source : str - Directory which size should be determined - - Returns - ------- - int - Size of the source folder in bytes - """ - - total_size = 0 - for dirpath, _, filenames in os.walk(source): - for f in filenames: - fp = os.path.join(dirpath, f) - # skip if it is symbolic link - if not os.path.islink(fp): - total_size += os.path.getsize(fp) - - return total_size - - -def locate_latest_imaris(paths_to_check=None): - """Find paths to latest installed Imaris or ImarisFileConverter version. - - Parameters - ---------- - paths_to_check: list of str, optional - A list of paths that should be used to look for the installations, by default - `None` which will fall back to the standard installation locations of Bitplane. - - Returns - ------- - str - Full path to the most recent (as in "version number") ImarisFileConverter - or Imaris installation folder with the latter one having priority. - Will be empty if nothing is found. - """ - - if not paths_to_check: - paths_to_check = [ - r"C:\Program Files\Bitplane\ImarisFileConverter ", - r"C:\Program Files\Bitplane\Imaris ", - ] - - imaris_paths = [""] - - for check in paths_to_check: - hits = glob.glob(check + "*") - imaris_paths += sorted( - hits, key=lambda x: float(x.replace(check, "").replace(".", "")) - ) - - return imaris_paths[-1] - - -def convert_to_imaris2(convert_to_ims, path_to_image): - """Convert a given file to Imaris5 .ims using ImarisConvert.exe directly with subprocess - - Parameters - ---------- - convert_to_ims : Boolean - True if the users chose file conversion - path_to_image : str - the full path to the input image - """ - - if convert_to_ims == True: - path_root, file_extension = os.path.splitext(path_to_image) - if file_extension == ".ids": - file_extension = ".ics" - path_to_image = path_root + file_extension - - os.chdir(locate_latest_imaris()) - - command = 'ImarisConvert.exe -i "%s" -of Imaris5 -o "%s"' % ( - path_to_image, - path_to_image.replace(file_extension, ".ims"), - ) - print("\n%s" % command) - IJ.log("Converting to Imaris5 .ims...") - subprocess.call(command, shell=True) - IJ.log("Conversion to .ims is finished") - - -def send_mail(sender, recipient, filename, total_execution_time_min): - """Send an email via smtp.unibas.ch. - Will likely NOT work without connection to the unibas network. - - Parameters - ---------- - sender : string - senders email address - recipient : string - recipients email address - filename : string - the name of the file to be passed in the email - total_execution_time_min : float - the time it took to process the file - """ - - header = "From: imcf@unibas.ch\n" - header += "To: %s\n" - header += "Subject: Your stitching job finished successfully\n\n" - text = ( - "Dear recipient,\n\n" - "This is an automated message from the recursive stitching tool.\n" - "Your folder %s has been successfully processed (%s min).\n\n" - "Kind regards,\n" - "The IMCF-team" - ) - - message = header + text - - try: - smtpObj = smtplib.SMTP("smtp.unibas.ch") - smtpObj.sendmail( - sender, recipient, message % (recipient, filename, total_execution_time_min) - ) - print("Successfully sent email") - except smtplib.SMTPException: - print("Error: unable to send email") - - # ─── Main Code ──────────────────────────────────────────────────────────────── if __name__ == "__main__": @@ -723,10 +363,10 @@ def send_mail(sender, recipient, filename, total_execution_time_min): filetype = "." + filetype # In case script is ran batch - if os.path.isfile(source.getAbsolutePath()): - source = os.path.dirname(source.getAbsolutePath()) - source = fix_ij_dirs(source) - all_source_dirs = list_dirs_containing_filetype(source, filetype) + source_info = pathtools.parse_path(source) + source = source_info["path"] + # source = fix_ij_dirs(source) + all_source_dirs = pathtools.find_dirs_containing_filetype(source, filetype) if only_register: fusion_method = "Do not fuse images (only write TileConfiguration)" @@ -741,52 +381,64 @@ def send_mail(sender, recipient, filename, total_execution_time_min): IJ.log("Now working on " + source_dir) print("bigdata= ", str(bigdata)) free_memory_bytes = MemoryTools().totalAvailableMemory() - folder_size_bytes = get_folder_size(source_dir) + folder_size_bytes = pathtools.folder_size(source_dir) if free_memory_bytes / folder_size_bytes < 3.5: bigdata = True IJ.log("Not enough free RAM, switching to BigData mode (slow)") - allimages = pathtools.listdir_matching(source_dir, filetype, sort=True) + allimages = pathtools.listdir_matching( + source_dir, filetype, fullpath=True, sort=True + ) - ome_metadata = get_ome_metadata(source_dir, allimages) + ome_stage_metadata = bf.get_stage_coords(allimages) # if filetype == "ome.tif": # write_tileconfig(source_dir, ome_metadata[0], allimages, ome_metadata[1], ome_metadata[2], ome_metadata[3]) # else: write_tileconfig( source_dir, - ome_metadata[0], - ome_metadata[10], - ome_metadata[4], - ome_metadata[5], - ome_metadata[6], + ome_stage_metadata.dimensions, + ome_stage_metadata.series_names, + ome_stage_metadata.relative_coordinates_x, + ome_stage_metadata.relative_coordinates_y, + ome_stage_metadata.relative_coordinates_z, ) run_GC_stitcher(source_dir, fusion_method, bigdata, quick, reg_threshold) - if bigdata and not only_register: - path = pathtools.join2(source_dir, "temp") - open_sequential_gcimages_from_folder(path, ome_metadata[9]) - calibrate_current_image(ome_metadata[7], ome_metadata[8]) - path_to_image = save_current_image_as_bdv( - allimages[0], filetype, source_dir - ) - convert_to_imaris2(convert_to_ims, path_to_image) - shutil.rmtree(path, ignore_errors=True) # remove temp folder + calibrate_current_image( + ome_stage_metadata.image_calibration, + ome_stage_metadata.calibration_unit, + ) - if bigdata and bdv and not only_register: - calibrate_current_image(ome_metadata[7], ome_metadata[8]) - path_to_image = save_current_image_as_bdv( - allimages[0], filetype, source_dir - ) - convert_to_imaris2(convert_to_ims, path_to_image) + if bigdata and not only_register: + if not bdv: + path = pathtools.join2(source_dir, "temp") + open_sequential_gcimages_from_folder( + path, ome_stage_metadata.image_dimensions_czt + ) + calibrate_current_image( + ome_stage_metadata.image_calibration, + ome_stage_metadata.calibration_unit, + ) + path_to_image = save_current_image_as_bdv( + allimages[0], filetype, source_dir + ) + misc.convert_to_imaris(convert_to_ims, path_to_image) + shutil.rmtree(path, ignore_errors=True) # remove temp folder + else: + path_to_image = save_current_image_as_bdv( + allimages[0], filetype, source_dir + ) + misc.convert_to_imaris(convert_to_ims, path_to_image) if not bigdata and not bdv and not only_register: - calibrate_current_image(ome_metadata[7], ome_metadata[8]) path_to_image = save_current_image_with_BF_as_ics1( allimages[0], filetype, source_dir ) - convert_to_imaris2(convert_to_ims, path_to_image) + + if convert_to_ims: + misc.run_imarisconvert(path_to_image) # run the garbage collector to clear the memory # Seems to not work in a function and needs to be started several times with waits in between :( @@ -800,8 +452,10 @@ def send_mail(sender, recipient, filename, total_execution_time_min): total_execution_time_min = misc.elapsed_time_since(execution_start_time) - if email_address != "": - send_mail("imcf@unibas.ch", email_address, source, total_execution_time_min) + if email_address: + misc.send_notification_email( + "Stitching script", email_address, source, total_execution_time_min + ) else: print("Email address field is empty, no email was sent") From 61cf6bdf4c960302c8ce8c9936c858dc1f1617d2 Mon Sep 17 00:00:00 2001 From: Laurent Guerard Date: Thu, 24 Apr 2025 09:52:11 +0200 Subject: [PATCH 10/15] Add the missing logging import --- .../scripts/Plugins/IMCF_Utilities/Analyze/PSF Inspector.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/resources/scripts/Plugins/IMCF_Utilities/Analyze/PSF Inspector.py b/src/main/resources/scripts/Plugins/IMCF_Utilities/Analyze/PSF Inspector.py index fa1a663..01fa15b 100644 --- a/src/main/resources/scripts/Plugins/IMCF_Utilities/Analyze/PSF Inspector.py +++ b/src/main/resources/scripts/Plugins/IMCF_Utilities/Analyze/PSF Inspector.py @@ -15,6 +15,7 @@ from collections import OrderedDict from datetime import date +import sjlogging from fr.igred.omero.roi import ROIWrapper from ij import IJ from ij import WindowManager as wm From 45c937791d76d1b387ebc84686cb3871f448fc67 Mon Sep 17 00:00:00 2001 From: Laurent Guerard Date: Tue, 24 Jun 2025 11:06:13 +0200 Subject: [PATCH 11/15] Add option for individual multi series files --- .../Stitch_Files_In_Directories.py | 56 +++++++++++++++---- 1 file changed, 45 insertions(+), 11 deletions(-) diff --git a/src/main/resources/scripts/Plugins/IMCF_Utilities/Stitching_Registration/Stitch_Files_In_Directories.py b/src/main/resources/scripts/Plugins/IMCF_Utilities/Stitching_Registration/Stitch_Files_In_Directories.py index 6e6ef9c..ffe7a86 100644 --- a/src/main/resources/scripts/Plugins/IMCF_Utilities/Stitching_Registration/Stitch_Files_In_Directories.py +++ b/src/main/resources/scripts/Plugins/IMCF_Utilities/Stitching_Registration/Stitch_Files_In_Directories.py @@ -6,6 +6,7 @@ # @Boolean(label="conserve RAM but be slower", description="tick this if your previous attempt failed with error", value="False") bigdata # @Boolean (label="convert stitched & fused image to Imaris5", description="convert the fused image to *.ims", value=True) convert_to_ims # @String (label="Send info email to: ", description="empty = skip", required="False") email_address +# @Boolean(label="Individual multi-series files ?", value=False) individual_series # @Boolean(label="Only output TileConfiguration.registered txt file ?", value=False) only_register # @DatasetIOService io # @ImageDisplayService ImageDisplayService @@ -140,13 +141,25 @@ def run_GC_stitcher(source, fusion_method, bigdata, quick, reg_threshold): + "image_output=[Fuse and display]" ) - params = ( - "type=[Positions from file] order=[Defined by TileConfiguration] " - + "directory=[" - + source - + "] " - + "layout_file=TileConfiguration.txt" - + " fusion_method=[" + if os.path.isfile(source): + params = ( + "type=[Positions from file] " + + "order=[Defined by image metadata] " + + "multi_series_file=[" + + source + + "]" + ) + else: + params = ( + "type=[Positions from file] order=[Defined by TileConfiguration] " + + "directory=[" + + source + + "] " + + "layout_file=TileConfiguration.txt" + ) + + params += ( + " fusion_method=[" + fusion_method + "] " + "regression_threshold=" @@ -155,7 +168,7 @@ def run_GC_stitcher(source, fusion_method, bigdata, quick, reg_threshold): + "max/avg_displacement_threshold=2.50 " + "absolute_displacement_threshold=3.50 " + ("" if quick else "compute_overlap subpixel_accuracy ") - + str(mode), + + mode ) print(params) @@ -377,7 +390,18 @@ def open_sequential_gcimages_from_folder(source, image_dimensions_czt): else: fusion_method = "Linear Blending" - for source_dir in all_source_dirs: + if individual_series: + all_source_dirs = [ + source_dirs + * len( + pathtools.listdir_matching( + source_dirs, filetype, fullpath=True, sort=True + ) + ) + for source_dirs in all_source_dirs + ] + + for dir_index, source_dir in enumerate(all_source_dirs): IJ.log("Now working on " + source_dir) print("bigdata= ", str(bigdata)) free_memory_bytes = MemoryTools().totalAvailableMemory() @@ -390,7 +414,11 @@ def open_sequential_gcimages_from_folder(source, image_dimensions_czt): source_dir, filetype, fullpath=True, sort=True ) - ome_stage_metadata = bf.get_stage_coords(allimages) + ome_stage_metadata = ( + bf.get_stage_coords(all_images) + if not individual_series + else bf.get_stage_coords(all_images[dir_index]) + ) # if filetype == "ome.tif": # write_tileconfig(source_dir, ome_metadata[0], allimages, ome_metadata[1], ome_metadata[2], ome_metadata[3]) @@ -404,7 +432,13 @@ def open_sequential_gcimages_from_folder(source, image_dimensions_czt): ome_stage_metadata.relative_coordinates_z, ) - run_GC_stitcher(source_dir, fusion_method, bigdata, quick, reg_threshold) + run_GC_stitcher( + source_dir if not individual_series else all_images[dir_index], + fusion_method, + bigdata, + quick, + reg_threshold, + ) calibrate_current_image( ome_stage_metadata.image_calibration, From 3d02914c74ebc5baeb0393b6536a14eac4e6dd9e Mon Sep 17 00:00:00 2001 From: Laurent Guerard Date: Tue, 24 Jun 2025 11:06:24 +0200 Subject: [PATCH 12/15] Clear log window --- .../Stitching_Registration/Stitch_Files_In_Directories.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/main/resources/scripts/Plugins/IMCF_Utilities/Stitching_Registration/Stitch_Files_In_Directories.py b/src/main/resources/scripts/Plugins/IMCF_Utilities/Stitching_Registration/Stitch_Files_In_Directories.py index ffe7a86..cebe3ba 100644 --- a/src/main/resources/scripts/Plugins/IMCF_Utilities/Stitching_Registration/Stitch_Files_In_Directories.py +++ b/src/main/resources/scripts/Plugins/IMCF_Utilities/Stitching_Registration/Stitch_Files_In_Directories.py @@ -369,6 +369,9 @@ def open_sequential_gcimages_from_folder(source, image_dimensions_czt): # ─── Main Code ──────────────────────────────────────────────────────────────── if __name__ == "__main__": + IJ.log("\\Clear") + IJ.log("Script starting") + # start the process execution_start_time = time.time() From e86f70a351f6745496726bfc7b0fa319c0d61095 Mon Sep 17 00:00:00 2001 From: Laurent Guerard Date: Tue, 24 Jun 2025 11:06:41 +0200 Subject: [PATCH 13/15] Fix path info to use the `imcflibs` --- .../Stitching_Registration/Stitch_Files_In_Directories.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/resources/scripts/Plugins/IMCF_Utilities/Stitching_Registration/Stitch_Files_In_Directories.py b/src/main/resources/scripts/Plugins/IMCF_Utilities/Stitching_Registration/Stitch_Files_In_Directories.py index cebe3ba..a6f1cea 100644 --- a/src/main/resources/scripts/Plugins/IMCF_Utilities/Stitching_Registration/Stitch_Files_In_Directories.py +++ b/src/main/resources/scripts/Plugins/IMCF_Utilities/Stitching_Registration/Stitch_Files_In_Directories.py @@ -380,7 +380,7 @@ def open_sequential_gcimages_from_folder(source, image_dimensions_czt): # In case script is ran batch source_info = pathtools.parse_path(source) - source = source_info["path"] + source = source_info["full"] # source = fix_ij_dirs(source) all_source_dirs = pathtools.find_dirs_containing_filetype(source, filetype) From b95317b3ded8fff1c62341095a7304f8576b7b07 Mon Sep 17 00:00:00 2001 From: Laurent Guerard Date: Tue, 24 Jun 2025 11:07:01 +0200 Subject: [PATCH 14/15] Ruff formatting and variable renaming --- .../Stitch_Files_In_Directories.py | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/src/main/resources/scripts/Plugins/IMCF_Utilities/Stitching_Registration/Stitch_Files_In_Directories.py b/src/main/resources/scripts/Plugins/IMCF_Utilities/Stitching_Registration/Stitch_Files_In_Directories.py index a6f1cea..d6ad6f7 100644 --- a/src/main/resources/scripts/Plugins/IMCF_Utilities/Stitching_Registration/Stitch_Files_In_Directories.py +++ b/src/main/resources/scripts/Plugins/IMCF_Utilities/Stitching_Registration/Stitch_Files_In_Directories.py @@ -102,7 +102,9 @@ def write_tileconfig( empty_column = list(str(" ") * len(imagenames)) - final_line = [";".join(i) for i in zip(image_filenames, empty_column, coordinates_xyz)] + final_line = [ + ";".join(i) for i in zip(image_filenames, empty_column, coordinates_xyz) + ] with open(outCSV, "wb") as f: f.write(row_1 + "\n") @@ -413,7 +415,7 @@ def open_sequential_gcimages_from_folder(source, image_dimensions_czt): bigdata = True IJ.log("Not enough free RAM, switching to BigData mode (slow)") - allimages = pathtools.listdir_matching( + all_images = pathtools.listdir_matching( source_dir, filetype, fullpath=True, sort=True ) @@ -424,7 +426,7 @@ def open_sequential_gcimages_from_folder(source, image_dimensions_czt): ) # if filetype == "ome.tif": - # write_tileconfig(source_dir, ome_metadata[0], allimages, ome_metadata[1], ome_metadata[2], ome_metadata[3]) + # write_tileconfig(source_dir, ome_metadata[0], all_images, ome_metadata[1], ome_metadata[2], ome_metadata[3]) # else: write_tileconfig( source_dir, @@ -459,19 +461,19 @@ def open_sequential_gcimages_from_folder(source, image_dimensions_czt): ome_stage_metadata.calibration_unit, ) path_to_image = save_current_image_as_bdv( - allimages[0], filetype, source_dir + all_images[0], filetype, source_dir ) misc.convert_to_imaris(convert_to_ims, path_to_image) shutil.rmtree(path, ignore_errors=True) # remove temp folder else: path_to_image = save_current_image_as_bdv( - allimages[0], filetype, source_dir + all_images[0], filetype, source_dir ) misc.convert_to_imaris(convert_to_ims, path_to_image) if not bigdata and not bdv and not only_register: path_to_image = save_current_image_with_BF_as_ics1( - allimages[0], filetype, source_dir + all_images[0], filetype, source_dir ) if convert_to_ims: From 2074d921c01d03934318f9923c169e1b5a5c1896 Mon Sep 17 00:00:00 2001 From: Laurent Guerard Date: Tue, 24 Jun 2025 11:31:05 +0200 Subject: [PATCH 15/15] Fix mistake in code --- .../Stitching_Registration/Stitch_Files_In_Directories.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/resources/scripts/Plugins/IMCF_Utilities/Stitching_Registration/Stitch_Files_In_Directories.py b/src/main/resources/scripts/Plugins/IMCF_Utilities/Stitching_Registration/Stitch_Files_In_Directories.py index d6ad6f7..3881fa6 100644 --- a/src/main/resources/scripts/Plugins/IMCF_Utilities/Stitching_Registration/Stitch_Files_In_Directories.py +++ b/src/main/resources/scripts/Plugins/IMCF_Utilities/Stitching_Registration/Stitch_Files_In_Directories.py @@ -438,7 +438,7 @@ def open_sequential_gcimages_from_folder(source, image_dimensions_czt): ) run_GC_stitcher( - source_dir if not individual_series else all_images[dir_index], + source_dir if not individual_series else [all_images[dir_index]], fusion_method, bigdata, quick,