Mercurial > repos > bgruening > woundhealing_scratch_assay
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 |