comparison measureWoundClosing.groovy @ 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
comparison
equal deleted inserted replaced
-1:000000000000 0:8948cc562b7c
1 /**
2 * This script runs in Fiji
3 *
4 * It needs the following Fiji update sites:
5 * - IJPB-Plugins (MorpholibJ)
6 */
7
8
9 import fiji.threshold.Auto_Threshold
10 import ij.IJ
11 import ij.ImagePlus
12 import ij.gui.Roi
13 import ij.measure.Measurements
14 import ij.measure.ResultsTable
15 import ij.plugin.FolderOpener
16 import ij.plugin.ImageCalculator
17 import ij.plugin.filter.Analyzer
18 import ij.plugin.frame.RoiManager
19 import inra.ijpb.binary.BinaryImages
20 import inra.ijpb.morphology.Morphology
21 import inra.ijpb.morphology.Reconstruction
22 import inra.ijpb.morphology.strel.SquareStrel
23 import inra.ijpb.segment.Threshold
24 import net.imagej.ImageJ
25
26 // INPUT UI AND CLI
27 //
28 #@ File (label="Input directory", style="directory") inputDir
29 #@ String (label="Dataset ID") datasetId
30 #@ Double (label="CoV threshold (-1: auto)", default="-1") threshold
31 #@ Boolean (label="Run headless", default="false") headless
32 #@ Boolean (label="Save results", default="true") saveResults
33 #@ String (label="Output directory name (will be created next to input directory)", default="analysis") outDirName
34
35 // INPUT FOR TESTING WITHIN IDE
36 //
37 //def inputDir = new File("/Users/tischer/Documents/wound-healing-htm-screen/data/input")
38 //def datasetId = "A3ROI2_Slow"; // C4ROI1_Fast A3ROI2_Slow
39 //def outDirName = "analysis"
40 //def threshold = (double) -1.0 // auto
41 //def headless = false;
42 //def saveResults = false;
43 //new ij.ImageJ().setVisible(true)
44
45 // FIXED PARAMETERS
46 //
47 def cellDiameter = 20
48 def scratchDiameter = 500
49 def binningFactor = 2
50
51 // DERIVED PARAMETERS
52 //
53 def cellFilterRadius = cellDiameter/binningFactor
54 def scratchFilterRadius = scratchDiameter/binningFactor
55
56 println("Cell filter radius: " + cellFilterRadius)
57 println("Scratch filter radius: " + scratchFilterRadius)
58
59 // CODE
60 //
61
62 // open the images
63 //
64 IJ.run("Close All");
65 println("Opening: " + datasetId)
66 def imp = FolderOpener.open(inputDir.toString(), " filter=(.*"+datasetId+".*)")
67 println("Number of slices: " + imp.getNSlices())
68
69 if ( imp == null || imp.getNSlices() == 0 ) {
70 println("Could not find any files matching the pattern!")
71 System.exit(1)
72 }
73
74 // process the images to enhance regions with cells
75 // using the fact the the cell regions have a higher local variance
76 //
77 println("Process images to enhance regions containing cells...")
78 // remove scaling to work in pixel units
79 IJ.run(imp,"Properties...", "pixel_width=1 pixel_height=1 voxel_depth=1");
80 // bin to save compute time
81 IJ.run(imp, "Bin...", "x=" + binningFactor + " y=" + binningFactor + " z=1 bin=Average");
82 def binnedImp = imp.duplicate() // keep for saving
83 // enhance cells
84 IJ.run(imp, "32-bit", "");
85 def sdevImp = imp.duplicate()
86 sdevImp.setTitle(datasetId + " sdev" )
87 IJ.run(sdevImp, "Find Edges", "stack"); // removes larger structures, such as dirt in the background
88 IJ.run(sdevImp, "Variance...", "radius=" + cellFilterRadius + " stack");
89 IJ.run(sdevImp, "Square Root", "stack");
90 // mean
91 def meanImp = imp.duplicate()
92 meanImp.setTitle(datasetId + " mean")
93 IJ.run(meanImp, "Mean...", "radius=" + cellFilterRadius + " stack");
94 // cov
95 def covImp = ImageCalculator.run(sdevImp, meanImp, "Divide create 32-bit stack");
96 IJ.run(covImp, "Enhance Contrast", "saturated=0.35");
97 IJ.run(covImp, "8-bit", ""); // otherwise the thresholding does not seem to work
98 covImp.setTitle(datasetId + " cov" )
99 if (!headless) covImp.duplicate().show();
100
101 // create binary image (cell-free regions are foreground)
102 //
103 println("Creating binary image of cell-free regions...")
104 // configure black background
105 IJ.run("Options...", "iterations=1 count=1 black");
106 // determine threshold in first frame, because there we are
107 // closest to a 50/50 occupancy of the image with signal,
108 // which is best for most auto-thresholding algorithms
109 covImp.setPosition(1)
110 def histogram = covImp.getProcessor().getHistogram()
111 // Auto threshold with Huang method and
112 // multiply the threshold with a fixed factor (as is done in CellProfiler),
113 // based on the observation that the threshold is consistently
114 // a bit too high, which may be due to
115 // the fact that the majority of the image is foreground
116 if ( threshold == -1 )
117 threshold = Auto_Threshold.Huang(histogram) * 0.8
118 println("Threshold: " + threshold)
119 // create binary image of whole movie,
120 // using the threshold of the first image
121 // defining the cell free regions as foreground
122 def binaryImp = (ImagePlus) Threshold.threshold(covImp, 0.0, threshold)
123 // dilate the cell free regions, because due to the cell filter radius
124 // the cell sizes are over estimated (blurred into cell free regions)
125 binaryImp = Morphology.dilation(binaryImp, SquareStrel.fromRadius((int) cellFilterRadius))
126 binaryImp.setTitle(datasetId + " binary")
127 if(!headless) binaryImp.duplicate().show()
128
129 // create scratch ROIs
130 //
131 println("Creating scratch ROI...")
132 binaryImp.setPosition(1) // scratch is most visible in first frame
133 def scratchIp = binaryImp.crop("whole-slice").getProcessor().duplicate();
134 // identify largest cell free region as scratch region
135 scratchIp = BinaryImages.keepLargestRegion(scratchIp)
136 // remove cells inside scratch region
137 scratchIp = Reconstruction.fillHoles(scratchIp)
138 if(!headless) new ImagePlus("Scratch", scratchIp.duplicate()).show()
139 // disconnect from cell free regions outside scratch
140 scratchIp = Morphology.opening(scratchIp, SquareStrel.fromRadius((int)(scratchFilterRadius/20)))
141 // in case the morphological opening cut off some cell free
142 // areas outside the scratch we again only keep the largest region
143 scratchIp = BinaryImages.keepLargestRegion(scratchIp)
144 // smoothen scratch edges
145 scratchIp = Morphology.closing(scratchIp, SquareStrel.fromRadius((int)(scratchFilterRadius/2)))
146
147 // convert binary image to ROI, which is handy for measurements
148 def scratchImp = new ImagePlus("Finale scratch", scratchIp)
149 if(!headless) scratchImp.show()
150 IJ.run(scratchImp, "Create Selection", "");
151 def scratchROI = scratchImp.getRoi()
152
153 // measure occupancy of scratch ROI
154 // `area_fraction` returns the fraction of foreground pixels
155 // (cell free area) within the measurement ROI
156 println("Performing measurements...")
157 IJ.run("Set Measurements...", "area bounding area_fraction redirect=None decimal=2");
158 def rt = RoiManager.multiMeasure(binaryImp, new Roi[]{scratchROI}, false)
159
160 // show results
161 //
162 if ( !headless ) {
163 rt.show("Results")
164 binnedImp.show()
165 binnedImp.setTitle(datasetId + " binned")
166 binnedImp.setRoi(scratchROI, true)
167 binaryImp.setPosition(1)
168 binaryImp.show()
169 binaryImp.setTitle(datasetId + " binary")
170 binaryImp.setRoi(scratchROI, true)
171 }
172
173 // save results
174 //
175 if ( saveResults ) {
176 // create output directory next to input directory
177 def outputDir = new File(inputDir.getParent(), outDirName);
178 println("Ensuring existence of output directory: " + outputDir)
179 outputDir.mkdir()
180 // save table
181 rt.save(new File(outputDir, datasetId + ".csv").toString());
182 // save binned image with ROI
183 binnedImp.setRoi(scratchROI, false)
184 IJ.save(binnedImp, new File(outputDir, datasetId + ".tif").toString());
185 }
186
187 println("Analysis of "+datasetId+" is done!")
188 if ( headless ) System.exit(0)
189
190 // FUNCTIONS
191 //
192
193 // copied from ImageJ RoiManager because
194 // https://forum.image.sc/t/make-multimeasure-public-in-roimanager/69273
195 private static ResultsTable multiMeasure(ImagePlus imp, Roi[] rois) {
196 int nSlices = imp.getStackSize();
197 Analyzer aSys = new Analyzer(imp); // System Analyzer
198 ResultsTable rtSys = Analyzer.getResultsTable();
199 ResultsTable rtMulti = new ResultsTable();
200 rtMulti.showRowNumbers(true);
201 rtSys.reset();
202 int currentSlice = imp.getCurrentSlice();
203 for (int slice=1; slice<=nSlices; slice++) {
204 int sliceUse = slice;
205 if (nSlices==1) sliceUse = currentSlice;
206 imp.setSliceWithoutUpdate(sliceUse);
207 rtMulti.incrementCounter();
208 if ((Analyzer.getMeasurements()& Measurements.LABELS)!=0)
209 rtMulti.addLabel("Label", imp.getTitle());
210 int roiIndex = 0;
211 for (int i=0; i<rois.length; i++) {
212 imp.setRoi(rois[i]);
213 roiIndex++;
214 aSys.measure();
215 for (int j=0; j<=rtSys.getLastColumn(); j++){
216 float[] col = rtSys.getColumn(j);
217 String head = rtSys.getColumnHeading(j);
218 String suffix = ""+roiIndex;
219 Roi roi = imp.getRoi();
220 if (roi!=null) {
221 String name = roi.getName();
222 if (name!=null && name.length()>0 && (name.length()<9||!Character.isDigit(name.charAt(0))))
223 suffix = "("+name+")";
224 }
225 if (head!=null && col!=null && !head.equals("Slice"))
226 rtMulti.addValue(head+suffix, rtSys.getValue(j,rtSys.getCounter()-1));
227 }
228 }
229 }
230 return rtMulti;
231 }
232