requires("1.54s"); macro "Batch_DAPI_C1_Micronuclei_FixedThreshold_40_255_Area3_30_Circ0p5_NoOverlapMainNuclei" { // ========================================================= // DEFAULT PARAMETERS // ========================================================= rollingBallNuc = 15; gaussianSigmaNuc = 0.6; // fixed threshold minThresholdNuc = 50; maxThresholdNuc = 255; // micronuclei filters minMicronucleusArea = 2; maxMicronucleusArea = 30; minMicronucleusCirc = 0.3; excludeEdges = true; saveOverlay = true; fontSizeLabel = 16; // ========================================================= // SELECT FOLDERS // ========================================================= inputDir = getDirectory("Scegli la cartella con le immagini C1"); if (inputDir == "") exit("Nessuna cartella input."); mainRoiDir = getDirectory("Scegli la cartella con le ROI dei nuclei principali"); if (mainRoiDir == "") exit("Nessuna cartella ROI nuclei principali."); outputDir = getDirectory("Scegli la cartella dove salvare i risultati"); if (outputDir == "") exit("Nessuna cartella output."); if (!endsWith(inputDir, "/")) inputDir = inputDir + "/"; if (!endsWith(mainRoiDir, "/")) mainRoiDir = mainRoiDir + "/"; if (!endsWith(outputDir, "/")) outputDir = outputDir + "/"; fileList = getFileList(inputDir); // ========================================================= // PARAMETER DIALOG // ========================================================= Dialog.create("Parametri micronuclei batch"); Dialog.addNumber("Rolling ball", rollingBallNuc); Dialog.addNumber("Gaussian sigma", gaussianSigmaNuc); Dialog.addNumber("Min threshold fixed (8-bit)", minThresholdNuc); Dialog.addNumber("Max threshold fixed (8-bit)", maxThresholdNuc); Dialog.addNumber("Min micronucleus area", minMicronucleusArea); Dialog.addNumber("Max micronucleus area", maxMicronucleusArea); Dialog.addNumber("Min micronucleus circularity", minMicronucleusCirc); Dialog.addCheckbox("Exclude edge particles", excludeEdges); Dialog.addCheckbox("Save overlay", saveOverlay); Dialog.addNumber("Overlay font size", fontSizeLabel); Dialog.show(); rollingBallNuc = Dialog.getNumber(); gaussianSigmaNuc = Dialog.getNumber(); minThresholdNuc = Dialog.getNumber(); maxThresholdNuc = Dialog.getNumber(); minMicronucleusArea = Dialog.getNumber(); maxMicronucleusArea = Dialog.getNumber(); minMicronucleusCirc = Dialog.getNumber(); excludeEdges = Dialog.getCheckbox(); saveOverlay = Dialog.getCheckbox(); fontSizeLabel = Dialog.getNumber(); // ========================================================= // BATCH FILES // ========================================================= batchSummaryCsv = outputDir + "Batch_Micronuclei_Report.csv"; batchAllCsv = outputDir + "Batch_Micronuclei_All.csv"; batchSummaryHeader = "Sample_ID," + "Main_nuclei_count," + "Micronuclei_count," + "Micronuclei_per_main_nucleus," + "Mean_area," + "SD_area," + "Min_area," + "Max_area," + "Total_area," + "Threshold_mode," + "Min_threshold_fixed," + "Max_threshold_fixed," + "Min_area_filter," + "Max_area_filter," + "Min_circularity_filter," + "Exclude_edges\n"; File.saveString(batchSummaryHeader, batchSummaryCsv); batchAllHeader = "Sample_ID," + "Micronucleus_ID," + "Area," + "X," + "Y\n"; File.saveString(batchAllHeader, batchAllCsv); // ========================================================= // LOOP FILES // ========================================================= for (f = 0; f < fileList.length; f++) { name = fileList[f]; path = inputDir + name; if (File.isDirectory(path)) continue; if (!(endsWith(name, "C1.tif") || endsWith(name, "C1.tiff") || endsWith(name, "C1.TIF") || endsWith(name, "C1.TIFF"))) continue; open(path); originalTitle = getTitle(); run("Select None"); roiManager("Reset"); // ----------------------------------------------------- // BASE NAME // ----------------------------------------------------- baseName = originalTitle; if (endsWith(baseName, ".tif") || endsWith(baseName, ".TIF")) baseName = substring(baseName, 0, lengthOf(baseName) - 4); else if (endsWith(baseName, ".tiff") || endsWith(baseName, ".TIFF")) baseName = substring(baseName, 0, lengthOf(baseName) - 5); mainRoiZip = mainRoiDir + baseName + "_Nuclei_ROIs.zip"; if (!File.exists(mainRoiZip)) { print("ROI nuclei principali non trovate per: " + baseName); close(); continue; } roiZip = outputDir + baseName + "_Micronuclei_ROIs.zip"; areaCsv = outputDir + baseName + "_Micronuclei_Area.csv"; summaryCsv = outputDir + baseName + "_Micronuclei_Summary.csv"; overlayTif = outputDir + baseName + "_Micronuclei_Overlay.tif"; // ===================================================== // PREP IMAGE // ===================================================== Stack.getDimensions(w, h, c, z, t); if (z > 1) { run("Z Project...", "projection=[Max Intensity]"); rename("DAPI_MAX"); selectWindow(originalTitle); close(); } else { run("Duplicate...", "title=DAPI_MAX"); selectWindow(originalTitle); close(); } selectWindow("DAPI_MAX"); run("Select None"); run("Duplicate...", "title=DAPI_FOR_OVERLAY"); // ===================================================== // LOAD MAIN NUCLEI ROIs // ===================================================== roiManager("Reset"); roiManager("Open", mainRoiZip); mainNucleiCount = roiManager("count"); if (mainNucleiCount == 0) { print("Nessuna ROI nucleo principale in: " + mainRoiZip); close("*"); roiManager("Reset"); continue; } // ===================================================== // SEGMENT MICRONUCLEI CANDIDATES // ===================================================== selectWindow("DAPI_MAX"); run("Select None"); run("Duplicate...", "title=MICRO_PREP"); selectWindow("MICRO_PREP"); run("8-bit"); run("Subtract Background...", "rolling=" + rollingBallNuc); if (gaussianSigmaNuc > 0) run("Gaussian Blur...", "sigma=" + gaussianSigmaNuc); run("Enhance Contrast...", "saturated=0.35 normalize"); setThreshold(minThresholdNuc, maxThresholdNuc); setOption("BlackBackground", false); run("Convert to Mask"); run("Fill Holes"); rename("MICRONUCLEI_MASK"); // ===================================================== // FIND ALL CANDIDATES // ===================================================== roiManager("Reset"); if (isOpen("Results")) { selectWindow("Results"); run("Clear Results"); } run("Set Measurements...", "area centroid shape decimal=3"); options = "size=" + minMicronucleusArea + "-" + maxMicronucleusArea + " circularity=" + minMicronucleusCirc + "-1.00 display add"; if (excludeEdges) options = options + " exclude"; selectWindow("MICRONUCLEI_MASK"); run("Analyze Particles...", options); candCount = roiManager("count"); if (candCount == 0) { micronucleiPerMainNucleus = 0; if (isOpen("Results")) { selectWindow("Results"); run("Clear Results"); } setResult("Image", 0, baseName); setResult("Main_nuclei_count", 0, mainNucleiCount); setResult("Micronuclei_count", 0, 0); setResult("Micronuclei_per_main_nucleus", 0, micronucleiPerMainNucleus); setResult("Mean_area", 0, 0); setResult("SD_area", 0, 0); setResult("Min_area", 0, 0); setResult("Max_area", 0, 0); setResult("Total_area", 0, 0); setResult("Threshold_mode", 0, "fixed"); setResult("Min_threshold_fixed", 0, minThresholdNuc); setResult("Max_threshold_fixed", 0, maxThresholdNuc); setResult("Min_area_filter", 0, minMicronucleusArea); setResult("Max_area_filter", 0, maxMicronucleusArea); setResult("Min_circularity_filter", 0, minMicronucleusCirc); setResult("Exclude_edges", 0, excludeEdges); updateResults(); saveAs("Results", summaryCsv); batchLine = baseName + "," + mainNucleiCount + "," + "0," + "0," + "0,0,0,0,0," + "fixed," + minThresholdNuc + "," + maxThresholdNuc + "," + minMicronucleusArea + "," + maxMicronucleusArea + "," + d2s(minMicronucleusCirc, 3) + "," + excludeEdges + "\n"; File.append(batchLine, batchSummaryCsv); close("*"); roiManager("Reset"); run("Collect Garbage"); continue; } // ===================================================== // STORE CANDIDATE ROIs AS TEMP ZIP // ===================================================== tempCandZip = outputDir + "__temp_candidates_" + baseName + ".zip"; roiManager("Deselect"); roiManager("Save", tempCandZip); // ===================================================== // MEASURE CANDIDATES // ===================================================== candArea = newArray(candCount); candX = newArray(candCount); candY = newArray(candCount); keepFlag = newArray(candCount); if (isOpen("Results")) { selectWindow("Results"); run("Clear Results"); } run("Set Measurements...", "area centroid shape decimal=3"); for (i = 0; i < candCount; i++) { selectWindow("DAPI_MAX"); roiManager("select", i); run("Measure"); candArea[i] = getResult("Area", i); candX[i] = getResult("X", i); candY[i] = getResult("Y", i); keepFlag[i] = 1; } // ===================================================== // FILTER BY TRUE GEOMETRIC OVERLAP // ===================================================== for (i = 0; i < candCount; i++) { overlapFound = 0; roiManager("Reset"); roiManager("Open", tempCandZip); roiManager("select", i); roiManager("Add"); candSelIndex = roiManager("count") - 1; roiManager("Open", mainRoiZip); totalNow = roiManager("count"); for (j = candSelIndex + 1; j < totalNow; j++) { roiManager("select", newArray(candSelIndex, j)); roiManager("AND"); if (selectionType() != -1) { getStatistics(andArea); if (andArea > 0) { overlapFound = 1; break; } } } if (overlapFound == 1) keepFlag[i] = 0; } // ===================================================== // COUNT FINAL KEPT MICRONUCLEI // ===================================================== micronucleiCount = 0; for (i = 0; i < candCount; i++) { if (keepFlag[i] == 1) micronucleiCount++; } if (mainNucleiCount > 0) micronucleiPerMainNucleus = micronucleiCount / mainNucleiCount; else micronucleiPerMainNucleus = 0; if (micronucleiCount == 0) { if (isOpen("Results")) { selectWindow("Results"); run("Clear Results"); } setResult("Image", 0, baseName); setResult("Main_nuclei_count", 0, mainNucleiCount); setResult("Micronuclei_count", 0, 0); setResult("Micronuclei_per_main_nucleus", 0, micronucleiPerMainNucleus); setResult("Mean_area", 0, 0); setResult("SD_area", 0, 0); setResult("Min_area", 0, 0); setResult("Max_area", 0, 0); setResult("Total_area", 0, 0); setResult("Threshold_mode", 0, "fixed"); setResult("Min_threshold_fixed", 0, minThresholdNuc); setResult("Max_threshold_fixed", 0, maxThresholdNuc); setResult("Min_area_filter", 0, minMicronucleusArea); setResult("Max_area_filter", 0, maxMicronucleusArea); setResult("Min_circularity_filter", 0, minMicronucleusCirc); setResult("Exclude_edges", 0, excludeEdges); updateResults(); saveAs("Results", summaryCsv); batchLine = baseName + "," + mainNucleiCount + "," + "0," + d2s(micronucleiPerMainNucleus, 6) + "," + "0,0,0,0,0," + "fixed," + minThresholdNuc + "," + maxThresholdNuc + "," + minMicronucleusArea + "," + maxMicronucleusArea + "," + d2s(minMicronucleusCirc, 3) + "," + excludeEdges + "\n"; File.append(batchLine, batchSummaryCsv); roiManager("Reset"); File.delete(tempCandZip); close("*"); run("Collect Garbage"); continue; } // ===================================================== // BUILD FINAL ARRAYS // ===================================================== microArea = newArray(micronucleiCount); microX = newArray(micronucleiCount); microY = newArray(micronucleiCount); totalArea = 0; k = 0; for (i = 0; i < candCount; i++) { if (keepFlag[i] == 1) { microArea[k] = candArea[i]; microX[k] = candX[i]; microY[k] = candY[i]; totalArea += microArea[k]; k++; } } if (micronucleiCount == 1) { minA = microArea[0]; maxA = microArea[0]; meanA = microArea[0]; sdA = 0; } else { Array.getStatistics(microArea, minA, maxA, meanA, sdA); if (sdA != sdA) sdA = 0; } // ===================================================== // REBUILD FINAL ROI SET AND SAVE // ===================================================== roiManager("Reset"); roiManager("Open", tempCandZip); for (i = candCount - 1; i >= 0; i--) { if (keepFlag[i] == 0) { roiManager("select", i); roiManager("Delete"); } } roiManager("Deselect"); roiManager("Save", roiZip); // ===================================================== // APPEND TO GLOBAL ALL CSV // ===================================================== for (i = 0; i < micronucleiCount; i++) { line = baseName + "," + (i + 1) + "," + d2s(microArea[i], 6) + "," + d2s(microX[i], 3) + "," + d2s(microY[i], 3) + "\n"; File.append(line, batchAllCsv); } // ===================================================== // SAVE PER-IMAGE AREA CSV // ===================================================== if (isOpen("Results")) { selectWindow("Results"); run("Clear Results"); } for (i = 0; i < micronucleiCount; i++) { setResult("Image", i, baseName); setResult("Micronucleus_ID", i, i + 1); setResult("Area", i, microArea[i]); setResult("X", i, microX[i]); setResult("Y", i, microY[i]); } updateResults(); saveAs("Results", areaCsv); // ===================================================== // SAVE SUMMARY CSV // ===================================================== if (isOpen("Results")) { selectWindow("Results"); run("Clear Results"); } setResult("Image", 0, baseName); setResult("Main_nuclei_count", 0, mainNucleiCount); setResult("Micronuclei_count", 0, micronucleiCount); setResult("Micronuclei_per_main_nucleus", 0, micronucleiPerMainNucleus); setResult("Mean_area", 0, meanA); setResult("SD_area", 0, sdA); setResult("Min_area", 0, minA); setResult("Max_area", 0, maxA); setResult("Total_area", 0, totalArea); setResult("Threshold_mode", 0, "fixed"); setResult("Min_threshold_fixed", 0, minThresholdNuc); setResult("Max_threshold_fixed", 0, maxThresholdNuc); setResult("Min_area_filter", 0, minMicronucleusArea); setResult("Max_area_filter", 0, maxMicronucleusArea); setResult("Min_circularity_filter", 0, minMicronucleusCirc); setResult("Exclude_edges", 0, excludeEdges); updateResults(); saveAs("Results", summaryCsv); // ===================================================== // APPEND TO BATCH SUMMARY // ===================================================== batchLine = baseName + "," + mainNucleiCount + "," + micronucleiCount + "," + d2s(micronucleiPerMainNucleus, 6) + "," + d2s(meanA, 6) + "," + d2s(sdA, 6) + "," + d2s(minA, 6) + "," + d2s(maxA, 6) + "," + d2s(totalArea, 6) + "," + "fixed," + minThresholdNuc + "," + maxThresholdNuc + "," + minMicronucleusArea + "," + maxMicronucleusArea + "," + d2s(minMicronucleusCirc, 3) + "," + excludeEdges + "\n"; File.append(batchLine, batchSummaryCsv); // ===================================================== // SAVE OVERLAY // ===================================================== if (saveOverlay) { selectWindow("DAPI_FOR_OVERLAY"); run("RGB Color"); roiManager("Reset"); roiManager("Open", mainRoiZip); roiManager("Deselect"); roiManager("Set Color", "magenta"); roiManager("Show All"); run("Flatten"); rename("OVERLAY_MAIN"); roiManager("Reset"); roiManager("Open", roiZip); selectWindow("OVERLAY_MAIN"); roiManager("Deselect"); roiManager("Set Color", "cyan"); roiManager("Show All"); run("Flatten"); rename("OVERLAY_FINAL"); selectWindow("OVERLAY_FINAL"); setForegroundColor(255, 255, 0); setFont("SansSerif", fontSizeLabel, "antialiased"); for (i = 0; i < micronucleiCount; i++) { drawString("" + (i + 1), microX[i] + 3, microY[i] - 3); } drawString("Micronuclei = " + micronucleiCount, 20, 30); drawString("Micronuclei / main nucleus = " + d2s(micronucleiPerMainNucleus, 3), 20, 55); drawString("Thr = " + minThresholdNuc + "-" + maxThresholdNuc, 20, 80); drawString("Area = " + minMicronucleusArea + "-" + maxMicronucleusArea + " ; Circ >= " + d2s(minMicronucleusCirc, 2), 20, 105); saveAs("Tiff", overlayTif); close(); if (isOpen("OVERLAY_MAIN")) { selectWindow("OVERLAY_MAIN"); close(); } } // ===================================================== // CLEANUP // ===================================================== roiManager("Reset"); File.delete(tempCandZip); close("*"); run("Collect Garbage"); } print("=== Batch micronuclei completato ==="); print("Batch summary salvato in: " + batchSummaryCsv); print("Batch per micronucleo salvato in: " + batchAllCsv); }