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