changeset 0:8948cc562b7c draft default tip

planemo upload for repository https://github.com/bgruening/galaxytools/tree/master/tools commit 4d4d0b10eb6be3b1f13b2becc8f057ac41d3f0de
author bgruening
date Thu, 29 Feb 2024 17:43:50 +0000
parents
children
files macros.xml measureWoundClosing.groovy test-data/A3ROI2_Slow.csv woundhealing.xml
diffstat 4 files changed, 319 insertions(+), 0 deletions(-) [+]
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/macros.xml	Thu Feb 29 17:43:50 2024 +0000
@@ -0,0 +1,23 @@
+<macros>
+    <token name="@VERSION@">1.6.1</token>
+    <token name="@FORMATS@">jpg,png,tiff,tif,bmp,gif,pcx,ppm,psd,pbm,pgm,eps</token>
+    <xml name="requirements">
+        <requirements>
+            <requirement type="package" version="@VERSION@">fiji-morpholibj</requirement>
+            <yield />
+        </requirements>
+    </xml>
+    <xml name="citations">
+        <citations>
+            <citation type="bibtex">
+                @misc{gitlabwound,
+		    author={C Tischer},
+		    title= "Wound healing scratch assay image analysis",
+		    publisher = {GitLab},
+		    journal ={GitLab repository},
+		    url  ={https://git.embl.de/grp-cba/wound-healing-htm-screen} 
+		}
+            </citation>
+        </citations>
+    </xml>
+</macros>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/measureWoundClosing.groovy	Thu Feb 29 17:43:50 2024 +0000
@@ -0,0 +1,232 @@
+/**
+ * This script runs in Fiji
+ *
+ * It needs the following Fiji update sites:
+ * - IJPB-Plugins (MorpholibJ)
+ */
+
+
+import fiji.threshold.Auto_Threshold
+import ij.IJ
+import ij.ImagePlus
+import ij.gui.Roi
+import ij.measure.Measurements
+import ij.measure.ResultsTable
+import ij.plugin.FolderOpener
+import ij.plugin.ImageCalculator
+import ij.plugin.filter.Analyzer
+import ij.plugin.frame.RoiManager
+import inra.ijpb.binary.BinaryImages
+import inra.ijpb.morphology.Morphology
+import inra.ijpb.morphology.Reconstruction
+import inra.ijpb.morphology.strel.SquareStrel
+import inra.ijpb.segment.Threshold
+import net.imagej.ImageJ
+
+// INPUT UI AND CLI
+//
+#@ File (label="Input directory", style="directory") inputDir
+#@ String (label="Dataset ID") datasetId
+#@ Double (label="CoV threshold (-1: auto)", default="-1") threshold
+#@ Boolean (label="Run headless", default="false") headless
+#@ Boolean (label="Save results", default="true") saveResults
+#@ String (label="Output directory name (will be created next to input directory)", default="analysis") outDirName
+
+// INPUT FOR TESTING WITHIN IDE
+//
+//def inputDir = new File("/Users/tischer/Documents/wound-healing-htm-screen/data/input")
+//def datasetId = "A3ROI2_Slow"; // C4ROI1_Fast A3ROI2_Slow
+//def outDirName = "analysis"
+//def threshold = (double) -1.0 // auto
+//def headless = false;
+//def saveResults = false;
+//new ij.ImageJ().setVisible(true)
+
+// FIXED PARAMETERS
+//
+def cellDiameter = 20
+def scratchDiameter = 500
+def binningFactor = 2
+
+// DERIVED PARAMETERS
+//
+def cellFilterRadius = cellDiameter/binningFactor
+def scratchFilterRadius = scratchDiameter/binningFactor
+
+println("Cell filter radius: " + cellFilterRadius)
+println("Scratch filter radius: " + scratchFilterRadius)
+
+// CODE
+//
+
+// open the images
+//
+IJ.run("Close All");
+println("Opening: " + datasetId)
+def imp = FolderOpener.open(inputDir.toString(), " filter=(.*"+datasetId+".*)")
+println("Number of slices: " + imp.getNSlices())
+
+if ( imp == null  || imp.getNSlices() == 0 ) {
+    println("Could not find any files matching the pattern!")
+    System.exit(1)
+}
+
+// process the images to enhance regions with cells
+// using the fact the the cell regions have a higher local variance
+//
+println("Process images to enhance regions containing cells...")
+// remove scaling to work in pixel units
+IJ.run(imp,"Properties...", "pixel_width=1 pixel_height=1 voxel_depth=1");
+// bin to save compute time
+IJ.run(imp, "Bin...", "x=" + binningFactor + " y=" + binningFactor + " z=1 bin=Average");
+def binnedImp = imp.duplicate() // keep for saving
+// enhance cells
+IJ.run(imp, "32-bit", "");
+def sdevImp = imp.duplicate()
+sdevImp.setTitle(datasetId + " sdev" )
+IJ.run(sdevImp, "Find Edges", "stack"); // removes larger structures, such as dirt in the background
+IJ.run(sdevImp, "Variance...", "radius=" + cellFilterRadius + " stack");
+IJ.run(sdevImp, "Square Root", "stack");
+// mean
+def meanImp = imp.duplicate()
+meanImp.setTitle(datasetId + " mean")
+IJ.run(meanImp, "Mean...", "radius=" + cellFilterRadius + " stack");
+// cov
+def covImp = ImageCalculator.run(sdevImp, meanImp, "Divide create 32-bit stack");
+IJ.run(covImp, "Enhance Contrast", "saturated=0.35");
+IJ.run(covImp, "8-bit", ""); // otherwise the thresholding does not seem to work
+covImp.setTitle(datasetId + " cov" )
+if (!headless) covImp.duplicate().show();
+
+// create binary image (cell-free regions are foreground)
+//
+println("Creating binary image of cell-free regions...")
+// configure black background
+IJ.run("Options...", "iterations=1 count=1 black");
+// determine threshold in first frame, because there we are
+// closest to a 50/50 occupancy of the image with signal,
+// which is best for most auto-thresholding algorithms
+covImp.setPosition(1)
+def histogram = covImp.getProcessor().getHistogram()
+// Auto threshold with Huang method and
+// multiply the threshold with a fixed factor (as is done in CellProfiler),
+// based on the observation that the threshold is consistently
+// a bit too high, which may be due to
+// the fact that the majority of the image is foreground
+if ( threshold == -1 )
+  threshold = Auto_Threshold.Huang(histogram) * 0.8
+println("Threshold: " + threshold)
+// create binary image of whole movie,
+// using the threshold of the first image
+// defining the cell free regions as foreground
+def binaryImp = (ImagePlus) Threshold.threshold(covImp, 0.0, threshold)
+// dilate the cell free regions, because due to the cell filter radius
+// the cell sizes are over estimated (blurred into cell free regions)
+binaryImp = Morphology.dilation(binaryImp, SquareStrel.fromRadius((int) cellFilterRadius))
+binaryImp.setTitle(datasetId + " binary")
+if(!headless) binaryImp.duplicate().show()
+
+// create scratch ROIs
+//
+println("Creating scratch ROI...")
+binaryImp.setPosition(1) // scratch is most visible in first frame
+def scratchIp = binaryImp.crop("whole-slice").getProcessor().duplicate();
+// identify largest cell free region as scratch region
+scratchIp = BinaryImages.keepLargestRegion(scratchIp)
+// remove cells inside scratch region
+scratchIp = Reconstruction.fillHoles(scratchIp)
+if(!headless) new ImagePlus("Scratch", scratchIp.duplicate()).show()
+// disconnect from cell free regions outside scratch
+scratchIp = Morphology.opening(scratchIp, SquareStrel.fromRadius((int)(scratchFilterRadius/20)))
+// in case the morphological opening cut off some cell free
+// areas outside the scratch we again only keep the largest region
+scratchIp = BinaryImages.keepLargestRegion(scratchIp)
+// smoothen scratch edges
+scratchIp = Morphology.closing(scratchIp, SquareStrel.fromRadius((int)(scratchFilterRadius/2)))
+
+// convert binary image to ROI, which is handy for measurements
+def scratchImp = new ImagePlus("Finale scratch", scratchIp)
+if(!headless) scratchImp.show()
+IJ.run(scratchImp, "Create Selection", "");
+def scratchROI = scratchImp.getRoi()
+
+// measure occupancy of scratch ROI
+// `area_fraction` returns the fraction of foreground pixels
+// (cell free area) within the measurement ROI
+println("Performing measurements...")
+IJ.run("Set Measurements...", "area bounding area_fraction redirect=None decimal=2");
+def rt = RoiManager.multiMeasure(binaryImp, new Roi[]{scratchROI}, false)
+
+// show results
+//
+if ( !headless ) {
+    rt.show("Results")
+    binnedImp.show()
+    binnedImp.setTitle(datasetId + " binned")
+    binnedImp.setRoi(scratchROI, true)
+    binaryImp.setPosition(1)
+    binaryImp.show()
+    binaryImp.setTitle(datasetId + " binary")
+    binaryImp.setRoi(scratchROI, true)
+}
+
+// save results
+//
+if ( saveResults ) {
+    // create output directory next to input directory
+    def outputDir = new File(inputDir.getParent(), outDirName);
+    println("Ensuring existence of output directory: " + outputDir)
+    outputDir.mkdir()
+    // save table
+    rt.save(new File(outputDir, datasetId + ".csv").toString());
+    // save binned image with ROI
+    binnedImp.setRoi(scratchROI, false)
+    IJ.save(binnedImp, new File(outputDir, datasetId + ".tif").toString());
+}
+
+println("Analysis of "+datasetId+" is done!")
+if ( headless ) System.exit(0)
+
+// FUNCTIONS
+//
+
+// copied from ImageJ RoiManager because
+// https://forum.image.sc/t/make-multimeasure-public-in-roimanager/69273
+private static ResultsTable multiMeasure(ImagePlus imp, Roi[] rois) {
+    int nSlices = imp.getStackSize();
+    Analyzer aSys = new Analyzer(imp); // System Analyzer
+    ResultsTable rtSys = Analyzer.getResultsTable();
+    ResultsTable rtMulti = new ResultsTable();
+    rtMulti.showRowNumbers(true);
+    rtSys.reset();
+    int currentSlice = imp.getCurrentSlice();
+    for (int slice=1; slice<=nSlices; slice++) {
+        int sliceUse = slice;
+        if (nSlices==1) sliceUse = currentSlice;
+        imp.setSliceWithoutUpdate(sliceUse);
+        rtMulti.incrementCounter();
+        if ((Analyzer.getMeasurements()& Measurements.LABELS)!=0)
+            rtMulti.addLabel("Label", imp.getTitle());
+        int roiIndex = 0;
+        for (int i=0; i<rois.length; i++) {
+            imp.setRoi(rois[i]);
+            roiIndex++;
+            aSys.measure();
+            for (int j=0; j<=rtSys.getLastColumn(); j++){
+                float[] col = rtSys.getColumn(j);
+                String head = rtSys.getColumnHeading(j);
+                String suffix = ""+roiIndex;
+                Roi roi = imp.getRoi();
+                if (roi!=null) {
+                    String name = roi.getName();
+                    if (name!=null && name.length()>0 && (name.length()<9||!Character.isDigit(name.charAt(0))))
+                        suffix = "("+name+")";
+                }
+                if (head!=null && col!=null && !head.equals("Slice"))
+                    rtMulti.addValue(head+suffix, rtSys.getValue(j,rtSys.getCounter()-1));
+            }
+        }
+    }
+    return rtMulti;
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test-data/A3ROI2_Slow.csv	Thu Feb 29 17:43:50 2024 +0000
@@ -0,0 +1,3 @@
+ ,Area1,BX1,BY1,Width1,Height1,%Area1
+1,221779,299,0,333,980,90.880
+2,221779,299,0,333,980,80.706
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/woundhealing.xml	Thu Feb 29 17:43:50 2024 +0000
@@ -0,0 +1,61 @@
+<tool id="woundhealing_scratch_assay" name="Wound healing scratch assay" version="@VERSION@+galaxy0" profile="23.1">
+    <description>image analysis</description>
+    <macros>
+        <import>macros.xml</import>
+    </macros>
+    <expand macro="requirements" />
+    <command detect_errors="aggressive">
+    <![CDATA[
+    mkdir -p ./input &&
+
+    #if $con_input_type.input_type =="yes"
+        tar -C ./input -xvf $con_input_type.input_images &&
+    #else
+        #for $i, $filename in enumerate($con_input_type.input_images):
+            ln -s '$filename' './input/${filename.element_identifier}' &&
+        #end for
+    #end if
+    ImageJ --ij2 --headless 
+    --run '$__tool_directory__/measureWoundClosing.groovy'
+    'inputDir="./input",datasetId="$dataset_id",threshold="$threshold",headless="true",saveResults="true",outDirName="./output"'
+    ]]>
+    </command>
+    <inputs>
+        <conditional name="con_input_type">
+            <param name="input_type" type="select" label="Are the input images packed into a tar archive?">
+                <option value="yes">Yes</option>
+                <option value="no">No</option>
+            </param> 
+            <when value="no">
+                <param name="input_images" type="data" multiple="true" format="@FORMATS@" label="Images" />
+            </when>
+            <when value="yes">
+                <param name="input_images" type="data" format="tar" label="A tarball of images" />
+            </when>
+        </conditional>
+        <param name="dataset_id" type="text" label="Dataset ID" />
+        <param name="threshold"  type="text" label="CoV threshold (-1: auto)" value="-1"/>
+    </inputs>
+    <outputs>
+        <data name="tif_output" format="tif" from_work_dir="output/*.tif" label="movie output ${on_string}" />
+	<data name="csv_output" format="tabular" from_work_dir="output/*.csv" label="tabular output"/>
+    </outputs>
+    <tests>
+        <test >
+            <conditional name="con_input_type">
+                <param name="input_type" value="no" />
+                <param name="input_images" location="https://github.com/sunyi000/wound-healing-htm-screen/raw/main/assets/data/input/A3ROI2_Slow_0000.tif,https://github.com/sunyi000/wound-healing-htm-screen/raw/main/assets/data/input/A3ROI2_Slow_0002.tif" />
+	    </conditional>
+	    <param name="dataset_id" value="A3ROI2_Slow" />
+	    <output name="tif_output" location="https://github.com/sunyi000/wound-healing-htm-screen/raw/main/assets/data/A3ROI2_Slow.tif"/>
+	    <output name="csv_output" file="A3ROI2_Slow.csv"/>
+        </test>
+    </tests>
+    <help>
+        <![CDATA[
+            **What it does**
+            Automated quantification of wound healing in high-throughput time-lapse transmission microscopy scratch assays.
+        ]]>
+    </help>
+    <expand macro="citations" />
+</tool>