Commit f99ade4e authored by Karel Štěpka's avatar Karel Štěpka
Browse files

Added gapEvolutionTolerance parameter that controls what growth of the gap is...

Added gapEvolutionTolerance parameter that controls what growth of the gap is allowed. If 0.0, no growth is permitted at all (once a pixel is labeled as non-gap, it will always remain non-gap in the future frames); if negative, tolerance is unlimited (any pixel can be labeled as gap regardless of its history); if >0, a pixel can be labeled as gap only in the first frame, or if sufficiently close to a gap in the previous frame.
parent eb587286
Loading
Loading
Loading
Loading
+98 −19
Original line number Diff line number Diff line
@@ -3,20 +3,32 @@ Common testing images:

42_1_t0.JPG
42_1_t24.JPG
driveLetter = "d";
driveLetter = "h";

open("D:/Scratch Assay/Images/2025-02-25 KER/42_1_t0.JPG");
open("D:/Scratch Assay/Images/2025-02-25 KER/42_1_t24.JPG");

open("d:/Scratch Assay/Images/5.2.2025 fibroblasty/mit1_t0.JPG");
open("d:/Scratch Assay/Images/5.2.2025 fibroblasty/mit1_t24.JPG");
open("d:/Scratch Assay/Images/5.2.2025 fibroblasty/mit1_t48.JPG");

open("d:/Scratch Assay/Images/2025-03-05 KER/25_1_t48.JPG");
open("d:/Scratch Assay/Images/2025-03-05 KER/25_1_t5.JPG");
open("d:/Scratch Assay/Images/2025-03-05 KER/25_1_t0.JPG");
open("d:/Scratch Assay/Images/2025-03-05 KER/25_1_t24.JPG");
driveLetter = "h";
open(driveLetter + ":/Scratch Assay/Images/2025-02-25 KER/42_1_t0.JPG");
open(driveLetter + ":/Scratch Assay/Images/2025-02-25 KER/42_1_t24.JPG");

driveLetter = "h";
open(driveLetter + ":/Scratch Assay/Images/5.2.2025 fibroblasty/mit1_t0.JPG");
open(driveLetter + ":/Scratch Assay/Images/5.2.2025 fibroblasty/mit1_t24.JPG");
open(driveLetter + ":/Scratch Assay/Images/5.2.2025 fibroblasty/mit1_t48.JPG");

driveLetter = "h";
open(driveLetter + ":/Scratch Assay/Images/2025-03-05 KER/25_1_t48.JPG");
open(driveLetter + ":/Scratch Assay/Images/2025-03-05 KER/25_1_t5.JPG");
open(driveLetter + ":/Scratch Assay/Images/2025-03-05 KER/25_1_t0.JPG");
open(driveLetter + ":/Scratch Assay/Images/2025-03-05 KER/25_1_t24.JPG");


driveLetter = "h";
open(driveLetter + ":/Scratch Assay/Images/2025-03-05 KER/35_1_t48.JPG");
open(driveLetter + ":/Scratch Assay/Images/2025-03-05 KER/35_1_t5.JPG");
open(driveLetter + ":/Scratch Assay/Images/2025-03-05 KER/35_1_t0.JPG");
open(driveLetter + ":/Scratch Assay/Images/2025-03-05 KER/35_1_t24.JPG");


*/
@@ -25,9 +37,8 @@ open("d:/Scratch Assay/Images/2025-03-05 KER/25_1_t24.JPG");

TODO: (General TODO notes.)

In the first image of a series, try to pick only the one largest component (assume the gap is unbroken in the first image).

In each subsequent image, try intersection of empty regions from that image with a dilated version of the empty regions from the previous image (dilation radius as a parameter linked to cell size).
maybe DONE: In the first image of a series, try to pick only the one largest component (assume the gap is unbroken in the first image).
maybe DONE: In each subsequent image, try intersection of empty regions from that image with a dilated version of the empty regions from the previous image (dilation radius as gapEvolutionTolerance * sizeParameter, but still limited by the rectangle).

*/

@@ -56,8 +67,10 @@ doScalingDuringRegistration = false; // When registering ROIs between different
defaultFiducialPlacementOption = 1;  // 0 if the fiducials are placed along the gap. 1 if they are placed to the sides of it.
defaultGapOrientationOption = 0;  // 0 if the gap is vertical. 1 if the gap is horizontal. 2 if the gap orientation is unknown or varies from series to series.
fiducialBlurFactor = 1/46;  // The higher fiducialBlurFactor is, the more blurred the fiducial detection image will be. (Gaussian blur sigma will be equal to image width * fiducialBlurFactor.)

defaultContrastEnhancementPercentage = 20;
defaultSizeParameter = 15;
defaultGapEvolutionTolerance = -1; // Negative for infinite tolerance (gap can grow by any amount between frames). Zero for no growth tolerance (gap can only ever shrink). Positive for some tolerance (only pixels within the gapEvolutionTolerance distance from the previous frame's gap can be labeled as the gap now).

