changeset 0:2ac2a7d68b72 draft default tip

planemo upload for repository https://github.com/lldelisle/tools-lldelisle/tree/master/tools/omero_hyperstack_to_fluo_measurements_on_gastruloid commit 4eb6599440c6b220775dc79509983e7549e36e0b
author lldelisle
date Fri, 24 Mar 2023 13:03:27 +0000
parents
children
files 2-omero_timelapse_image_to_fluo_measurements.groovy omero_hyperstack_to_fluo_measurements_on_gastruloid.xml
diffstat 2 files changed, 915 insertions(+), 0 deletions(-) [+]
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/2-omero_timelapse_image_to_fluo_measurements.groovy	Fri Mar 24 13:03:27 2023 +0000
@@ -0,0 +1,819 @@
+// This macro was written by Lucille Delisle
+// A block was taken from https://www.geeksforgeeks.org/minimum-distance-from-a-point-to-the-line-segment-using-vectors/
+// written by 29AjayKumar
+// A block was written by Romain Guiet (BIOP, EPFL)
+// Interactions with omero are largely inspired by
+// templates available at https://github.com/BIOP/OMERO-scripts/tree/main/Fiji
+
+// Last modification: 2023-03-24
+
+// This macro works both in headless
+// or GUI
+
+// The input image(s) must be on omero
+// The input image(s) may have multiple time stacks
+// The input image(s) may have multiple channels
+// The channel to quantify on are set as parameter
+// The input image(s) must have ROIs on omero
+// Gastruloids ROI names must start with gastruloid (case insensitive)
+// Spine ROI names must start with spine (case insensitive)
+
+// In both modes,
+// The result table is written to {image_basename}__{t}_profil_Results.csv
+// One table per time
+// The measures are: Area,Mean,Min,Max,Date,Version,NPieces,Channel,Segment
+// A single table per image is sent to omero
+
+import fr.igred.omero.annotations.TableWrapper
+import fr.igred.omero.Client
+import fr.igred.omero.repository.DatasetWrapper
+import fr.igred.omero.repository.ImageWrapper
+import fr.igred.omero.repository.PlateWrapper
+import fr.igred.omero.repository.PixelsWrapper
+import fr.igred.omero.repository.WellWrapper
+import fr.igred.omero.roi.ROIWrapper
+
+import ij.ImagePlus
+import ij.gui.Overlay
+import ij.gui.PolygonRoi
+import ij.gui.Roi
+import ij.gui.ShapeRoi
+import ij.gui.TextRoi
+import ij.IJ
+import ij.io.FileSaver
+import ij.plugin.frame.RoiManager
+import ij.util.FontUtil
+
+import java.io.File
+
+import java.awt.Color
+import java.awt.GraphicsEnvironment
+
+import loci.plugins.in.ImporterOptions
+
+import org.apache.commons.io.FilenameUtils
+import org.apache.commons.io.FileUtils
+
+// This block was taken from https://www.geeksforgeeks.org/minimum-distance-from-a-point-to-the-line-segment-using-vectors/
+// written by 29AjayKumar
+class pair
+{
+    double F, S;
+    public pair(double F, double S)
+    {
+        this.F = F;
+        this.S = S;
+    }
+    public pair() {
+    }
+}
+ 
+// Function to return the minimum distance
+// between a line segment AB and a point E
+def minDistance(pair A, pair B, pair E)
+{
+ 
+    // vector AB
+    pair AB = new pair();
+    AB.F = B.F - A.F;
+    AB.S = B.S - A.S;
+ 
+    // vector BP
+    pair BE = new pair();
+    BE.F = E.F - B.F;
+    BE.S = E.S - B.S;
+ 
+    // vector AP
+    pair AE = new pair();
+    AE.F = E.F - A.F;
+    AE.S = E.S - A.S;
+ 
+    // Variables to store dot product
+    double AB_BE, AB_AE;
+ 
+    // Calculating the dot product
+    AB_BE = (AB.F * BE.F + AB.S * BE.S);
+    AB_AE = (AB.F * AE.F + AB.S * AE.S);
+ 
+    // Minimum distance from
+    // point E to the line segment
+    double reqAns = 0;
+ 
+    // Case 1
+    if (AB_BE > 0)
+    {
+ 
+        // Finding the magnitude
+        double y = E.S - B.S;
+        double x = E.F - B.F;
+        reqAns = Math.sqrt(x * x + y * y);
+    }
+ 
+    // Case 2
+    else if (AB_AE < 0)
+    {
+        double y = E.S - A.S;
+        double x = E.F - A.F;
+        reqAns = Math.sqrt(x * x + y * y);
+    }
+ 
+    // Case 3
+    else
+    {
+ 
+        // Finding the perpendicular distance
+        double x1 = AB.F;
+        double y1 = AB.S;
+        double x2 = AE.F;
+        double y2 = AE.S;
+        double mod = Math.sqrt(x1 * x1 + y1 * y1);
+        reqAns = Math.abs(x1 * y2 - y1 * x2) / mod;
+    }
+    return reqAns;
+}
+// end of block
+
+/**
+ * helper function to get coordonates that split a polyline into n pieces so return 2 arrays of size n+1
+ * For example:
+ *           B   C   
+ *           * --*
+ *          /    |
+ *         /     | M    the ROI ABCD
+ *      A *      |      cut into 3 should gives [[[xA,xB], [xB, xC, xM], [xM, xC], [[yA,yB], [yB, yC, yM], [yM, yD]]]
+ *               |
+ *               * C
+ */
+
+def getCooPoly(PolygonRoi test, int n) {
+    // The final x and y will be arrays where each element
+    // is an array with coordinates of the ROI which correspond to a piece
+    // of x
+    x = []
+    y = []
+    xpoints = test.getFloatPolygon().xpoints
+    ypoints = test.getFloatPolygon().ypoints
+    total_lines = xpoints.size() - 1
+    // The total length using each point may be different from the length provided by `.getLength()`
+    // Evaluate the total length useach each point:
+    total_length = 0
+    for (current_line = 0; current_line < total_lines; current_line ++) {
+        start_line_x = xpoints[current_line]
+        start_line_y = ypoints[current_line]
+        end_line_x = xpoints[current_line + 1]
+        end_line_y = ypoints[current_line + 1]
+        line_length = Math.sqrt( Math.pow(start_line_x  - end_line_x,2) + Math.pow(start_line_y  - end_line_y,2) )
+        total_length += line_length
+    }
+    // println total_length
+    // println test.getLength()
+
+    // current_x and current_y will have all coordinates to build the ROI with the current piece.
+    current_x = []
+    current_y = []
+    // current_line is the current line of the ROI test which we are testing
+    current_line = 0
+    // current_length is the length of the piece we are building
+    current_length = 0
+    start_line_x = xpoints[current_line]
+    start_line_y = ypoints[current_line]
+    // The first piece starts with the first coordinate of the first line
+    current_x.add(start_line_x)
+    current_y.add(start_line_y)
+    end_line_x = xpoints[current_line + 1]
+    end_line_y = ypoints[current_line + 1]
+    line_length = Math.sqrt( Math.pow(start_line_x  - end_line_x,2) + Math.pow(start_line_y  - end_line_y,2) )
+    // we loop over the index of the final pieces starting at 1
+    for (istop = 1; istop < n; istop++) {
+        // the final piece istop should go from
+        // total_length / n * (istop - 1) to total_length / n * istop
+        target_length = total_length / n * istop
+        // We compare the the length of the ROI formed by the points in current_x current_y
+        // Where we add the length of the current line
+        // with the target length
+        while (current_length + line_length < target_length) {
+            // We can integrate the current_line to the piece
+            // Go to the next line:
+            current_line ++
+            // Add the length of the previous line
+            current_length += line_length
+            // Get the start of the new line (= end of previous line)
+            start_line_x = xpoints[current_line]
+            start_line_y = ypoints[current_line]
+            // Add the coordinate to the piece
+            current_x.add(start_line_x)
+            current_y.add(start_line_y)
+            end_line_x = xpoints[current_line + 1]
+            end_line_y = ypoints[current_line + 1]
+            // Adjust the line_length to the new line
+            line_length = Math.sqrt( Math.pow(start_line_x  - end_line_x,2) + Math.pow(start_line_y  - end_line_y,2) )
+        }
+        // The target_length is between start_line and end_line:
+        // Get the proportion of the line to integrate into this piece:
+        prop_line = (target_length - current_length) / line_length
+        // Compute the coordinate of this point
+        target_x = start_line_x + (end_line_x - start_line_x) * prop_line
+        target_y = start_line_y + (end_line_y - start_line_y) * prop_line
+        // Add it to the current piece
+        current_x.add(target_x as int)
+        current_y.add(target_y as int)
+        // Add the piece to x and y
+        x.add(current_x)
+        y.add(current_y)
+        // Start a new piece with this point
+        current_x = [target_x as int]
+        current_y = [target_y as int]
+    }
+    // To build the last piece, no need to compare lengths
+    // Just add all points to the piece
+    while(current_line != total_lines) {
+        // Go to the next line:
+        current_line ++
+        start_line_x = xpoints[current_line]
+        start_line_y = ypoints[current_line]
+        current_x.add(start_line_x)
+        current_y.add(start_line_y)
+    }
+    // Add the final piece to x and y
+    x.add(current_x)
+    y.add(current_y)
+    return([x,y])
+}
+
+
+/** Find the index i of the coordinates of poly_x poly_y
+* where poly_x poly_y are the coordinates of a closed polygon
+* for which the line composed of the point i i+1
+* which is the closest to the point A
+* depends on the class pair
+* and the function minDistance
+*/
+def find_index(poly_x, poly_y, pair A) {
+    // Initiate to non existing index
+    // And a large distance
+    closest_seg_start = -1
+    dist = 1000000
+    for (i = 0; i < (poly_x.size() - 1); i++) {
+        // For each line of the ROI poly_x poly_y
+        // Compute the distance with pair A
+        pair pairstart = new pair(poly_x[i], poly_y[i])
+        pair pairend = new pair(poly_x[i+1], poly_y[i+1])
+        current_dist = minDistance(pairstart, pairend, A)
+        // If it is the minimum update dist and closest_seg_start
+        if (current_dist < dist) {
+            dist = current_dist
+            closest_seg_start = i
+        }
+    }
+    // Do the last comp:
+    // The line which joins the last point to the first point
+    pair pairstart = new pair(poly_x[-1], poly_y[-1])
+    pair pairend = new pair(poly_x[0], poly_y[0])
+    current_dist = minDistance(pairstart, pairend, A)
+    if (current_dist < dist) {
+        dist = current_dist
+        closest_seg_start = i
+    }
+    return closest_seg_start
+}
+
+
+def processDataset(Client user_client, DatasetWrapper dataset_wpr,
+                   File output_directory, Integer quantif_ch,
+                   Integer n_pieces,
+                   Boolean headless_mode, String tool_version) {
+    dataset_wpr.getImages(user_client).each{ ImageWrapper img_wpr ->
+        processImage(user_client, img_wpr,
+                     output_directory, quantif_ch,
+                     n_pieces,
+                     headless_mode, tool_version)
+    }
+}
+
+def processSinglePlate(Client user_client, PlateWrapper plate_wpr,
+                       File output_directory, Integer quantif_ch,
+                       Integer n_pieces,
+                       Boolean headless_mode, String tool_version) {
+    plate_wpr.getWells(user_client).each{ well_wpr ->
+        processSingleWell(user_client, well_wpr,
+                          output_directory, quantif_ch,
+                          n_pieces,
+                          headless_mode, tool_version)
+    }
+}
+
+def processSingleWell(Client user_client, WellWrapper well_wpr,
+                      File output_directory, Integer quantif_ch,
+                      Integer n_pieces,
+                      Boolean headless_mode, String tool_version) {
+    well_wpr.getWellSamples().each{
+        processImage(user_client, it.getImage(),
+                     output_directory, quantif_ch,
+                     n_pieces,
+                     headless_mode, tool_version)
+    }
+}
+
+def processImage(Client user_client, ImageWrapper image_wpr,
+                 File output_directory, Integer quantif_ch,
+                 Integer n_pieces,
+                 Boolean headless_mode, String tool_version) {
+
+    IJ.run("Close All", "")
+    IJ.run("Clear Results")
+    // Clean ROI manager
+    if (!headless_mode) {
+        rm = new RoiManager()
+        rm = rm.getRoiManager()
+        rm.reset()
+    }
+
+    // Print image information
+    println "\n Image infos"
+    String image_basename = image_wpr.getName()
+    println ("Image_name : " + image_basename + " / id : " + image_wpr.getId())
+    List<DatasetWrapper> dataset_wpr_list = image_wpr.getDatasets(user_client)
+
+    // if the image is part of a dataset
+    if(!dataset_wpr_list.isEmpty()){
+        dataset_wpr_list.each{println("dataset_name : "+it.getName()+" / id : "+it.getId())};
+        image_wpr.getProjects(user_client).each{println("Project_name : "+it.getName()+" / id : "+it.getId())};
+    }
+
+    // if the image is part of a plate
+    else {
+        WellWrapper well_wpr = image_wpr.getWells(user_client).get(0)
+        println ("Well_name : "+well_wpr.getName() +" / id : "+ well_wpr.getId())
+
+        def plate_wpr = image_wpr.getPlates(user_client).get(0)
+        println ("plate_name : "+plate_wpr.getName() + " / id : "+ plate_wpr.getId())
+
+        def screen_wpr = image_wpr.getScreens(user_client).get(0)
+        println ("screen_name : "+screen_wpr.getName() + " / id : "+ screen_wpr.getId())
+    }
+    ImagePlus imp = image_wpr.toImagePlus(user_client);
+
+    if (!headless_mode) {
+        imp.show()
+    }
+
+    // get imp info
+    dim_array = imp.getDimensions();
+    nC = dim_array[2]
+    nT = dim_array[4]
+
+    // Get Gastruloids and Spines from omero:
+    println "Get ROIs from OMERO"
+    List<Roi> omeroRois = ROIWrapper.toImageJ(image_wpr.getROIs(user_client), "ROI")
+    println "Found " + omeroRois.size()
+
+    if (omeroRois.size() == 0) {
+        return
+    }
+
+    println "Get tables from OMERO"
+    // get the list of image tables
+    // remove the one with table_name
+    image_wpr.getTables(user_client).each{ TableWrapper t_wpr ->
+        println "Found table: " + t_wpr.getName()
+        if (t_wpr.getName() == table_name){
+            user_client.delete(t_wpr)
+        }
+    }
+    // Define an omero table
+    TableWrapper table_wpr
+    // Put them in HashMap
+    Map<String, Roi> gastruloid_rois = new HashMap<>();
+    Map<String, Roi> spine_rois = new HashMap<>();
+    Map<String, Boolean> spine_rois_on_fluo = new HashMap<>();
+    for (roi in omeroRois) {
+        current_t = roi.getTPosition()
+        roi_name = roi.getName()
+        println "Found ROI: " + roi_name
+
+        if (roi_name.toLowerCase().startsWith("gastruloid") && !roi_name.toLowerCase().startsWith("gastruloidnotfound")) {
+        	println "This is a gastruloid of time " + current_t
+            gastruloid_rois.put(current_t, roi)
+        }
+        if (roi_name.toLowerCase().startsWith("spine")) {
+        	println "This is a spine of time " + current_t
+            if (roi.getCPosition() != quantif_ch) {
+                spine_rois.put(current_t, roi)
+            } else {
+                spine_rois_on_fluo.put(current_t, true)
+            }
+        }
+    }
+    
+    if ( gastruloid_rois.size() == 0) {
+    	println "No gastruloid found"
+    	return
+    }
+
+    /**
+     * Measure on ROI along spine
+     * Make a table with the values for each t
+     * Add Overlay
+     */
+
+    IJ.run("Set Measurements...", "area mean min display redirect=None decimal=3");
+    // to_omero_ov contains all ROIs that
+    // are not quantified but will be shiped to OMERO
+    Overlay to_omero_ov = new Overlay()
+    // Get Date
+    Date date = new Date()
+    String now = date.format("yyyy-MM-dd_HH-mm")
+
+    for (int t = 1; t <= nT; t++ ){
+    	println "Analysis on t: " + t
+        // We need to create one overlay per frame
+        // In order to measure it
+        Overlay ov = new Overlay()
+        gastruloid_roi = gastruloid_rois.get(t)
+        if ( gastruloid_roi != null) {
+        	println "Gastruloid found"
+            // There is a gastruloid
+            gastruloid_roi.setName("WholeGastruloid_t" + t)
+            gastruloid_roi.setPosition( quantif_ch, 1, t)
+            ov.add(gastruloid_roi)
+            full = new Roi(0, 0, imp.getWidth(), imp.getHeight())
+            full.setName("Full_t" + t)
+            full.setPosition( quantif_ch, 1, t)
+            ov.add(full)
+            non_gastruloid = new ShapeRoi(full).not(new ShapeRoi(gastruloid_roi))
+            non_gastruloid.setName("NonGastruloid_t" + t)
+            non_gastruloid.setPosition( quantif_ch, 1, t)
+            ov.add(non_gastruloid)
+
+            println "Current t = " + t
+            spine_roi = spine_rois.get(t)
+            println "spine is " + spine_roi
+            if (spine_roi != null) {
+                // Extract coordinates of the spine
+                spine_xs = spine_roi.getFloatPolygon().xpoints
+                spine_ys = spine_roi.getFloatPolygon().ypoints
+
+                // A is first point
+                pair A = new pair(spine_xs[0], spine_ys[0])
+                // P is last point
+                pair P = new pair(spine_xs[-1], spine_ys[-1])
+
+                // Extract coordinates of the ROI
+                gastruloid_x = gastruloid_roi.getFloatPolygon().xpoints
+                gastruloid_y = gastruloid_roi.getFloatPolygon().ypoints
+
+                // Find where integrate A and P in the ROI
+                // println "Find ap on gastruloid"
+                closest_seg_start_A = find_index(gastruloid_x, gastruloid_y, A)
+                closest_seg_start_P = find_index(gastruloid_x, gastruloid_y, P)
+
+                // Create 2 polylines going from A to P using both sides of polygon
+                println "Split gastruloid ROI into 2 lines"
+                // First going increasing:
+                poly1_x = [A.F]
+                poly1_y = [A.S]
+                if (closest_seg_start_A + 1 < closest_seg_start_P) {
+                    // Just goes from one to the other
+                    poly1_x += Arrays.copyOfRange(gastruloid_x as int[], closest_seg_start_A + 1, closest_seg_start_P + 1) as List
+                    poly1_y += Arrays.copyOfRange(gastruloid_y as int[], closest_seg_start_A + 1, closest_seg_start_P + 1) as List
+                } else {
+                    // Goes from one to the end and from the start to the second one
+                    poly1_x += Arrays.copyOfRange(gastruloid_x as int[], closest_seg_start_A + 1, gastruloid_x.size()) as List
+                    poly1_x += Arrays.copyOfRange(gastruloid_x as int[], 0, closest_seg_start_P + 1) as List
+                    poly1_y += Arrays.copyOfRange(gastruloid_y as int[], closest_seg_start_A + 1, gastruloid_x.size()) as List
+                    poly1_y += Arrays.copyOfRange(gastruloid_y as int[], 0, closest_seg_start_P + 1) as List
+                }
+                poly1_x.add(P.F)
+                poly1_y.add(P.S)
+                poly1 = new PolygonRoi(poly1_x as int[], poly1_y as int[], poly1_x.size(), Roi.POLYLINE)
+                // Second going decreasing:
+                // Because it is easier we build it P to A
+                // And then revert it:
+                // Building
+                poly2_x = [P.F]
+                poly2_y = [P.S]
+                if (closest_seg_start_P + 1 <= closest_seg_start_A) {
+                    // Just goes from one to the other
+                    poly2_x += Arrays.copyOfRange(gastruloid_x as int[], closest_seg_start_P + 1, closest_seg_start_A + 1) as List
+                    poly2_y += Arrays.copyOfRange(gastruloid_y as int[], closest_seg_start_P + 1, closest_seg_start_A + 1) as List
+                } else {
+                    // Goes from one to the end and from the start to the second one
+                    poly2_x += Arrays.copyOfRange(gastruloid_x as int[], closest_seg_start_P + 1, gastruloid_x.size()) as List
+                    poly2_x += Arrays.copyOfRange(gastruloid_x as int[], 0, closest_seg_start_A + 1) as List
+                    poly2_y += Arrays.copyOfRange(gastruloid_y as int[], closest_seg_start_P + 1, gastruloid_x.size()) as List
+                    poly2_y += Arrays.copyOfRange(gastruloid_y as int[], 0, closest_seg_start_A + 1) as List
+                }
+                poly2_x.add(A.F)
+                poly2_y.add(A.S)
+                // Reverting
+                poly2_x = poly2_x.reverse()
+                poly2_y = poly2_y.reverse()
+                poly2 = new PolygonRoi(poly2_x as int[], poly2_y as int[], poly2_x.size(), Roi.POLYLINE)
+
+                // Now I cut into n_pieces pieces the 3 polylines:
+                // println "CUT"
+                cut_spine = getCooPoly(spine_roi, n_pieces)
+                cut_poly1 = getCooPoly(poly1, n_pieces)
+                cut_poly2 = getCooPoly(poly2, n_pieces)
+
+                // For each piece we form the new ROI:
+                for (i = 0 ; i < n_pieces; i++) {
+                    // println "Reform " + i
+                    roi_x = [cut_spine[0][i][0]] + cut_poly1[0][i]+ [cut_spine[0][i][-1]] + cut_poly2[0][i].reverse()
+                    roi_y = [cut_spine[1][i][0]] + cut_poly1[1][i] + [cut_spine[1][i][-1]] + cut_poly2[1][i].reverse()
+                    new_roi = new PolygonRoi(roi_x as int[], roi_y as int[], roi_x.size(), Roi.POLYGON)
+                    // We restrict the piece to the part which
+                    // overlaps the gastruloid
+                    new_roi2 = new ShapeRoi(new_roi as Roi).and(new ShapeRoi(gastruloid_roi as Roi))
+                    new_roi2.setPosition( quantif_ch, 1, t)
+                    new_roi2.setName("Segment_" + i + "_t" + t)
+                    ov.add(new_roi2)
+                    if (!headless_mode) {
+                        rm.addRoi(new_roi2)
+                    }
+                }
+                // println "DONE"
+                // finally add Rois to Overlay if not already present:
+                if (spine_rois_on_fluo.get(t) == null) {
+                    to_omero_ov = addText(to_omero_ov, spine_roi, "A", t, 0, A.F, A.S, quantif_ch)
+                    to_omero_ov = addText(to_omero_ov, spine_roi, "P", t, -1, P.F, P.S, quantif_ch)
+                    spine_roi.setName(spine_roi.getName() + "_on_fluo")
+                    spine_roi.setPosition( quantif_ch, 1, t)
+                    to_omero_ov.add(spine_roi)
+                }
+            }
+            imp.setPosition(quantif_ch, 1, t)
+            println "set overlay"
+            imp.setOverlay(ov)
+            // Measure on each item of the overlay
+            def rt_profil_t = ov.measure(imp)
+
+            // Add Date, version and params
+            for ( int row = 0;row<rt_profil_t.size();row++) {
+                rt_profil_t.setValue("Date", row, now)
+                rt_profil_t.setValue("Version", row, tool_version)
+                rt_profil_t.setValue("NPieces", row, n_pieces)
+                rt_profil_t.setValue("Channel", row, quantif_ch)
+                rt_profil_t.setValue("Time", row, t)
+                String label = rt_profil_t.getLabel(row)
+                rt_profil_t.setValue("BaseImage", row, label.split(":")[0])
+                rt_profil_t.setValue("ROI", row, label.split(":")[1])
+                rt_profil_t.setValue("ROI_type", row, label.split(":")[1].split("_t")[0])
+            }
+            println "Remove ROIs from previous experiments on OMERO"
+            // Remove existing ROIs
+            println "Time " + t
+            image_wpr.getROIs(user_client).each{
+            	String roi_name = it.toImageJ().get(0).getName()
+                if ((roi_name == "WholeGastruloid_t" + t) ||
+                    (roi_name == "Full_t" + t) ||
+                    (roi_name == "NonGastruloid_t" + t) ||
+                    (roi_name.startsWith("Segment_") &&
+                     roi_name.endsWith("_t" + t))) {
+                     	println "Remove " + roi_name
+                        user_client.delete(it)
+                }
+            }
+            println "Store " + ov.size() + " ROIs on OMERO"
+            // Save ROIs to omero
+            image_wpr.saveROIs(user_client, ROIWrapper.fromImageJ(ov as List))
+
+            // Get them back with IDs:
+            ArrayList<Roi> updatedRois = []
+            println "Now there is"
+            ROIWrapper.toImageJ(image_wpr.getROIs(user_client), "ROI").each{
+            	println it.getName()
+                if ((it.getName() == "WholeGastruloid_t" + t) ||
+                    (it.getName() == "Full_t" + t) ||
+                    (it.getName() == "NonGastruloid_t" + t) ||
+                    (it.getName().startsWith("Segment_") &&
+                     it.getName().endsWith("_t" + t))) {
+                        updatedRois.add(it)
+                }
+            }
+            println "Writting measurements to file"
+            rt_profil_t.save(output_directory.toString() + '/' + image_basename+"__"+t+"_profil_Results.csv" )
+            if (table_wpr == null) {
+                table_wpr = new TableWrapper(user_client, rt_profil_t, image_wpr.getId(), updatedRois, "ROI")
+                // add the same infos to the super_table
+                if (super_table == null) {
+                    println "super_table is null"
+                    super_table = new TableWrapper(user_client, rt_profil_t, image_wpr.getId(), updatedRois, "ROI")
+                } else {
+                    println "adding rows"
+                    super_table.addRows(user_client, rt_profil_t, image_wpr.getId(), updatedRois, "ROI")
+                }
+            } else {
+                println "adding rows"
+                table_wpr.addRows(user_client, rt_profil_t, image_wpr.getId(), updatedRois, "ROI")
+                super_table.addRows(user_client, rt_profil_t, image_wpr.getId(), updatedRois, "ROI")
+            }
+        }
+    }
+
+    // upload the table on OMERO
+    table_wpr.setName(table_name)
+    image_wpr.addTable(user_client, table_wpr)
+
+    // Send non-quantified ROIs to OMERO
+    println "in non-quantified"
+    to_omero_ov.each{
+    	println it.getName()
+    }
+    image_wpr.saveROIs(user_client, ROIWrapper.fromImageJ(to_omero_ov as List))
+
+    // Put all ROIs in overlay:
+    Overlay global_overlay = new Overlay()
+    ROIWrapper.toImageJ(image_wpr.getROIs(user_client), "ROI").each{
+    	if (it.getTypeAsString() == "Text") {
+    		it.setFont(new FontUtil().getFont("Arial", 1 , 72 as float ))
+    	}
+        global_overlay.add(it)
+    }
+
+    imp.setOverlay(global_overlay)
+
+    // save file
+    def fs = new FileSaver(imp)
+    output_path = new File (output_directory ,image_basename+".tif" )
+    fs.saveAsTiff(output_path.toString() )
+
+    return
+}
+
+// Block written by Romain Guiet
+/**
+ * helper function to add A and P letter to the image
+ */
+
+def addText( ov, roi, txt, pos, idx, x, y, channel){
+    if (idx == 0)    roi.defaultColor = new Color(255,128,0)
+    else             roi.defaultColor = new Color(0,128,255)
+
+    txtroi = new TextRoi( x , y , 50.0 , 50.0 ,txt ,  new FontUtil().getFont("Arial", 1 , 72 as float ) )
+    txtroi.setPosition(channel, 1, pos as int)
+    txtroi.setName(txt + "_t" + pos)
+    ov.add( txtroi  )
+
+    return ov
+}
+// End of block
+
+
+// In simple-omero-client
+// Strings that can be converted to double are stored in double
+// In order to build the super_table, tool_version should stay String
+String tool_version = "Fluo_v20230324"
+
+// User set variables
+
+#@ String(visibility=MESSAGE, value="Inputs", required=false) msg
+#@ String(label="User name") USERNAME
+#@ String(label="PASSWORD", style='PASSWORD', value="", persist=false) PASSWORD
+#@ String(label="File path with omero credentials") credentials
+#@ String(label="omero host server") host
+#@ Integer(label="omero host server port", value=4064) port
+#@ String(label="Object", choices={"image","dataset","well","plate"}) object_type
+#@ Long(label="ID", value=119273) id
+
+#@ String(visibility=MESSAGE, value="Parameters for quantification", required=false) msg2
+#@ Integer(label="Number of pieces along the spine", value="10") n_pieces
+#@ Integer(label="Index of the channel to quantify (1-based)", value="1") quantif_ch
+
+#@ String(visibility=MESSAGE, value="Parameters for output", required=false) msg4
+#@ File(style = "directory", label="Directory where measures are put") output_directory
+
+// java.awt.GraphicsEnvironment.checkheadless_mode(GraphicsEnvironment.java:204)
+Boolean headless_mode = GraphicsEnvironment.isHeadless()
+if (headless_mode) {
+    println "Running in headless mode"
+}
+
+// Define rm even if in headless mode
+def rm
+if (!headless_mode){
+    // Reset RoiManager if not in headless
+    rm = new RoiManager()
+    rm = rm.getRoiManager()
+    rm.reset()
+}
+
+if (PASSWORD == "") {
+    File cred_file = new File(credentials)
+    if (!cred_file.exists()) {
+        throw new Exception("Password or credential file need to be set.")
+    }
+    String creds = FileUtils.readFileToString(cred_file, "UTF-8")
+    if (creds.split("\n").size() < 2) {
+        throw new Exception("Credential file requires 2 lines")
+    }
+    USERNAME = creds.split("\n")[0]
+    PASSWORD = creds.split("\n")[1]
+}
+
+// Connection to server
+Client user_client = new Client()
+user_client.connect(host, port, USERNAME, PASSWORD.toCharArray())
+
+
+// This super_table is a global variable and will be filled each time an image is processed.
+super_table = null
+// table_name is also a global variable
+table_name = "Measurements_from_Galaxy_fluo"
+if (user_client.isConnected()) {
+    println "\nConnected to "+host
+    println "Results will be in " + output_directory.toString()
+
+    try {
+        switch (object_type) {
+            case "image":
+                ImageWrapper image_wr
+                try {
+                    image_wpr = user_client.getImage(id)
+                } catch(Exception e) {
+                    throw Exception("Could not retrieve the image, please check the id.")
+                }
+                processImage(user_client, image_wpr,
+                             output_directory, quantif_ch,
+                             n_pieces,
+                             headless_mode, tool_version)
+                break
+            case "dataset":
+                DatasetWrapper dataset_wrp
+                try {
+                    dataset_wrp = user_client.getDataset(id)
+                } catch(Exception e) {
+                    throw Exception("Could not retrieve the dataset, please check the id.")
+                }
+                processDataset(user_client, dataset_wrp,
+                               output_directory, quantif_ch,
+                               n_pieces,
+                               headless_mode, tool_version)
+                // get the list of dataset tables
+                // remove the one with table_name
+                dataset_wrp.getTables(user_client).each{ TableWrapper t_wpr ->
+                    if (t_wpr.getName() == table_name + "_global"){
+                        user_client.delete(t_wpr)
+                    }
+                }
+                // upload the table on OMERO
+                super_table.setName(table_name + "_global")
+                dataset_wrp.addTable(user_client, super_table)
+                break
+            case "well":
+                WellWrapper well_wrp
+                try {
+                    well_wrp = user_client.getWells(id)[0]
+                } catch(Exception e) {
+                    throw Exception("Could not retrieve the well, please check the id.")
+                }
+                processSingleWell(user_client, well_wrp,
+                                  output_directory, quantif_ch,
+                                  n_pieces,
+                                  headless_mode, tool_version)
+                // get the list of well tables
+                // remove the one with table_name
+                well_wrp.getTables(user_client).each{ TableWrapper t_wpr ->
+                    if (t_wpr.getName() == table_name + "_global"){
+                        user_client.delete(t_wpr)
+                    }
+                }
+                // upload the table on OMERO
+                super_table.setName(table_name + "_global")
+                well_wrp.addTable(user_client, super_table)
+                break
+            case "plate":
+                PlateWrapper plate_wrp
+                try {
+                    plate_wrp = user_client.getPlates(id)[0]
+                } catch(Exception e) {
+                    throw Exception("Could not retrieve the plate, please check the id.")
+                }
+                processSinglePlate(user_client, plate_wrp,
+                                   output_directory, quantif_ch,
+                                   n_pieces,
+                                   headless_mode, tool_version)
+                // get the list of well tables
+                // remove the one with table_name
+                plate_wrp.getTables(user_client).each{ TableWrapper t_wpr ->
+                    if (t_wpr.getName() == table_name + "_global"){
+                        user_client.delete(t_wpr)
+                    }
+                }
+                // upload the table on OMERO
+                super_table.setName(table_name + "_global")
+                plate_wrp.addTable(user_client, super_table)
+                break
+        }
+
+    } catch(Exception e) {
+        println("Something went wrong: " + e)
+    } finally {
+        user_client.disconnect()
+        println "Disonnected " + host
+    }
+} else {
+    println "Not able to connect to " + host
+}
+
+return
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/omero_hyperstack_to_fluo_measurements_on_gastruloid.xml	Fri Mar 24 13:03:27 2023 +0000
@@ -0,0 +1,96 @@
+<tool id="omero_hyperstack_to_fluo_measurements_on_gastruloid" name="Omero hyperstack to Fluo measurements" profile="20.01" version="@TOOL_VERSION@+galaxy0">
+    <description>on gastruloid</description>
+    <macros>
+        <token name="@TOOL_VERSION@">20230324</token>
+    </macros>
+    <requirements>
+        <requirement type="package" version="20220414">fiji</requirement>
+        <requirement type="package" version="5.8.0">fiji-omero_ij</requirement>
+        <requirement type="package" version="5.12.2">fiji-simple_omero_client</requirement>
+    </requirements>
+    <command detect_errors="exit_code"><![CDATA[
+        ## the user wants to use a non-public OMERO instance
+        ## check if credentials are set in the user-preferences, if not warn the user and exit
+        #set $username = $__user__.extra_preferences.get('omero_account|username', "")
+        #set $password = $__user__.extra_preferences.get('omero_account|password', "")
+
+        #if ($username == "" or $password ==""):
+            echo "OMERO connection credentials are empty. Set your credentials via: User -> Preferences -> Manage Information" 1>&2 &&
+            exit 1 &&
+        #end if
+        mkdir output &&
+        ## Because ilastik wants to write to ${HOME}/.cache and ${HOME}/.config
+        export HOME=`pwd` &&
+        ImageJ --ij2 --headless --console --run '$__tool_directory__/'2-omero_timelapse_image_to_fluo_measurements.groovy 
+            'USERNAME="",PASSWORD="",credentials="${credentials}",host="${omero_host}",port="${omero_port}",object_type="${omero_object.object_type}",id="${omero_object.omero_id}",n_pieces="${n_pieces}",quantif_ch="${quantif_ch}",output_directory="output"' > output.log
+        ]]>
+    </command>
+    <configfiles>
+        <configfile name="credentials"><![CDATA[#set $username = $__user__.extra_preferences.get('omero_account|username', "")
+    #set $password = $__user__.extra_preferences.get('omero_account|password', "")
+$username
+$password
+        ]]></configfile>
+    </configfiles>
+    <inputs>
+        <param name="omero_host" type="text" label="OMERO host URL">
+            <validator type="regex" message="Enter a valid host location, for example, your.omero.server">^[a-zA-Z0-9._-]*$</validator>
+            <validator type="expression" message="No two dots (..) allowed">'..' not in value</validator>
+        </param>
+        <param name="omero_port" type="integer" value="4064" label="Omero port" />
+        <conditional name="omero_object">
+            <param name="object_type" type="select" label="Type of object to analyze">
+                <option value="image">Single Omero Image</option>
+                <option value="well">All images of a Well</option>
+                <option value="plate">All images of a Plate</option>
+                <option value="dataset">All images of a Dataset</option>
+            </param>
+            <when value="image">
+                <param name="omero_id" type="integer" value="" label="Image ID on omero" />
+            </when>
+            <when value="well">
+                <param name="omero_id" type="integer" value="" label="Well ID on omero" />
+            </when>
+            <when value="plate">
+                <param name="omero_id" type="integer" value="" label="Plate ID on omero" />
+            </when>
+            <when value="dataset">
+                <param name="omero_id" type="integer" value="" label="Dataset ID on omero" />
+            </when>
+        </conditional>
+        <param name="n_pieces" type="integer" value="10" label="Number of segments along the spine" />
+        <param name="quantif_ch" type="integer" value="2" label="Index of the channel to quantify (1-based)" />
+    </inputs>
+
+    <outputs>
+        <data name="logfile" format="txt" from_work_dir="output.log" label="${tool.name} on ID ${omero_object.omero_id}: logfile">
+        </data>
+        <collection name="tables" type="list" label="${tool.name} on ID ${omero_object.omero_id}: Profile Tables">
+            <discover_datasets pattern="(?P&lt;designation&gt;.+)\.csv" directory="output" format="csv"/>
+        </collection>
+        <collection name="hyperstacks_with_overlay" type="list" label="${tool.name} on ID ${omero_object.omero_id}: Hyperstacks">
+            <discover_datasets pattern="(?P&lt;designation&gt;.+)\.tif" directory="output" format="tiff"/>
+        </collection>
+    </outputs>
+    <help>
+    <![CDATA[
+**Overview**
+
+This tool must be run after omero_hyperstack_to_gastruloid_measurements.
+It will get images from omero, measure fluorescence on the gastruloids and outside.
+It will also cut the gastruloid along the spine into segments and quantify the fluo on each segment.
+
+**License**
+
+License text::
+
+    // This macro was written by Lucille Delisle
+    // A block was taken from https://www.geeksforgeeks.org/minimum-distance-from-a-point-to-the-line-segment-using-vectors/
+    // written by 29AjayKumar
+    // A block was written by Romain Guiet (BIOP, EPFL)
+    // Interactions with omero are largely inspired by
+    // templates available at https://github.com/BIOP/OMERO-scripts/tree/main/Fiji
+
+]]>
+    </help>
+</tool>