selectionColor = "red";

@@ -85,6 +98,8 @@ minContrastEnhancementPercentage = 0;
maxContrastEnhancementPercentage = 90;
minSizeParameter = 1;
maxSizeParameter = 50;
minGapEvolutionTolerance = -1;
maxGapEvolutionTolerance = 20;

////////////////////////////////////////////////////////////////////////////////

@@ -259,6 +274,9 @@ function createSetupDialog() {
	Dialog.setInsets(0, 10, 0);
	Dialog.addNumber("Size parameter (" + minSizeParameter + ".." + maxSizeParameter + ", default " + defaultSizeParameter + "):", defaultSizeParameter, 1, 5, "");
		
	Dialog.setInsets(0, 10, 0);
	Dialog.addNumber("Gap evolution tolerance (" + minGapEvolutionTolerance + ".." + maxGapEvolutionTolerance + ", default " + defaultGapEvolutionTolerance + "):", defaultGapEvolutionTolerance, 1, 5, "");
		
	Dialog.setInsets(10, 0, 0);
	Dialog.addMessage("------------------------------------------------------------------------------------------------------------------------------------------------");
	
@@ -339,6 +357,10 @@ function readSettingsFromSetupDialog() {
	sizeParameter = Math.max(Math.min(Dialog.getNumber(), maxSizeParameter), minSizeParameter);
	print("sizeParameter: " + sizeParameter);

	// Read the gap evolution tolerance.
	gapEvolutionTolerance = Math.max(Math.min(Dialog.getNumber(), maxGapEvolutionTolerance), minGapEvolutionTolerance);
	print("gapEvolutionTolerance: " + gapEvolutionTolerance);

	// Read the debugging options.
	useBatchModeForComputations = Dialog.getCheckbox();

@@ -439,6 +461,8 @@ function getImageNameForStage(imageIndex, stage) {
		result = intermediateDebugImagePrefix + "Temp_for_Thresholding" + "_" + imagesToAnalyze[imageIndex];
	} else if (stage == "temp_for_plot") {
		result = intermediateDebugImagePrefix + "Temp_for_Plot" + "_" + imagesToAnalyze[imageIndex];
	} else if (stage == "temp_for_gap_tolerance") {
		result = intermediateDebugImagePrefix + "Temp_for_Gap_Tolerance" + "_" + imagesToAnalyze[imageIndex];
	} else if (stage == "gap_mask") {
		result = intermediateDebugImagePrefix + "Gap_Mask" + "_" + imagesToAnalyze[imageIndex];
	} else if (stage == "final_gap_mask") {
@@ -448,7 +472,7 @@ function getImageNameForStage(imageIndex, stage) {
	} else {  // Otherwise, assume the "rgb_input" stage.
		result = imagesToAnalyze[imageIndex];
	}
print("getting image name: \"" + result + "\"");  // Debugging output.
//print("getting image name: \"" + result + "\"");  // Debugging output.
	return result;
}

@@ -791,7 +815,7 @@ function findEmptyRegions(imageIndex, emptyRegionThreshold) {
	}

	// Remove small components.
	run("Area Opening", "pixel=15000");  // TODO: The area parameter of this area opening should be related to the actual cell diameter in the image.
	run("Area Opening", "pixel=15000");  // TODO: The area parameter of this area opening should be related to the actual cell diameter in the image (sizeParameter), and possibly to a modifiable parameter.
	rename(getImageNameForStage(imageIndex, "final_gap_mask"));
	
	// Convert the mask into a selection and store it as the  getImageNameForStage(imageIndex, "rgb_input") + " Empty Regions" ROI, if it is not empty.
@@ -938,13 +962,66 @@ print("selected ROI before plotting of horizontal line profile: \"" + Roi.getNam


function limitEmptyRegionsByIntersectedInterfiducialRectangle(imageIndex) {
	// Select all the empty regions in this image, and the registered rectangle covered in all images.
	// Limit the possible gap growth based on the gap in the previous frame and the user-specified parameters.
	previousImageLimitingROIIndex = -1;
	// If the gap evolution tolerance is not unlimited (indicated by a negative value), and this is not the first frame, use the empty regions from the previous frame to limit those in the current one.
	if ((gapEvolutionTolerance >= 0) && (imageIndex > 0)) {
		// Register empty regions from the previous frame to this one.
		
		// Find the transform registering fiducials from imagesToAnalyze[imageIndex - 1] to imagesToAnalyze[imageIndex].
		RoiManager.selectByName(getImageNameForStage(imageIndex, "rgb_input") + " Fiducials");	
		Roi.getContainedPoints(xFixed, yFixed);		
		RoiManager.selectByName(getImageNameForStage(imageIndex - 1, "rgb_input") + " Fiducials");	
		Roi.getContainedPoints(xMoving, yMoving);
		transformValues = findRegisteringTransform(xFixed, yFixed, xMoving, yMoving);
		
		// Create a ROI in imagesToAnalyze[imageIndex] which corresponds to the empty regions between the fiducials in imagesToAnalyze[imageIndex - 1].
		previousImageEmptyRegionsROIIndex = RoiManager.getIndex(getImageNameForStage(imageIndex - 1, "rgb_input") + " Empty Regions Between Fiducials");
		if (previousImageEmptyRegionsROIIndex > -1) {
			RoiManager.select(previousImageEmptyRegionsROIIndex);
			setSelectionName(getImageNameForStage(imageIndex, "rgb_input") + " Empty Regions From " + getImageNameForStage(imageIndex - 1, "rgb_input"));
			roiManager("Add");
			RoiManager.selectByName(getImageNameForStage(imageIndex, "rgb_input") + " Empty Regions From " + getImageNameForStage(imageIndex - 1, "rgb_input"));
			RoiManager.rotate(transformValues[2] / PI * 180);
			if (doScalingDuringRegistration) {
				RoiManager.scale(transformValues[3], transformValues[3], true);
			}
			RoiManager.translate(transformValues[0], transformValues[1]);
			RoiManager.selectByName(getImageNameForStage(imageIndex, "rgb_input") + " Empty Regions From " + getImageNameForStage(imageIndex - 1, "rgb_input"));
	
			// Dilate the registered ROI from the previous frame. 
			run("Create Mask");
			rename(getImageNameForStage(imageIndex, "temp_for_gap_tolerance"));
			run("Morphological Filters", "operation=Dilation element=Disk radius=" + sizeParameter * gapEvolutionTolerance);
			rename(getImageNameForStage(imageIndex, "temp_for_gap_tolerance") + "_Dilated");
			close(getImageNameForStage(imageIndex, "temp_for_gap_tolerance"));
			run("Create Selection");
			setSelectionName(getImageNameForStage(imageIndex, "rgb_input") + " Empty Regions From " + getImageNameForStage(imageIndex - 1, "rgb_input") + " With Tolerance");
			roiManager("Add");
			close(getImageNameForStage(imageIndex, "temp_for_gap_tolerance") + "_Dilated");
			
			previousImageLimitingROIIndex = RoiManager.getIndex(getImageNameForStage(imageIndex, "rgb_input") + " Empty Regions From " + getImageNameForStage(imageIndex - 1, "rgb_input") + " With Tolerance");
		}
	}

	
	
	// Select all the empty regions in this image, and the registered rectangle covered in all images ("the common FOV").
	currentImageEmptyRegionsROIIndex = RoiManager.getIndex(getImageNameForStage(imageIndex, "rgb_input") + " Empty Regions");
	currentImageLimitingROIIndex = RoiManager.getIndex(getImageNameForStage(imageIndex, "rgb_input") + " Intersected Rectangles Between Fiducials");
	
	// If both such ROIs exist, compute their intersection.
	if ((currentImageEmptyRegionsROIIndex > -1) && (currentImageLimitingROIIndex > -1)) {
	// If the ROIs exist, compute their intersection.
	if (   (currentImageEmptyRegionsROIIndex > -1)                                                     // If the current frame has any regions that seem empty...
	    && (currentImageLimitingROIIndex > -1)                                                         // ...and we have the common FOV...
	    && ((imageIndex == 0) || (gapEvolutionTolerance < 0) || (previousImageLimitingROIIndex > -1))  // ...and either this is the first frame, or we have unlimited gap evolution tolerance, or we have the empty regions from the previous frame...
	    ) {                                                                                            // ...then compute the intersection of the relevant regions.
		if (previousImageLimitingROIIndex > -1) {
			// If we have a limited tolerance mask from the previous frame, apply it too (i.e., do not label pixels as part of a gap, unless they are sufficiently close to where the gap was in the previous frame).
			roiManager("select", newArray(currentImageEmptyRegionsROIIndex, currentImageLimitingROIIndex, previousImageLimitingROIIndex));
		} else {
			// If the tolerance is unlimited (i.e., new gap pixels are allowed to appear anywhere), just use intersection with the common FOV.
			roiManager("select", newArray(currentImageEmptyRegionsROIIndex, currentImageLimitingROIIndex));
		}
		roiManager("AND");  // TODO: BUG? It seems that intersecting some regions under some circumstances can yield shifted results. Test if this can happen here, and if it influences the results.
		if (selectionType > -1) {
			setSelectionName(getImageNameForStage(imageIndex, "rgb_input") + " Empty Regions Between Fiducials");
@@ -1194,8 +1271,10 @@ var imagesToAnalyze = newArray();

var fiducialsAreAlongTheGap;  // True if the fiducials are placed along the gap. False if they are placed to the sides of it.
var gapOrientation;

var contrastEnhancementPercentage;
var sizeParameter;
var gapEvolutionTolerance;

var closeIntermediateImages;
var useBatchModeForComputations;