diff --git a/pom.xml b/pom.xml
index 9befd9e..9086898 100644
--- a/pom.xml
+++ b/pom.xml
@@ -85,31 +85,42 @@
net.imagej
ij
+ compile
net.imagej
imagej-common
+ compile
org.jogamp.jocl
jocl-main
- 2.3.2
+ 2.5.0
+ compile
org.jogamp.gluegen
gluegen-rt-main
- 2.3.2
+ 2.5.0
+ compile
org.apache.commons
commons-math3
3.6.1
+ compile
org.junit.jupiter
junit-jupiter-api
- 5.10.0
- test
+ 5.10.0
+ compile
+
+
+ org.junit.jupiter
+ junit-jupiter-engine
+ 5.10.0
+ compile
diff --git a/src/main/java/BlockRedundancy2DT_.java b/src/main/java/BlockRedundancy2DT_.java
deleted file mode 100644
index 525f276..0000000
--- a/src/main/java/BlockRedundancy2DT_.java
+++ /dev/null
@@ -1,844 +0,0 @@
-/**
- *
- * This class calculates block repetition maps for 2D+T data.
- *
- * @author Afonso Mendes
- *
- **/
-
-import com.jogamp.opencl.*;
-import ij.*;
-import ij.gui.NonBlockingGenericDialog;
-import ij.measure.Calibration;
-import ij.plugin.PlugIn;
-import ij.process.FloatProcessor;
-import ij.process.ImageProcessor;
-
-import java.io.ByteArrayOutputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.nio.FloatBuffer;
-
-import static com.jogamp.opencl.CLMemory.Mem.READ_ONLY;
-import static com.jogamp.opencl.CLMemory.Mem.READ_WRITE;
-import static ij.WindowManager.getIDList;
-import static ij.WindowManager.getImageCount;
-import static java.lang.Math.*;
-
-
-public class BlockRedundancy2DT_ implements PlugIn {
-
- // ------------------------ //
- // ---- OpenCL formats ---- //
- // ------------------------ //
-
- static private CLContext context;
-
- static private CLProgram programGetPatchMeans, programGetPatchPearson, programGetRelevanceMap;
-
- static private CLKernel kernelGetPatchMeans, kernelGetPatchPearson, kernelGetRelevanceMap;
-
- static private CLPlatform clPlatformMaxFlop;
-
- static private CLCommandQueue queue;
-
- private CLBuffer clPatchPixels, clRefPixels, clLocalMeans, clLocalStds, clPearsonMap, clRelevanceMap;
-
- @Override
- public void run(String s) {
-
- float EPSILON = 0.0000001f;
-
- // -------------------- //
- // ---- Dialog box ---- //
- // -------------------- //
-
- // Get all open image titles
- int nImages = getImageCount();
- if (nImages < 2) {
- IJ.error("Not enough images found. You need at least two.");
- return;
- }
-
- int[] ids = getIDList();
- String[] titles = new String[nImages];
- for(int i=0; i imgMax) {
- imgMax = pixelValue;
- }
- }
- // Remap pixels
- for (int i=0; i nFlops) {
- nFlops = clDevice.getMaxComputeUnits() * clDevice.getMaxClockFrequency();
- clPlatformMaxFlop = allPlatform;
- }
- }
- }
- IJ.log("--------");
-
- // Create context
- context = CLContext.create(clPlatformMaxFlop);
-
- // Choose the best device (i.e., Filter out CPUs if GPUs are available (FLOPS calculation was giving higher ratings to CPU vs. GPU))
- //TODO: Rate devices on Mandelbrot and chooose the best, GPU will perform better
- CLDevice[] allDevices = context.getDevices();
-
- boolean hasGPU = false;
- for (int i = 0; i < allDevices.length; i++) {
- if (allDevices[i].getType() == CLDevice.Type.GPU) {
- hasGPU = true;
- }
- }
- CLDevice chosenDevice;
- if (hasGPU) {
- chosenDevice = context.getMaxFlopsDevice(CLDevice.Type.GPU);
- } else {
- chosenDevice = context.getMaxFlopsDevice();
- }
-
- // Get chosen device from preferences
- if(useDevice){
- String deviceName = Prefs.get("SReD.OpenCL.device", null);
- for (CLDevice device : allDevices) {
- if (device.getName().equals(deviceName)) {
- chosenDevice = device;
- break;
- }
- }
- }
-
- IJ.log("Chosen device: " + chosenDevice.getName());
- IJ.log("--------");
-
- // Create command queue
- queue = chosenDevice.createCommandQueue();
- int elementCount = wh;
- int localWorkSize = min(chosenDevice.getMaxWorkGroupSize(), 256);
- int globalWorkSize = roundUp(localWorkSize, elementCount);
-
-
- // -------------------------------------------------------- //
- // ---- Calculate repetition for each frame separately ---- //
- // -------------------------------------------------------- //
-
- // Create stack to store final results
- ImageStack imsFinal = new ImageStack(w, h, nFrames);
-
- // Process frames
- for(int f=0; f0.0f) {
- IJ.showStatus("Calculating relevance map... Frame " + (f+1) + "/" + nFrames);
- IJ.showProgress(f, nFrames);
-
- // Create OpenCL program
- String programStringGetRelevanceMap = getResourceAsString(BlockRedundancy2DT_.class, "kernelGetRelevanceMask2D.cl");
- programStringGetRelevanceMap = replaceFirst(programStringGetRelevanceMap, "$WIDTH$", "" + w);
- programStringGetRelevanceMap = replaceFirst(programStringGetRelevanceMap, "$HEIGHT$", "" + h);
- programStringGetRelevanceMap = replaceFirst(programStringGetRelevanceMap, "$PATCH_SIZE$", "" + patchSize);
- programStringGetRelevanceMap = replaceFirst(programStringGetRelevanceMap, "$BRW$", "" + bRW);
- programStringGetRelevanceMap = replaceFirst(programStringGetRelevanceMap, "$BRH$", "" + bRH);
- programStringGetRelevanceMap = replaceFirst(programStringGetRelevanceMap, "$EPSILON$", "" + EPSILON);
- programGetRelevanceMap = context.createProgram(programStringGetRelevanceMap).build();
-
- // Create and fill buffers
- float[] relevanceMap = new float[wh];
- clRelevanceMap = context.createFloatBuffer(wh, READ_WRITE);
- fillBufferWithFloatArray(clRelevanceMap, relevanceMap);
- queue.putWriteBuffer(clRelevanceMap, true);
- queue.finish();
-
- // Create OpenCL kernel and set args
- kernelGetRelevanceMap = programGetRelevanceMap.createCLKernel("kernelGetRelevanceMask2D");
-
- argn = 0;
- kernelGetRelevanceMap.setArg(argn++, clRefPixels);
- kernelGetRelevanceMap.setArg(argn++, clRelevanceMap);
-
- // Calculate
- queue.put2DRangeKernel(kernelGetRelevanceMap, 0, 0, w, h, 0, 0);
- queue.finish();
-
- // Read the relevance mask back from the device
- queue.putReadBuffer(clRelevanceMap, true);
- for (int y=0; y noiseMeanVar * filterConstant) {
- float pixelValue = pearsonMap[j * w + i];
- if (pixelValue > pearsonMax) {
- pearsonMax = pixelValue;
- }
- if (pixelValue < pearsonMin) {
- pearsonMin = pixelValue;
- }
- }
- }
- }
-
- // Remap pixels
- for (int j = bRH; j < h - bRH; j++) {
- for (int i = bRW; i < w - bRW; i++) {
- if (relevanceMap[j * w + i] > noiseMeanVar * filterConstant) {
- pearsonMap[j * w + i] = (pearsonMap[j * w + i] - pearsonMin) / (pearsonMax - pearsonMin);
- }
- }
- }
- }
-
- }else if(normalizeOutput){
-
- // -------------------------- //
- // ---- Normalize output ---- //
- // -------------------------- //
-
- // Find min and max
- float pearsonMin = Float.MAX_VALUE;
- float pearsonMax = -Float.MAX_VALUE;
-
- for (int j=bRH; j pearsonMax) {
- pearsonMax = pixelValue;
- }
- if (pixelValue < pearsonMin) {
- pearsonMin = pixelValue;
- }
- }
- }
-
- // Remap pixels
- for (int j=bRH; j clBuffer, float[] pixels) {
- FloatBuffer buffer = clBuffer.getBuffer();
- for(int n=0; n minMax[1]){
- minMax[1] = inputArray[j*w+i];
- }
- }
- }
- return minMax;
- }
-
- public static float[] normalize(float[] rawPixels, int w, int h, int offsetX, int offsetY, float[] minMax, float tMin, float tMax){
- float rMin = minMax[0];
- float rMax = minMax[1];
- float denominator = rMax - rMin + 0.000001f;
- float factor;
-
- if(tMax == 0 && tMin == 0){
- factor = 1; // So that the users can say they don't want an artificial range by choosing tMax and tMin = 0
- }else {
- factor = tMax - tMin;
- }
- float[] normalizedPixels = new float[w*h];
-
- for(int j=offsetY; j clRefPixels, clLocalMeans, clLocalStds, clPatchPixels, clCosineSimMap, clDiffStdMap, clPearsonMap,
- clHuMap, clSsimMap, clRelevanceMap, clRmseMap;
-
- @Override
- public void run(String s) {
-
- float EPSILON = 0.0000001f;
-
- // Install SReD LUT
- installLUT.run();
-
- // -------------------- //
- // ---- Dialog box ---- //
- // -------------------- //
-
- // Get all open image titles
- int nImages = getImageCount();
- if (nImages < 2) {
- IJ.error("Not enough images found. You need at least two.");
- return;
- }
-
- int[] ids = getIDList();
- String[] titles = new String[nImages];
- for (int i = 0; i < nImages; i++) {
- titles[i] = WindowManager.getImage(ids[i]).getTitle();
- }
-
- // Define metric possibilities
- String[] metrics = new String[4];
- metrics[0] = "Pearson's R";
- metrics[1] = "Cosine similarity";
- metrics[2] = "SSIM";
- metrics[3] = "NRMSE (inverted)";
-
- // Initialize dialog box
- NonBlockingGenericDialog gd = new NonBlockingGenericDialog("SReD: Block Repetition (2D)");
- gd.addChoice("Block:", titles, titles[1]);
- gd.addChoice("Image:", titles, titles[0]);
- gd.addSlider("Relevance constant: ", 0.0f, 5.0f, 0.0f, 0.1f);
- gd.addChoice("Metric:", metrics, metrics[0]);
- gd.addCheckbox("Normalize output?", true);
- gd.addCheckbox("Use device from preferences?", false);
-
- gd.showDialog();
- if (gd.wasCanceled()) return;
-
- // Get parameters from dialog box
- String patchTitle = gd.getNextChoice();
- int patchID = 0;
- for (int i = 0; i < nImages; i++) {
- if (titles[i].equals(patchTitle)) { // .equals() instead of "==" required to run from macro
- patchID = ids[i];
- }
- }
-
- String imgTitle = gd.getNextChoice();
- int imgID = 0;
- for (int i = 0; i < nImages; i++) {
- if (titles[i].equals(imgTitle)) { // .equals() instead of "==" required to run from macro
- imgID = ids[i];
- }
- }
-
- float filterConstant = (float) gd.getNextNumber();
-
- String metric = gd.getNextChoice();
-
- boolean normalizeOutput = gd.getNextBoolean();
-
- boolean useDevice = gd.getNextBoolean();
-
-
- // --------------------- //
- // ---- Start timer ---- //
- // --------------------- //
-
- IJ.log("SReD has started, please wait.");
- long start = System.currentTimeMillis();
-
-
- // ------------------------------------------------- //
- // ---- Get reference patch and some parameters ---- //
- // ------------------------------------------------- //
- ImagePlus imp = WindowManager.getImage(patchID);
- if (imp == null) {
- IJ.error("Block image not found. Try again.");
- return;
- }
- ImageProcessor ip = imp.getProcessor();
- FloatProcessor fp = ip.convertToFloatProcessor();
- float[] patchPixels = (float[]) fp.getPixels();
- int bW = fp.getWidth(); // Patch width
- int bH = fp.getHeight(); // Patch height
-
- // Check if patch dimensions are odd, otherwise kill program
- if (bW % 2 == 0 || bH % 2 == 0) {
- IJ.error("Block dimensions must be odd (e.g., 3x3 or 5x5). Please try again.");
- return;
- }
-
- // Calculate block radius
- int bRW = bW / 2; // Patch radius (x-axis)
- int bRH = bH / 2; // Patch radius (y-axis)
-
-
- // ------------------------------------------------- //
- // ---- Get reference image and some parameters ---- //
- // ------------------------------------------------- //
-
- ImagePlus imp0 = WindowManager.getImage(imgID);
- if (imp0 == null) {
- IJ.error("Image not found. Try again.");
- return;
- }
- ImageProcessor ip0 = imp0.getProcessor();
- FloatProcessor fp0 = ip0.convertToFloatProcessor();
- float[] refPixels = (float[]) fp0.getPixels();
- int w = fp0.getWidth();
- int h = fp0.getHeight();
- int wh = w * h;
- //int sizeWithoutBorders = (w-bRW*2)*(h-bRH*2); // The area of the search field (= image without borders)
-
-
- // ---------------------------------- //
- // ---- Stabilize noise variance ---- //
- // ---------------------------------- //
-
- // Patch
- //IJ.log("Stabilising noise variance of the patch...");
- //GATMinimizer2D minimizer = new GATMinimizer2D(patchPixels, bW, bH, 0, 100, 0);
- //minimizer.run();
- //patchPixels = VarianceStabilisingTransform2D_.getGAT(patchPixels, minimizer.gain, minimizer.sigma, minimizer.offset);
- //IJ.log("Done.");
-
- // Image
- IJ.log("Stabilising noise variance of the image...");
- GATMinimizer2D minimizer = new GATMinimizer2D(refPixels, w, h, 0, 100, 0);
- minimizer.run();
- refPixels = VarianceStabilisingTransform2D_.getGAT(refPixels, minimizer.gain, minimizer.sigma, minimizer.offset);
- IJ.log("Done.");
-
-
- // --------------------------------- //
- // ---- Process reference block ---- //
- // --------------------------------- //
-
- // Get final block size (after removing pixels outside inbound circle/ellipse)
- int patchSize = 0;
- for (int j = 0; j < bH; j++) {
- for (int i = 0; i < bW; i++) {
- float dx = (float) (i - bRW);
- float dy = (float) (j - bRH);
- if (((dx * dx) / (float) (bRW * bRW)) + ((dy * dy) / (float) (bRH * bRH)) <= 1.0f) {
- patchSize++;
- }
- }
- }
-
- // Convert patch to "double" type (keeping only the pixels within the inbound circle/ellipse)
- double[] patchPixelsDouble = new double[patchSize];
- int index = 0;
- for (int j = 0; j < bH; j++) {
- for (int i = 0; i < bW; i++) {
- float dx = (float) (i - bRW);
- float dy = (float) (j - bRH);
- if (((dx * dx) / (float) (bRW * bRW)) + ((dy * dy) / (float) (bRH * bRH)) <= 1.0f) {
- patchPixelsDouble[index] = (double) patchPixels[j * bW + i];
- index++;
- }
- }
- }
-
- // Find min and max
- double patchMin = Double.MAX_VALUE; // Initialize as a very large number
- double patchMax = -Double.MAX_VALUE; // Initialize as a very small number
-
- for (int i = 0; i < patchSize; i++) {
- patchMin = min(patchMin, patchPixelsDouble[i]);
- patchMax = max(patchMax, patchPixelsDouble[i]);
- }
-
- // Normalize and calculate mean
- double patchMean = 0.0;
- for (int i = 0; i < patchSize; i++) {
- patchPixelsDouble[i] = (patchPixelsDouble[i] - patchMin) / (patchMax - patchMin + EPSILON);
- patchMean += patchPixelsDouble[i];
-
- }
- patchMean /= (double) patchSize;
-
- // Subtract mean
- for (int i = 0; i < patchSize; i++) {
- patchPixelsDouble[i] = patchPixelsDouble[i] - patchMean;
- }
-
- // Typecast back to float
- float[] patchPixelsFloat = new float[patchSize];
- for (int i = 0; i < patchSize; i++) {
- patchPixelsFloat[i] = (float) patchPixelsDouble[i];
- }
-
- // Calculate standard deviation
- float patchMeanFloat = (float) patchMean;
- double patchStdDev = 0.0;
- for (int i = 0; i < patchSize; i++) {
- patchStdDev += (patchPixelsDouble[i] - patchMean) * (patchPixelsDouble[i] - patchMean);
- }
- patchStdDev = (float) sqrt(patchStdDev / (patchSize - 1));
-
-
- // ----------------------- //
- // ---- Process image ---- //
- // ----------------------- //
-
- // Cast to "double" type
- double[] refPixelsDouble = new double[wh];
- for (int i = 0; i < wh; i++) {
- refPixelsDouble[i] = (double) refPixels[i];
- }
-
- // Get min and max
- double imgMin = Double.MAX_VALUE;
- double imgMax = -Double.MAX_VALUE;
- for (int i = 0; i < w * h; i++) {
- double pixelValue = refPixelsDouble[i];
- imgMin = min(imgMin, pixelValue);
- imgMax = max(imgMax, pixelValue);
- }
-
- // Normalize
- for (int i = 0; i < wh; i++) {
- refPixelsDouble[i] = (refPixelsDouble[i] - imgMin) / (imgMax - imgMin + EPSILON);
- }
-
- // Cast back to float
- for (int i = 0; i < wh; i++) {
- refPixels[i] = (float) refPixelsDouble[i];
- }
-
-
- // --------------------------- //
- // ---- Initialize OpenCL ---- //
- // --------------------------- //
-
- // Check OpenCL devices
- CLPlatform[] allPlatforms = CLPlatform.listCLPlatforms();
-
- try {
- allPlatforms = CLPlatform.listCLPlatforms();
- } catch (CLException ex) {
- IJ.log("Something went wrong while initialising OpenCL.");
- throw new RuntimeException("Something went wrong while initialising OpenCL.");
- }
-
- double nFlops = 0;
-
- for (CLPlatform allPlatform : allPlatforms) {
- CLDevice[] allCLdeviceOnThisPlatform = allPlatform.listCLDevices();
-
- for (CLDevice clDevice : allCLdeviceOnThisPlatform) {
- //IJ.log("--------");
- //IJ.log("Device name: " + clDevice.getName());
- //IJ.log("Device type: " + clDevice.getType());
- //IJ.log("Max clock: " + clDevice.getMaxClockFrequency() + " MHz");
- //IJ.log("Number of compute units: " + clDevice.getMaxComputeUnits());
- //IJ.log("Max work group size: " + clDevice.getMaxWorkGroupSize());
- if (clDevice.getMaxComputeUnits() * clDevice.getMaxClockFrequency() > nFlops) {
- nFlops = clDevice.getMaxComputeUnits() * clDevice.getMaxClockFrequency();
- clPlatformMaxFlop = allPlatform;
- }
- }
- }
- IJ.log("--------");
-
- // Create OpenCL context
- context = CLContext.create(clPlatformMaxFlop);
-
- // Choose the best device (i.e., Filter out CPUs if GPUs are available (FLOPS calculation was giving
- // higher ratings to CPU vs. GPU))
- //TODO: Rate devices on Mandelbrot and chooose the best, GPU will perform better
- CLDevice[] allDevices = context.getDevices();
-
- boolean hasGPU = false;
- for (int i = 0; i < allDevices.length; i++) {
- if (allDevices[i].getType() == CLDevice.Type.GPU) {
- hasGPU = true;
- }
- }
- CLDevice chosenDevice;
- if (hasGPU) {
- chosenDevice = context.getMaxFlopsDevice(CLDevice.Type.GPU);
- } else {
- chosenDevice = context.getMaxFlopsDevice();
- }
-
- // Get chosen device from preferences
- if (useDevice) {
- String deviceName = Prefs.get("SReD.OpenCL.device", null);
- for (CLDevice device : allDevices) {
- if (device.getName().equals(deviceName)) {
- chosenDevice = device;
- break;
- }
- }
- }
-
- IJ.log("Chosen device: " + chosenDevice.getName());
- IJ.log("--------");
-
- // Create command queue
- queue = chosenDevice.createCommandQueue();
- int elementCount = w * h;
- int localWorkSize = min(chosenDevice.getMaxWorkGroupSize(), 256);
- int globalWorkSize = roundUp(localWorkSize, elementCount);
-
- IJ.log("Calculating block repetition...");
-
-
- // ------------------------------- //
- // ---- Calculate local means ---- //
- // ------------------------------- //
-
- // Create buffers
- clRefPixels = context.createFloatBuffer(wh, READ_ONLY);
- clLocalMeans = context.createFloatBuffer(wh, READ_WRITE);
- clLocalStds = context.createFloatBuffer(wh, READ_WRITE);
-
- // Create OpenCL program
- String programStringGetPatchMeans = getResourceAsString(BlockRedundancy2D_.class, "kernelGetPatchMeans2D.cl");
- programStringGetPatchMeans = replaceFirst(programStringGetPatchMeans, "$WIDTH$", "" + w);
- programStringGetPatchMeans = replaceFirst(programStringGetPatchMeans, "$HEIGHT$", "" + h);
- programStringGetPatchMeans = replaceFirst(programStringGetPatchMeans, "$PATCH_SIZE$", "" + patchSize);
- programStringGetPatchMeans = replaceFirst(programStringGetPatchMeans, "$BRW$", "" + bRW);
- programStringGetPatchMeans = replaceFirst(programStringGetPatchMeans, "$BRH$", "" + bRH);
- programStringGetPatchMeans = replaceFirst(programStringGetPatchMeans, "$EPSILON$", "" + EPSILON);
- programGetPatchMeans = context.createProgram(programStringGetPatchMeans).build();
-
- // Fill OpenCL buffers
- fillBufferWithFloatArray(clRefPixels, refPixels);
-
- float[] localMeans = new float[wh];
- fillBufferWithFloatArray(clLocalMeans, localMeans);
-
- float[] localStds = new float[wh];
- fillBufferWithFloatArray(clLocalStds, localStds);
-
- // Create OpenCL kernel and set args
- kernelGetPatchMeans = programGetPatchMeans.createCLKernel("kernelGetPatchMeans2D");
-
- int argn = 0;
- kernelGetPatchMeans.setArg(argn++, clRefPixels);
- kernelGetPatchMeans.setArg(argn++, clLocalMeans);
- kernelGetPatchMeans.setArg(argn++, clLocalStds);
-
- // Calculate
- queue.putWriteBuffer(clRefPixels, true);
- queue.putWriteBuffer(clLocalMeans, true);
- queue.putWriteBuffer(clLocalStds, true);
-
- showStatus("Calculating local means...");
-
- queue.put2DRangeKernel(kernelGetPatchMeans, 0, 0, w, h, 0, 0);
- queue.finish();
-
- // Read the local means map back from the device
- queue.putReadBuffer(clLocalMeans, true);
- for (int y = 0; y < h; y++) {
- for (int x = 0; x < w; x++) {
- localMeans[y * w + x] = clLocalMeans.getBuffer().get(y * w + x);
- queue.finish();
-
- }
- }
-
- // Read the local stds map back from the device
- queue.putReadBuffer(clLocalStds, true);
- for (int y = 0; y < h; y++) {
- for (int x = 0; x < w; x++) {
- localStds[y * w + x] = clLocalStds.getBuffer().get(y * w + x);
- queue.finish();
- }
- }
-
- // Release memory
- kernelGetPatchMeans.release();
- programGetPatchMeans.release();
-
-
- // --------------------------------------------------------------- //
- // ---- Calculate block repetition map with the chosen metric ---- //
- // --------------------------------------------------------------- //
-
- float[] repetitionMap = new float[wh]; // Array to store output repetition map
-
- if (metric == metrics[0]) { // Pearson correlation
- showStatus("Calculating Pearson correlations...");
-
- // Build OpenCL program
- String programStringGetPatchPearson = getResourceAsString(BlockRedundancy2D_.class, "kernelGetPatchPearson2D.cl");
- programStringGetPatchPearson = replaceFirst(programStringGetPatchPearson, "$WIDTH$", "" + w);
- programStringGetPatchPearson = replaceFirst(programStringGetPatchPearson, "$HEIGHT$", "" + h);
- programStringGetPatchPearson = replaceFirst(programStringGetPatchPearson, "$PATCH_SIZE$", "" + patchSize);
- programStringGetPatchPearson = replaceFirst(programStringGetPatchPearson, "$BW$", "" + bW);
- programStringGetPatchPearson = replaceFirst(programStringGetPatchPearson, "$BH$", "" + bH);
- programStringGetPatchPearson = replaceFirst(programStringGetPatchPearson, "$BRW$", "" + bRW);
- programStringGetPatchPearson = replaceFirst(programStringGetPatchPearson, "$BRH$", "" + bRH);
- programStringGetPatchPearson = replaceFirst(programStringGetPatchPearson, "$PATCH_MEAN$", "" + patchMeanFloat);
- programStringGetPatchPearson = replaceFirst(programStringGetPatchPearson, "$PATCH_STD$", "" + patchStdDev);
- programStringGetPatchPearson = replaceFirst(programStringGetPatchPearson, "$EPSILON$", "" + EPSILON);
-
- programGetPatchPearson = context.createProgram(programStringGetPatchPearson).build();
- //System.out.println(programGetPatchPearson.getBuildLog()); // Print program build log to check for errors
-
- // Fill OpenCL buffers
- clPatchPixels = context.createFloatBuffer(patchSize, READ_ONLY);
- fillBufferWithFloatArray(clPatchPixels, patchPixelsFloat);
-
- clPearsonMap = context.createFloatBuffer(wh, READ_WRITE);
- fillBufferWithFloatArray(clPearsonMap, repetitionMap);
-
- // Create kernel and set args
- kernelGetPatchPearson = programGetPatchPearson.createCLKernel("kernelGetPatchPearson2D");
-
- argn = 0;
- kernelGetPatchPearson.setArg(argn++, clPatchPixels);
- kernelGetPatchPearson.setArg(argn++, clRefPixels);
- kernelGetPatchPearson.setArg(argn++, clLocalMeans);
- kernelGetPatchPearson.setArg(argn++, clLocalStds);
- kernelGetPatchPearson.setArg(argn++, clPearsonMap);
-
- // Calculate Pearson's correlation coefficient (reference patch vs. all)
- queue.putWriteBuffer(clPatchPixels, true);
- queue.putWriteBuffer(clPearsonMap, true);
- queue.put2DRangeKernel(kernelGetPatchPearson, 0, 0, w, h, 0, 0);
- queue.finish();
-
- // Read Pearson's coefficients back from the GPU
- queue.putReadBuffer(clPearsonMap, true);
- for (int y = 0; y < h; y++) {
- for (int x = 0; x < w; x++) {
- repetitionMap[y * w + x] = clPearsonMap.getBuffer().get(y * w + x);
- queue.finish();
- }
- }
- queue.finish();
-
- // Release GPU resources
- kernelGetPatchPearson.release();
- clPatchPixels.release();
- clPearsonMap.release();
- programGetPatchPearson.release();
- }
-
- if (metric == metrics[1]) { // Cosine similarity
- showStatus("Calculating Cosine similarity...");
-
- // Build OpenCL program
- String programStringGetPatchCosineSim = getResourceAsString(BlockRedundancy2D_.class, "kernelGetPatchCosineSim2D.cl");
- programStringGetPatchCosineSim = replaceFirst(programStringGetPatchCosineSim, "$WIDTH$", "" + w);
- programStringGetPatchCosineSim = replaceFirst(programStringGetPatchCosineSim, "$HEIGHT$", "" + h);
- programStringGetPatchCosineSim = replaceFirst(programStringGetPatchCosineSim, "$BRW$", "" + bRW);
- programStringGetPatchCosineSim = replaceFirst(programStringGetPatchCosineSim, "$BRH$", "" + bRH);
- programStringGetPatchCosineSim = replaceFirst(programStringGetPatchCosineSim, "$PATCH_STD$", "" + patchStdDev);
- programStringGetPatchCosineSim = replaceFirst(programStringGetPatchCosineSim, "$EPSILON$", "" + EPSILON);
- programGetPatchCosineSim = context.createProgram(programStringGetPatchCosineSim).build();
-
- // Fill OpenCL buffers
- clCosineSimMap = context.createFloatBuffer(wh, READ_WRITE);
- fillBufferWithFloatArray(clCosineSimMap, repetitionMap);
-
- // Create kernel and set args
- kernelGetPatchCosineSim = programGetPatchCosineSim.createCLKernel("kernelGetPatchCosineSim2D");
-
- argn = 0;
- kernelGetPatchCosineSim.setArg(argn++, clLocalStds);
- kernelGetPatchCosineSim.setArg(argn++, clCosineSimMap);
-
- // Calculate absolute difference of StdDevs
- queue.putWriteBuffer(clCosineSimMap, true);
- queue.put2DRangeKernel(kernelGetPatchCosineSim, 0, 0, w, h, 0, 0);
- queue.finish();
-
- // Read results back from the OpenCL device
- queue.putReadBuffer(clCosineSimMap, true);
- for (int y = 0; y < h; y++) {
- for (int x = 0; x < w; x++) {
- repetitionMap[y * w + x] = clCosineSimMap.getBuffer().get(y * w + x);
- queue.finish();
- }
- }
- queue.finish();
-
- // Release GPU resources
- kernelGetPatchCosineSim.release();
- clCosineSimMap.release();
- programGetPatchCosineSim.release();
- }
-
- if (metric == metrics[2]) { // SSIM
- showStatus("Calculating SSIM...");
-
- // Build OpenCL program
- String programStringGetPatchSsim = getResourceAsString(BlockRedundancy2D_.class, "kernelGetPatchSsim2D.cl");
- programStringGetPatchSsim = replaceFirst(programStringGetPatchSsim, "$WIDTH$", "" + w);
- programStringGetPatchSsim = replaceFirst(programStringGetPatchSsim, "$HEIGHT$", "" + h);
- programStringGetPatchSsim = replaceFirst(programStringGetPatchSsim, "$PATCH_SIZE$", "" + patchSize);
- programStringGetPatchSsim = replaceFirst(programStringGetPatchSsim, "$BW$", "" + bW);
- programStringGetPatchSsim = replaceFirst(programStringGetPatchSsim, "$BH$", "" + bH);
- programStringGetPatchSsim = replaceFirst(programStringGetPatchSsim, "$BRW$", "" + bRW);
- programStringGetPatchSsim = replaceFirst(programStringGetPatchSsim, "$BRH$", "" + bRH);
- programStringGetPatchSsim = replaceFirst(programStringGetPatchSsim, "$PATCH_MEAN$", "" + patchMeanFloat);
- programStringGetPatchSsim = replaceFirst(programStringGetPatchSsim, "$PATCH_STD$", "" + patchStdDev);
- programStringGetPatchSsim = replaceFirst(programStringGetPatchSsim, "$EPSILON$", "" + EPSILON);
- programGetPatchSsim = context.createProgram(programStringGetPatchSsim).build();
- //System.out.println(programGetPatchSsim.getBuildLog()); // Print program build log to check for errors
-
- // Fill OpenCL buffers
- clPatchPixels = context.createFloatBuffer(patchSize, READ_ONLY);
- fillBufferWithFloatArray(clPatchPixels, patchPixelsFloat);
-
- clSsimMap = context.createFloatBuffer(wh, READ_WRITE);
- fillBufferWithFloatArray(clSsimMap, repetitionMap);
-
- // Create kernel and set args
- kernelGetPatchSsim = programGetPatchSsim.createCLKernel("kernelGetPatchSsim2D");
-
- argn = 0;
- kernelGetPatchSsim.setArg(argn++, clPatchPixels);
- kernelGetPatchSsim.setArg(argn++, clRefPixels);
- kernelGetPatchSsim.setArg(argn++, clLocalMeans);
- kernelGetPatchSsim.setArg(argn++, clLocalStds);
- kernelGetPatchSsim.setArg(argn++, clSsimMap);
-
- // Calculate SSIM
- queue.putWriteBuffer(clPatchPixels, true);
- queue.putWriteBuffer(clSsimMap, true);
- queue.put2DRangeKernel(kernelGetPatchSsim, 0, 0, w, h, 0, 0);
- queue.finish();
-
- // Read SSIM back from the GPU
- queue.putReadBuffer(clSsimMap, true);
- for (int y = 0; y < h; y++) {
- for (int x = 0; x < w; x++) {
- repetitionMap[y * w + x] = clSsimMap.getBuffer().get(y * w + x);
- queue.finish();
- }
- }
- queue.finish();
-
- // Release GPU resources
- kernelGetPatchSsim.release();
- clPatchPixels.release();
- clSsimMap.release();
- programGetPatchSsim.release();
- }
-
- if (metric == metrics[3]) { // NRMSE (inverted)
- showStatus("Calculating NRMSE...");
-
- // Build OpenCL program
- String programStringGetPatchRmse = getResourceAsString(BlockRedundancy2D_.class, "kernelGetPatchRmse2D.cl");
- programStringGetPatchRmse = replaceFirst(programStringGetPatchRmse, "$WIDTH$", "" + w);
- programStringGetPatchRmse = replaceFirst(programStringGetPatchRmse, "$HEIGHT$", "" + h);
- programStringGetPatchRmse = replaceFirst(programStringGetPatchRmse, "$PATCH_SIZE$", "" + patchSize);
- programStringGetPatchRmse = replaceFirst(programStringGetPatchRmse, "$BW$", "" + bW);
- programStringGetPatchRmse = replaceFirst(programStringGetPatchRmse, "$BH$", "" + bH);
- programStringGetPatchRmse = replaceFirst(programStringGetPatchRmse, "$BRW$", "" + bRW);
- programStringGetPatchRmse = replaceFirst(programStringGetPatchRmse, "$BRH$", "" + bRH);
- programStringGetPatchRmse = replaceFirst(programStringGetPatchRmse, "$PATCH_MEAN$", "" + patchMeanFloat);
- programStringGetPatchRmse = replaceFirst(programStringGetPatchRmse, "$EPSILON$", "" + EPSILON);
- programGetPatchRmse = context.createProgram(programStringGetPatchRmse).build();
- //System.out.println(programGetPatchSsim.getBuildLog()); // Print program build log to check for errors
-
- // Fill OpenCL buffers
- clPatchPixels = context.createFloatBuffer(patchSize, READ_ONLY);
- fillBufferWithFloatArray(clPatchPixels, patchPixelsFloat);
-
- clRmseMap = context.createFloatBuffer(wh, READ_WRITE);
- fillBufferWithFloatArray(clRmseMap, repetitionMap);
-
- // Create kernel and set args
- kernelGetPatchRmse = programGetPatchRmse.createCLKernel("kernelGetPatchRmse2D");
-
- argn = 0;
- kernelGetPatchRmse.setArg(argn++, clPatchPixels);
- kernelGetPatchRmse.setArg(argn++, clRefPixels);
- kernelGetPatchRmse.setArg(argn++, clLocalMeans);
- kernelGetPatchRmse.setArg(argn++, clRmseMap);
-
- // Calculate RMSE
- queue.putWriteBuffer(clPatchPixels, true);
- queue.putWriteBuffer(clRmseMap, true);
- queue.put2DRangeKernel(kernelGetPatchRmse, 0, 0, w, h, 0, 0);
- queue.finish();
-
- // Read RMSE back from the GPU
- queue.putReadBuffer(clRmseMap, true);
- for (int y = 0; y < h; y++) {
- for (int x = 0; x < w; x++) {
- repetitionMap[y * w + x] = clRmseMap.getBuffer().get(y * w + x);
- queue.finish();
- }
- }
- queue.finish();
-
- // Release GPU resources
- kernelGetPatchRmse.release();
- clPatchPixels.release();
- clRmseMap.release();
- programGetPatchRmse.release();
-
- // Invert RMSE
- for (int y = bRH; y < h-bRH; y++) {
- for (int x = bRW; x < w-bRW; x++) {
- float rmse = repetitionMap[y*w+x];
-
- if(rmse == 0.0f){ // Special case where RMSE is 0, 1/rmse would be undefined but we want perfect similarity
- repetitionMap[y*w+x] = 1.0f;
- }else{
- repetitionMap[y*w+x] = 1.0f / rmse;
- }
- }
- }
- }
-
-
- // --------------------------------------- //
- // ---- Filter out irrelevant regions ---- //
- // --------------------------------------- //
-
- if (filterConstant > 0.0f) {
- showStatus("Calculating relevance mask...");
-
- // Create OpenCL program
- String programStringGetRelevanceMap = getResourceAsString(BlockRedundancy2D_.class, "kernelGetRelevanceMask2D.cl");
- programStringGetRelevanceMap = replaceFirst(programStringGetRelevanceMap, "$WIDTH$", "" + w);
- programStringGetRelevanceMap = replaceFirst(programStringGetRelevanceMap, "$HEIGHT$", "" + h);
- programStringGetRelevanceMap = replaceFirst(programStringGetRelevanceMap, "$PATCH_SIZE$", "" + patchSize);
- programStringGetRelevanceMap = replaceFirst(programStringGetRelevanceMap, "$BRW$", "" + bRW);
- programStringGetRelevanceMap = replaceFirst(programStringGetRelevanceMap, "$BRH$", "" + bRH);
- programStringGetRelevanceMap = replaceFirst(programStringGetRelevanceMap, "$EPSILON$", "" + EPSILON);
- programGetRelevanceMap = context.createProgram(programStringGetRelevanceMap).build();
-
- // Create and fill buffers
- float[] relevanceMap = new float[wh];
- clRelevanceMap = context.createFloatBuffer(wh, READ_WRITE);
- fillBufferWithFloatArray(clRelevanceMap, relevanceMap);
- queue.putWriteBuffer(clRelevanceMap, true);
- queue.finish();
-
- // Create OpenCL kernel and set args
- kernelGetRelevanceMap = programGetRelevanceMap.createCLKernel("kernelGetRelevanceMask2D");
-
- argn = 0;
- kernelGetRelevanceMap.setArg(argn++, clRefPixels);
- kernelGetRelevanceMap.setArg(argn++, clRelevanceMap);
-
- // Calculate
- queue.put2DRangeKernel(kernelGetRelevanceMap, 0, 0, w, h, 0, 0);
- queue.finish();
-
- // Read the relevance map back from the device
- queue.putReadBuffer(clRelevanceMap, true);
- for (int y = 0; y < h; y++) {
- for (int x = 0; x < w; x++) {
- relevanceMap[y * w + x] = clRelevanceMap.getBuffer().get(y * w + x);
- }
- }
- queue.finish();
-
- // Release resources
- kernelGetRelevanceMap.release();
- clRelevanceMap.release();
- programGetRelevanceMap.release();
-
- /* OLD
-
- // Calculate mean noise variance
- float noiseMeanVar = 0.0f;
- float n = 0.0f;
- for (int j = bRH; j < h - bRH; j++) {
- for (int i = bRW; i < w - bRW; i++) {
- noiseMeanVar += relevanceMap[j * w + i];
- n += 1.0f;
- }
- }
- noiseMeanVar /= n;
-
- */
-
- // Calculate mean noise variance
- float noiseMeanVar = getMeanNoiseVar(relevanceMap, w, h, wh);
-
- // Filter out irrelevant regions
- for (int j = bRH; j < h - bRH; j++) {
- for (int i = bRW; i < w - bRW; i++) {
- if (relevanceMap[j * w + i] <= noiseMeanVar * filterConstant) {
- repetitionMap[j * w + i] = 0.0f;
- }
- }
- }
-
- if (normalizeOutput) {
- // ----------------------------------------------------------------------- //
- // ---- Normalize output (avoiding pixels outside the relevance mask) ---- //
- // ----------------------------------------------------------------------- //
-
- // Find min and max within the relevance mask
- float repetitionMin = Float.MAX_VALUE;
- float repetitionMax = -Float.MAX_VALUE;
-
- for (int j = bRH; j < h - bRH; j++) {
- for (int i = bRW; i < w - bRW; i++) {
- if (relevanceMap[j * w + i] > noiseMeanVar * filterConstant) {
- float pixelValue = repetitionMap[j * w + i];
- if (pixelValue > repetitionMax) {
- repetitionMax = pixelValue;
- }
- if (pixelValue < repetitionMin) {
- repetitionMin = pixelValue;
- }
- }
- }
- }
-
- // Remap pixels
- for (int j = bRH; j < h - bRH; j++) {
- for (int i = bRW; i < w - bRW; i++) {
- if (relevanceMap[j * w + i] > noiseMeanVar * filterConstant) {
- repetitionMap[j * w + i] = (repetitionMap[j * w + i] - repetitionMin) / (repetitionMax - repetitionMin + EPSILON);
- }
- }
- }
- }
- }
-
- // Release resources
- context.release();
-
-
- // ------------------------- //
- // ---- Display results ---- //
- // ------------------------- //
-
- FloatProcessor fp1 = new FloatProcessor(w, h, repetitionMap);
- ImagePlus imp1 = new ImagePlus("Block Repetition Map", fp1);
-
- // Apply SReD LUT
- InputStream lutStream = getClass().getResourceAsStream("/luts/sred-jet.lut");
- if (lutStream == null) {
- IJ.error("Could not load SReD LUT. Using default LUT.");
- }else{
- try {
- // Load LUT file
- IndexColorModel icm = LutLoader.open(lutStream);
- byte[] r = new byte[256];
- byte[] g = new byte[256];
- byte[] b = new byte[256];
- icm.getReds(r);
- icm.getGreens(g);
- icm.getBlues(b);
- LUT lut = new LUT(8, 256, r, g, b);
-
- // Apply LUT to image
- imp1.getProcessor().setLut(lut);
- //imp1.updateAndDraw();
- } catch (IOException e) {
- IJ.error("Could not load SReD LUT");
- }
- }
-
- // Display
- imp1.show();
-
-
- // -------------------- //
- // ---- Stop timer ---- //
- // -------------------- //
-
- IJ.log("Finished!");
- long elapsedTime = System.currentTimeMillis() - start;
- IJ.log("Elapsed time: " + elapsedTime/1000 + " sec");
- IJ.log("--------");
- }
-
- // ------------------------ //
- // ---- USER FUNCTIONS ---- //
- // ------------------------ //
-
- public static float getMeanNoiseVar(float[] refPixels, int w, int h, int wh) {
-
- int blockWidth, blockHeight;
- int CIF = 352 * 288; // Resolution of a CIF file
-
- if (wh <= CIF) {
- blockWidth = 8;
- blockHeight = 8;
- } else {
- blockWidth = 16;
- blockHeight = 16;
- }
-
- int nBlocksX = w / blockWidth; // number of blocks in each row
- int nBlocksY = h / blockHeight; // number of blocks in each column
- int nBlocks = nBlocksX * nBlocksY; // total number of blocks
- float[] localVars = new float[nBlocks];
- Arrays.fill(localVars, 0.0f);
-
- // Calculate local variances
- int index = 0;
-
- for (int y = 0; y < nBlocksY; y++) {
- for (int x = 0; x < nBlocksX; x++) {
- float[] meanVar = getMeanAndVarBlock(refPixels, w, x * blockWidth, y * blockHeight, (x + 1) * blockWidth, (y + 1) * blockHeight);
- localVars[index] = (float) meanVar[1];
- index++;
- }
- }
-
- // Sort the local variances
- float[] sortedVars = new float[nBlocks];
- Arrays.fill(sortedVars, 0.0f);
-
- index = 0;
- for (int i = 0; i < nBlocks; i++) {
- sortedVars[index] = localVars[index];
- index++;
- }
- Arrays.sort(sortedVars);
-
- // Get the 3% lowest variances and calculate their average
- int nVars = (int) (0.03f * (float) nBlocks + 1.0f); // Number of blocks corresponding to 3% of the total amount of blocks
- float noiseVar = 0.0f;
-
- for (int i = 0; i < nVars; i++) {
- noiseVar += sortedVars[i];
- }
- noiseVar = abs(noiseVar / (float) nVars);
- noiseVar = (1.0f + 0.001f * (noiseVar - 40.0f)) * noiseVar;
-
- return noiseVar;
- }
-
- public static float[] getMeanAndVarBlock(float[] pixels, int width, int xStart, int yStart, int xEnd, int yEnd) {
- float mean = 0;
- float var;
-
- float sq_sum = 0;
-
- int bWidth = xEnd-xStart;
- int bHeight = yEnd - yStart;
- int bWH = bWidth*bHeight;
-
- for (int j=yStart; j clBuffer, float pixel) {
- FloatBuffer buffer = clBuffer.getBuffer();
- buffer.put(pixel);
- }
-
- public static void fillBufferWithFloatArray(CLBuffer clBuffer, float[] pixels) {
- FloatBuffer buffer = clBuffer.getBuffer();
- for(int n=0; n clBuffer, double[] pixels) {
- DoubleBuffer buffer = clBuffer.getBuffer();
- for(int n=0; n< pixels.length; n++) {
- buffer.put(n, pixels[n]);
- }
- }
-
- public static void fillBufferWithShortArray(CLBuffer clBuffer, short[] pixels) {
- ShortBuffer buffer = clBuffer.getBuffer();
- for(int n=0; n< pixels.length; n++) {
- buffer.put(n, pixels[n]);
- }
- }
-
- // Read a kernel from the resources
- public static String getResourceAsString(Class c, String resourceName) {
- InputStream programStream = c.getResourceAsStream("/" + resourceName);
- String programString = "";
-
- try {
- programString = inputStreamToString(programStream);
- } catch (IOException var5) {
- var5.printStackTrace();
- }
-
- return programString;
- }
-
- public static String inputStreamToString(InputStream inputStream) throws IOException {
- ByteArrayOutputStream result = new ByteArrayOutputStream();
- byte[] buffer = new byte[1024];
- int length;
- while((length = inputStream.read(buffer)) != -1) {
- result.write(buffer, 0, length);
- }
- return result.toString("UTF-8");
- }
-
- private static int roundUp(int groupSize, int globalSize) {
- int r = globalSize % groupSize;
- if (r == 0) {
- return globalSize;
- } else {
- return globalSize + groupSize - r;
- }
- }
-
- public static float[] findMinMax(float[] inputArray, int w, int h, int offsetX, int offsetY){
- float[] minMax = {Float.MAX_VALUE, -Float.MAX_VALUE};
-
- for(int j=offsetY; j minMax[1]){
- minMax[1] = inputArray[j*w+i];
- }
- }
- }
- return minMax;
- }
-
- public static String replaceFirst(String source, String target, String replacement) {
- int index = source.indexOf(target);
- if (index == -1) {
- return source;
- }
-
- return source.substring(0, index)
- .concat(replacement)
- .concat(source.substring(index+target.length()));
- }
-
- public static float[] normalize(float[] rawPixels, int w, int h, int offsetX, int offsetY, float[] minMax, float tMin, float tMax){
- float rMin = minMax[0];
- float rMax = minMax[1];
- float denominator = rMax - rMin + 0.000001f;
- float factor;
-
- if(tMax == 0 && tMin == 0){
- factor = 1; // So that the users can say they don't want an artificial range by choosing tMax and tMin = 0
- }else {
- factor = tMax - tMin;
- }
- float[] normalizedPixels = new float[w*h];
-
- for(int j=offsetY; j clRefPixels, clLocalMeans, clLocalStds, clPatchPixels, clDiffStdMap, clPearsonMap,
- clHuMap, clSsimMap, clRelevanceMap, clGaussianWindow;
-
- @Override
- public void run(String s) {
-
- float EPSILON = 0.0000001f;
-
-
- // -------------------- //
- // ---- Dialog box ---- //
- // -------------------- //
-
- // Get all open image titles
- int nImages = getImageCount();
- if (nImages < 2) {
- IJ.error("Not enough images found. You need at least two.");
- return;
- }
-
- int[] ids = getIDList();
- String[] titles = new String[nImages];
- for(int i=0; i z) {
- IJ.error("Patch must have at least 3 slices. Please try again.");
- return;
- }
-
- //int sizeWithoutBorders = (w-bRW*2)*(h-bRH*2); // The area of the search field (= image without borders)
-
-
- // ---------------------------------- //
- // ---- Stabilize noise variance ---- //
- // ---------------------------------- //
-
- // Patch
- IJ.log("Stabilizing noise variance of the patch...");
-
- float[][] patchPixels = new float[bZ][bW*bH];
- for(int n=0; nimgMax){
- imgMax = pixelValue;
- }
- }
-
- // Normalize
- for(int i=0; i nFlops) {
- nFlops = clDevice.getMaxComputeUnits() * clDevice.getMaxClockFrequency();
- clPlatformMaxFlop = allPlatform;
- }
- }
- }
- IJ.log("--------");
-
- // Create OpenCL context
- context = CLContext.create(clPlatformMaxFlop);
-
- // Choose the best device (i.e., Filter out CPUs if GPUs are available (FLOPS calculation was giving
- // higher ratings to CPU vs. GPU))
- //TODO: Rate devices on Mandelbrot and chooose the best, GPU will perform better
- CLDevice[] allDevices = context.getDevices();
-
- boolean hasGPU = false;
- for (int i=0; i0.0f) {
- showStatus("Calculating relevance map...");
-
- // Create OpenCL program
- String programStringGetRelevanceMap3D = getResourceAsString(BlockRedundancy3D_.class, "kernelGetRelevanceMask3D.cl");
- programStringGetRelevanceMap3D = replaceFirst(programStringGetRelevanceMap3D, "$WIDTH$", "" + w);
- programStringGetRelevanceMap3D = replaceFirst(programStringGetRelevanceMap3D, "$HEIGHT$", "" + h);
- programStringGetRelevanceMap3D = replaceFirst(programStringGetRelevanceMap3D, "$DEPTH$", "" + z);
- programStringGetRelevanceMap3D = replaceFirst(programStringGetRelevanceMap3D, "$PATCH_SIZE$", "" + patchSize);
- programStringGetRelevanceMap3D = replaceFirst(programStringGetRelevanceMap3D, "$BRW$", "" + bRW);
- programStringGetRelevanceMap3D = replaceFirst(programStringGetRelevanceMap3D, "$BRH$", "" + bRH);
- programStringGetRelevanceMap3D = replaceFirst(programStringGetRelevanceMap3D, "$BRZ$", "" + bRZ);
- programStringGetRelevanceMap3D = replaceFirst(programStringGetRelevanceMap3D, "$EPSILON$", "" + EPSILON);
- programGetRelevanceMap3D = context.createProgram(programStringGetRelevanceMap3D).build();
-
- // Create and fill buffers
- float[] relevanceMap = new float[whz];
- clRelevanceMap = context.createFloatBuffer(wh, READ_WRITE);
- fillBufferWithFloatArray(clRelevanceMap, relevanceMap);
- queue.putWriteBuffer(clRelevanceMap, true);
- queue.finish();
-
- // Create OpenCL kernel and set args
- kernelGetRelevanceMap3D = programGetRelevanceMap3D.createCLKernel("kernelGetRelevanceMask3D");
-
- argn = 0;
- kernelGetRelevanceMap3D.setArg(argn++, clRefPixels);
- kernelGetRelevanceMap3D.setArg(argn++, clRelevanceMap);
-
- // Calculate
- queue.put3DRangeKernel(kernelGetRelevanceMap3D, 0, 0, 0, w, h, z, 0, 0, 0);
- queue.finish();
-
- // Read the relevance mask back from the device
- queue.putReadBuffer(clRelevanceMap, true);
- for(int n=0; n noiseMeanVar * filterConstant) {
- float pixelValue = pearsonMap[w*h*n+j*w+i];
- if (pixelValue > pearsonMax) {
- pearsonMax = pixelValue;
- }
- if (pixelValue < pearsonMin) {
- pearsonMin = pixelValue;
- }
- }
- }
- }
- }
-
- // Remap pixels
- for(int n=bRZ; n noiseMeanVar * filterConstant) {
- pearsonMap[w*h*n+j*w+i] = (pearsonMap[w*h*n+j*w+i] - pearsonMin) / (pearsonMax - pearsonMin);
- }
- }
- }
- }
- }
-
- }else if(normalizeOutput){
-
- // -------------------------- //
- // ---- Normalize output ---- //
- // -------------------------- //
-
- // Find min and max
- float pearsonMin = Float.MAX_VALUE;
- float pearsonMax = -Float.MAX_VALUE;
-
- for(int n=bRZ; n pearsonMax) {
- pearsonMax = pixelValue;
- }
- if (pixelValue < pearsonMin) {
- pearsonMin = pixelValue;
- }
- }
- }
- }
-
- // Remap pixels
- for(int n=bRZ; n clBuffer, float[] pixels) {
- FloatBuffer buffer = clBuffer.getBuffer();
- for(int n=0; n minMax[1]){
- minMax[1] = inputArray[j*w+i];
- }
- }
- }
- return minMax;
- }
-
- public static float[] normalize(float[] rawPixels, int w, int h, int offsetX, int offsetY, float[] minMax, float tMin, float tMax){
- float rMin = minMax[0];
- float rMax = minMax[1];
- float denominator = rMax - rMin + 0.000001f;
- float factor;
-
- if(tMax == 0 && tMin == 0){
- factor = 1; // So that the users can say they don't want an artificial range by choosing tMax and tMin = 0
- }else {
- factor = tMax - tMin;
- }
- float[] normalizedPixels = new float[w*h];
-
- for(int j=offsetY; j inputImage.getWidth() || referenceBlock.getHeight() > inputImage.getHeight() || referenceBlock.getDepth() > inputImage.getDepth()) {
+ IJ.error("Block dimensions can't be larger than image dimensions.");
+ throw new IllegalArgumentException("Block dimensions must be smaller than image dimensions.");
+ }
+
+ // Calculate repetition map
+ float[] repetitionMap = CLUtils.calculateBlockRepetitionMap3D(metric, inputImage, referenceBlock, relevanceConstant, normalizeOutput, useDevice);
+
+ // Display results
+ Utils.displayResults3D(inputImage, repetitionMap);
+
+ // Stop timer
+ long elapsedTime = System.currentTimeMillis() - start;
+
+ IJ.log("Finished!");
+ IJ.log("Elapsed time: " + elapsedTime/1000 + " sec");
+ IJ.log("--------");
+
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/CLUtils.java b/src/main/java/CLUtils.java
new file mode 100644
index 0000000..aa7d0cb
--- /dev/null
+++ b/src/main/java/CLUtils.java
@@ -0,0 +1,3169 @@
+import com.jogamp.opencl.*;
+import ij.IJ;
+import ij.Prefs;
+
+import java.io.*;
+import java.nio.FloatBuffer;
+import java.util.Arrays;
+import java.util.logging.Logger;
+
+import static com.jogamp.opencl.CLMemory.Mem.*;
+import static ij.IJ.showStatus;
+import static java.lang.Math.max;
+import static java.lang.Math.min;
+
+public class CLUtils {
+
+ // Private constructor to prevent instantiation
+ private CLUtils() {
+ throw new UnsupportedOperationException("Utility class");
+ }
+
+
+ // ---------------------------- //
+ // ---- OBJECTS FOR OPENCL ---- //
+ // ---------------------------- //
+
+ /**
+ * This class encapsulates the OpenCL resources required for executing
+ * OpenCL kernels, including the context, device, and command queue.
+ */
+ public static class OpenCLResources
+ {
+
+ private final CLContext context;
+ private final CLDevice device;
+ private final CLCommandQueue queue;
+
+ /**
+ * Constructs an instance of OpenCLResources with the specified OpenCL context, device, and command queue.
+ *
+ * @param context the OpenCL context
+ * @param device the OpenCL device
+ * @param queue the command queue associated with the OpenCL device
+ */
+ public OpenCLResources(CLContext context, CLDevice device, CLCommandQueue queue) {
+ this.context = context;
+ this.device = device;
+ this.queue = queue;
+ }
+
+ /**
+ * Returns the OpenCL context associated with this instance.
+ *
+ * @return the OpenCL context
+ */
+ public CLContext getContext() {
+ return context;
+ }
+
+ /**
+ * Returns the OpenCL device associated with this instance.
+ *
+ * @return the OpenCL device
+ */
+ public CLDevice getDevice() {
+ return device;
+ }
+
+ /**
+ * Returns the command queue associated with the OpenCL device.
+ *
+ * @return the command queue
+ */
+ public CLCommandQueue getQueue() {
+ return queue;
+ }
+ }
+
+
+ /**
+ * This class encapsulates local statistics for a 2D image, including the local means, local standard deviations,
+ * and associated OpenCL buffers.
+ */
+ public static class CLLocalStatistics
+ {
+
+ private float[] localMeans;
+ private float[] localStds;
+ private CLBuffer clImageArray;
+ private CLBuffer clLocalMeans;
+ private CLBuffer clLocalStds;
+
+ /**
+ * Constructs an instance of CLLocalStatistics with the specified local means, local standard deviations,
+ * and OpenCL buffers for the image array, local means, and local standard deviations.
+ *
+ * @param localMeans an array of local means calculated from the image
+ * @param localStds an array of local standard deviations calculated from the image
+ * @param clImageArray OpenCL buffer containing the image data
+ * @param clLocalMeans OpenCL buffer containing the local means
+ * @param clLocalStds OpenCL buffer containing the local standard deviations
+ */
+ public CLLocalStatistics(float[] localMeans, float[] localStds, CLBuffer clImageArray, CLBuffer clLocalMeans, CLBuffer clLocalStds) {
+ this.localMeans = localMeans;
+ this.localStds = localStds;
+ this.clImageArray = clImageArray;
+ this.clLocalMeans = clLocalMeans;
+ this.clLocalStds = clLocalStds;
+ }
+
+ /**
+ * Returns the local means calculated for the image.
+ *
+ * @return an array of local means
+ */
+ public float[] getLocalMeans() {
+ return localMeans;
+ }
+
+ /**
+ * Returns the local standard deviations calculated for the image.
+ *
+ * @return an array of local standard deviations
+ */
+ public float[] getLocalStds() {
+ return localStds;
+ }
+
+ /**
+ * Returns the OpenCL buffer containing the image data.
+ *
+ * @return the OpenCL buffer for the image array
+ */
+ public CLBuffer getCLImageArray() {
+ return clImageArray;
+ }
+ /**
+ * Returns the OpenCL buffer for storing the local means.
+ *
+ * @return the OpenCL buffer for local means
+ */
+ public CLBuffer getCLLocalMeans() {
+ return clLocalMeans;
+ }
+
+ /**
+ * Returns the OpenCL buffer for storing the local standard deviations.
+ *
+ * @return the OpenCL buffer for local standard deviations
+ */
+ public CLBuffer getCLLocalStds() {
+ return clLocalStds;
+ }
+ }
+
+
+ // ------------------------------------------------------------------ //
+ // ---- METHODS FOR OPENCL INITIALISATION AND RESOURCE MANAGEMENT---- //
+ // ------------------------------------------------------------------ //
+ /**
+ * Initializes OpenCL and retrieves objects related to OpenCL resources.
+ *
+ * @param useDevice A boolean indicating if the OpenCL device should be chosen
+ * according to the user-defined preference.
+ * @return A {@link OpenCLResources} object containing the initialized OpenCL
+ * {@code CLContext}, {@code CLDevice}, and {@code CLCommandQueue}.
+ * @throws RuntimeException if there is an error during OpenCL initialization.
+ */
+ public static OpenCLResources getOpenCLResources(boolean useDevice)
+ {
+ IJ.log("Initialising OpenCL...");
+
+ // Check OpenCL devices
+ CLPlatform[] allPlatforms = CLPlatform.listCLPlatforms();
+ try {
+ allPlatforms = CLPlatform.listCLPlatforms();
+ } catch (CLException ex) {
+ IJ.log("Something went wrong while initialising OpenCL.");
+ throw new RuntimeException("Something went wrong while initialising OpenCL.");
+ }
+
+ double nFlops = 0;
+
+ CLPlatform clPlatformMaxFlop = null;
+ for (CLPlatform allPlatform : allPlatforms) {
+ CLDevice[] allCLdeviceOnThisPlatform = allPlatform.listCLDevices();
+
+ for (CLDevice clDevice : allCLdeviceOnThisPlatform) {
+ //IJ.log("--------");
+ //IJ.log("Device name: " + clDevice.getName());
+ //IJ.log("Device type: " + clDevice.getType());
+ //IJ.log("Max clock: " + clDevice.getMaxClockFrequency() + " MHz");
+ //IJ.log("Number of compute units: " + clDevice.getMaxComputeUnits());
+ //IJ.log("Max work group size: " + clDevice.getMaxWorkGroupSize());
+ if (clDevice.getMaxComputeUnits() * clDevice.getMaxClockFrequency() > nFlops) {
+ nFlops = clDevice.getMaxComputeUnits() * clDevice.getMaxClockFrequency();
+ clPlatformMaxFlop = allPlatform;
+ }
+ }
+ }
+
+ // Create OpenCL context
+ CLContext context = CLContext.create(clPlatformMaxFlop);
+
+ // Choose the best device (filter out CPUs if GPUs are available)
+ CLDevice[] allDevices = context.getDevices();
+ boolean hasGPU = false;
+
+ for (int i = 0; i < allDevices.length; i++) {
+ if (allDevices[i].getType() == CLDevice.Type.GPU) {
+ hasGPU = true;
+ }
+ }
+ CLDevice chosenDevice;
+ if (hasGPU) {
+ chosenDevice = context.getMaxFlopsDevice(CLDevice.Type.GPU);
+ } else {
+ chosenDevice = context.getMaxFlopsDevice();
+ }
+
+ // Get chosen device from preferences (optional)
+ if (useDevice) {
+ String deviceName = Prefs.get("SReD.OpenCL.device", null);
+ for (CLDevice device : allDevices) {
+ if (device.getName().equals(deviceName)) {
+ chosenDevice = device;
+ break;
+ }
+ }
+ }
+
+ IJ.log("Device chosen: " + chosenDevice.getName());
+
+ // Create command queue
+ CLCommandQueue queue = chosenDevice.createCommandQueue();
+
+ return new OpenCLResources(context, chosenDevice, queue);
+ }
+
+
+ /**
+ * Creates an OpenCL buffer and fills it with the provided float array.
+ *
+ * @param context the OpenCL context in which to create the buffer
+ * @param size the size of the buffer to be created
+ * @param flag the memory flag to specify the allocation behavior of the buffer
+ * @param array the float array containing the data to fill the buffer with
+ * @return a {@link CLBuffer} object filled with the data from the provided float array
+ */
+ public static CLBuffer createAndFillCLBuffer(CLContext context, int size, CLMemory.Mem flag, float[] array)
+ {
+ CLBuffer clBuffer = context.createFloatBuffer(size, flag);
+ fillBufferWithFloatArray(clBuffer, array);
+
+ return clBuffer;
+ }
+
+
+ /**
+ * Fills an OpenCL buffer with data from the provided float array.
+ *
+ * @param clBuffer the OpenCL buffer to be filled with data
+ * @param array the float array containing the data to be copied into the buffer
+ * @throws IndexOutOfBoundsException if the specified index is out of range
+ */
+ public static void fillBufferWithFloatArray(CLBuffer clBuffer, float[] array)
+ {
+ FloatBuffer buffer = clBuffer.getBuffer();
+ for(int n=0; n clImageArray = CLUtils.createAndFillCLBuffer(context, imageSize, READ_ONLY, inputImage2D.getImageArray());
+ queue.putWriteBuffer(clImageArray, true);
+
+ float[] localMeans = new float[inputImage2D.getSize()];
+ CLBuffer clLocalMeans = CLUtils.createAndFillCLBuffer(context, imageSize, READ_WRITE, localMeans);
+ queue.putWriteBuffer(clLocalMeans, true);
+
+ float[] localStds = new float[inputImage2D.getSize()];
+ CLBuffer clLocalStds = CLUtils.createAndFillCLBuffer(context, imageSize, READ_WRITE, localStds);
+ queue.putWriteBuffer(clLocalStds, true);
+
+ // Create OpenCL program
+ String programStringGetLocalStatistics2D = getResourceAsString(CLUtils.class, "kernelGetLocalStatistics2D.cl");
+ programStringGetLocalStatistics2D = replaceFirst(programStringGetLocalStatistics2D, "$WIDTH$", "" + imageWidth);
+ programStringGetLocalStatistics2D = replaceFirst(programStringGetLocalStatistics2D, "$HEIGHT$", "" + imageHeight);
+ programStringGetLocalStatistics2D = replaceFirst(programStringGetLocalStatistics2D, "$BLOCK_SIZE$", "" + blockSize);
+ programStringGetLocalStatistics2D = replaceFirst(programStringGetLocalStatistics2D, "$BRW$", "" + bRW);
+ programStringGetLocalStatistics2D = replaceFirst(programStringGetLocalStatistics2D, "$BRH$", "" + bRH);
+ programStringGetLocalStatistics2D = replaceFirst(programStringGetLocalStatistics2D, "$EPSILON$", "" + EPSILON);
+ CLProgram programGetLocalStatistics2D = context.createProgram(programStringGetLocalStatistics2D).build();
+
+ // Crete OpenCL kernel and set args
+ CLKernel kernelGetLocalStatistics2D = programGetLocalStatistics2D.createCLKernel("kernelGetLocalStatistics2D");
+
+ int argn = 0;
+ kernelGetLocalStatistics2D.setArg(argn++, clImageArray);
+ kernelGetLocalStatistics2D.setArg(argn++, clLocalMeans);
+ kernelGetLocalStatistics2D.setArg(argn++, clLocalStds);
+
+ int localWorkSize = min(device.getMaxWorkGroupSize(), 256);
+ int globalWorkSize = roundUp(localWorkSize, imageSize);
+
+ queue.put2DRangeKernel(kernelGetLocalStatistics2D,
+ 0,
+ 0,
+ imageWidth,
+ imageHeight,
+ 0,
+ 0);
+ queue.finish();
+
+ // Read the local means map back from the device
+ queue.putReadBuffer(clLocalMeans, true);
+ for (int y=0; y clBlockPixels = CLUtils.createAndFillCLBuffer(context, blockSize, READ_ONLY,
+ referenceBlock2D.getPixels());
+ queue.putWriteBuffer(clBlockPixels, true);
+
+ float[] repetitionMap = new float[imageSize];
+ CLBuffer clRepetitionMap = CLUtils.createAndFillCLBuffer(context, imageSize, READ_WRITE,
+ repetitionMap);
+
+ // Build OpenCL program
+ String programStringGetBlockPearson2D = getResourceAsString(CLUtils.class, "kernelGetBlockPearson2D.cl");
+ programStringGetBlockPearson2D = replaceFirst(programStringGetBlockPearson2D, "$WIDTH$", "" + imageWidth);
+ programStringGetBlockPearson2D = replaceFirst(programStringGetBlockPearson2D, "$HEIGHT$", "" + imageHeight);
+ programStringGetBlockPearson2D = replaceFirst(programStringGetBlockPearson2D, "$BLOCK_SIZE$", "" + blockSize);
+ programStringGetBlockPearson2D = replaceFirst(programStringGetBlockPearson2D, "$BLOCK_WIDTH$", "" + blockWidth);
+ programStringGetBlockPearson2D = replaceFirst(programStringGetBlockPearson2D, "$BLOCK_HEIGHT$", "" + blockHeight);
+ programStringGetBlockPearson2D = replaceFirst(programStringGetBlockPearson2D, "$BRW$", "" + bRW);
+ programStringGetBlockPearson2D = replaceFirst(programStringGetBlockPearson2D, "$BRH$", "" + bRH);
+ programStringGetBlockPearson2D = replaceFirst(programStringGetBlockPearson2D, "$BLOCK_STD$", "" + referenceBlock2D.getStd());
+ programStringGetBlockPearson2D = replaceFirst(programStringGetBlockPearson2D, "$EPSILON$", "" + Utils.EPSILON);
+ CLProgram programGetBlockPearson2D = context.createProgram(programStringGetBlockPearson2D).build();
+
+ // Create OpenCL kernel and set args
+ CLKernel kernelGetBlockPearson2D = programGetBlockPearson2D.createCLKernel("kernelGetBlockPearson2D");
+
+ int argn = 0;
+ kernelGetBlockPearson2D.setArg(argn++, clBlockPixels);
+ kernelGetBlockPearson2D.setArg(argn++, localStatistics.getCLImageArray());
+ kernelGetBlockPearson2D.setArg(argn++, localStatistics.getCLLocalMeans());
+ kernelGetBlockPearson2D.setArg(argn++, localStatistics.getCLLocalStds());
+ kernelGetBlockPearson2D.setArg(argn++, clRepetitionMap);
+
+ int localWorkSize = min(device.getMaxWorkGroupSize(), 256);
+ int globalWorkSize = roundUp(localWorkSize, imageSize);
+
+ queue.put2DRangeKernel(kernelGetBlockPearson2D, 0, 0, imageWidth, imageHeight,
+ 0, 0);
+ queue.finish();
+
+ // Read the repetition map back from the device
+ queue.putReadBuffer(clRepetitionMap, true);
+ for (int y=0; y0.0f) {
+ Utils.RelevanceMask relevanceMask = Utils.getRelevanceMask(inputImage2D.getImageArray(), imageWidth,
+ imageHeight, bRW, bRH, localStatistics.getLocalStds(), relevanceConstant);
+ relevanceMaskArray = relevanceMask.getRelevanceMask();
+ repetitionMap = Utils.applyMask2D(repetitionMap, imageWidth, imageHeight, relevanceMaskArray);
+ }
+
+ // Normalize repetition map (avoiding masked pixels)
+ if(normalizeOutput) {
+ repetitionMap = Utils.normalizeImage2D(repetitionMap, imageWidth, imageHeight, bRW, bRH,
+ relevanceMaskArray);
+ }
+
+ // Release memory
+ context.release();
+
+ return repetitionMap;
+ }
+
+
+ /**
+ * Computes the block-wise cosine similarity for a 2D image using OpenCL.
+ *
+ * This method calculates the cosine similarity between a reference block and all blocks
+ * of the input image. The result is stored in a repetition map, indicating the degree
+ * of similarity across the image. Optionally, a relevance mask can be applied to filter
+ * the results based on local standard deviations and a relevance constant.
+ *
+ * @param inputImage2D the input 2D image for similarity calculation
+ * @param referenceBlock2D the reference block used for comparison in the similarity calculation
+ * @param relevanceConstant a constant value used to cintrol the strength of the structural relevance filter
+ * @param normalizeOutput a boolean indicating whether to normalize the output repetition map
+ * @param useDevice a boolean indicating whether to use the user-defined OpenCL device preference
+ * @return a float array representing the repetition map, indicating the cosine similarity
+ * coefficients across the image
+ */
+ public static float[] getBlockCosineSimilarity2D(Utils.InputImage2D inputImage2D,
+ Utils.ReferenceBlock2D referenceBlock2D,
+ float relevanceConstant,
+ boolean normalizeOutput,
+ boolean useDevice)
+ {
+ // Cache variables
+ int imageWidth = inputImage2D.getWidth();
+ int imageHeight = inputImage2D.getHeight();
+ int imageSize = inputImage2D.getSize();
+ int blockWidth = referenceBlock2D.getWidth();
+ int blockHeight = referenceBlock2D.getHeight();
+ int blockSize = referenceBlock2D.getSize();
+ int bRW = referenceBlock2D.getRadiusWidth();
+ int bRH = referenceBlock2D.getRadiusHeight();
+
+ // Initialize OpenCL
+ CLUtils.OpenCLResources openCLResources = CLUtils.getOpenCLResources(useDevice);
+
+ // Retrieve OpenCL context, device and queue
+ CLContext context = openCLResources.getContext();
+ CLDevice device = openCLResources.getDevice();
+ CLCommandQueue queue = openCLResources.getQueue();
+
+ // Calculate local statistics
+ CLLocalStatistics localStatistics = getLocalStatistics2D(context, device, queue, inputImage2D,
+ blockWidth, blockHeight, Utils.EPSILON);
+
+ // Create and fill OpenCL buffers
+ IJ.log("Calculating Structural Repetition Scores...");
+
+ CLBuffer clBlockPixels = createAndFillCLBuffer(context, blockSize, READ_ONLY,
+ referenceBlock2D.getPixels());
+ queue.putWriteBuffer(clBlockPixels, true);
+
+ float[] repetitionMap = new float[imageSize];
+ CLBuffer clRepetitionMap = createAndFillCLBuffer(context, imageSize, READ_WRITE,
+ repetitionMap);
+
+ // Build OpenCL program
+ String programStringGetBlockCosineSimilarity2D = getResourceAsString(CLUtils.class, "kernelGetBlockCosineSimilarity2D.cl");
+ programStringGetBlockCosineSimilarity2D = replaceFirst(programStringGetBlockCosineSimilarity2D, "$WIDTH$", "" + imageWidth);
+ programStringGetBlockCosineSimilarity2D = replaceFirst(programStringGetBlockCosineSimilarity2D, "$HEIGHT$", "" + imageHeight);
+ programStringGetBlockCosineSimilarity2D = replaceFirst(programStringGetBlockCosineSimilarity2D, "$BRW$", "" + bRW);
+ programStringGetBlockCosineSimilarity2D = replaceFirst(programStringGetBlockCosineSimilarity2D, "$BRH$", "" + bRH);
+ programStringGetBlockCosineSimilarity2D = replaceFirst(programStringGetBlockCosineSimilarity2D, "$BLOCK_STD$", "" + referenceBlock2D.getStd());
+ programStringGetBlockCosineSimilarity2D = replaceFirst(programStringGetBlockCosineSimilarity2D, "$EPSILON$", "" + Utils.EPSILON);
+ CLProgram programGetBlockCosineSimilarity2D = context.createProgram(programStringGetBlockCosineSimilarity2D).build();
+
+ // Create OpenCL kernel and set args
+ CLKernel kernelGetBlockCosineSimilarity2D = programGetBlockCosineSimilarity2D.createCLKernel("kernelGetBlockCosineSimilarity2D");
+
+ int argn = 0;
+ kernelGetBlockCosineSimilarity2D.setArg(argn++, localStatistics.getCLLocalStds());
+ kernelGetBlockCosineSimilarity2D.setArg(argn++, clRepetitionMap);
+
+ int localWorkSize = min(device.getMaxWorkGroupSize(), 256);
+ int globalWorkSize = roundUp(localWorkSize, imageSize);
+
+ queue.put2DRangeKernel(kernelGetBlockCosineSimilarity2D, 0, 0, imageWidth,
+ imageHeight, 0, 0);
+ queue.finish();
+
+ // Read the repetition map back from the device
+ queue.putReadBuffer(clRepetitionMap, true);
+ for (int y=0; y0.0f) {
+ Utils.RelevanceMask relevanceMask = Utils.getRelevanceMask(inputImage2D.getImageArray(), imageWidth,
+ imageHeight, bRW, bRH, localStatistics.getLocalStds(), relevanceConstant);
+ relevanceMaskArray = relevanceMask.getRelevanceMask();
+ repetitionMap = Utils.applyMask2D(repetitionMap, imageWidth, imageHeight, relevanceMaskArray);
+ }
+
+ // Normalize repetition map (avoiding masked pixels)
+ if(normalizeOutput) {
+ repetitionMap = Utils.normalizeImage2D(repetitionMap, imageWidth, imageHeight, bRW, bRH,
+ relevanceMaskArray);
+ }
+
+ // Release memory
+ context.release();
+
+ return repetitionMap;
+ }
+
+
+ /**
+ * Computes the block-wise Structural Similarity Index (SSIM) for a 2D image using OpenCL.
+ *
+ * This method calculates the SSIM between a reference block and all blocks of the input image,
+ * generating a repetition map that indicates the similarity strength across the image.
+ * Optionally, a relevance mask can be applied to filter the results based on local
+ * standard deviations and a relevance constant.
+ *
+ * @param inputImage2D the input 2D image for SSIM calculation
+ * @param referenceBlock2D the reference block used for comparison in the SSIM calculation
+ * @param relevanceConstant a constant value used to control the strength of the structural relevance filter
+ * @param normalizeOutput a boolean indicating whether to normalize the output repetition map
+ * @param useDevice a boolean indicating whether to use the user-defined OpenCL device preference
+ * @return a float array representing the repetition map, indicating the SSIM coefficients
+ * across the image
+ */
+ public static float[] getBlockSsim2D(Utils.InputImage2D inputImage2D,
+ Utils.ReferenceBlock2D referenceBlock2D,
+ float relevanceConstant,
+ boolean normalizeOutput,
+ boolean useDevice)
+ {
+ IJ.log("Calculating Structural Repetition Scores...");
+
+ // Cache variables
+ int imageWidth = inputImage2D.getWidth();
+ int imageHeight = inputImage2D.getHeight();
+ int imageSize = inputImage2D.getSize();
+ int blockWidth = referenceBlock2D.getWidth();
+ int blockHeight = referenceBlock2D.getHeight();
+ int blockSize = referenceBlock2D.getSize();
+ int bRW = referenceBlock2D.getRadiusWidth();
+ int bRH = referenceBlock2D.getRadiusHeight();
+
+ // Initialize OpenCL
+ CLUtils.OpenCLResources openCLResources = CLUtils.getOpenCLResources(useDevice);
+
+ // Retrieve OpenCL context, device and queue
+ CLContext context = openCLResources.getContext();
+ CLDevice device = openCLResources.getDevice();
+ CLCommandQueue queue = openCLResources.getQueue();
+
+ // Calculate local statistics
+ CLLocalStatistics localStatistics = CLUtils.getLocalStatistics2D(context, device, queue, inputImage2D,
+ blockWidth, blockHeight, Utils.EPSILON);
+
+
+ // Create and fill OpenCL buffers
+ CLBuffer clBlockPixels = CLUtils.createAndFillCLBuffer(context, blockSize, READ_ONLY,
+ referenceBlock2D.getPixels());
+ queue.putWriteBuffer(clBlockPixels, true);
+
+ float[] repetitionMap = new float[imageSize];
+ CLBuffer clRepetitionMap = CLUtils.createAndFillCLBuffer(context, imageSize, READ_WRITE,
+ repetitionMap);
+
+ // Build OpenCL program
+ String programStringGetBlockSsim2D = getResourceAsString(CLUtils.class, "kernelGetBlockSsim2D.cl");
+ programStringGetBlockSsim2D = replaceFirst(programStringGetBlockSsim2D, "$WIDTH$", "" + imageWidth);
+ programStringGetBlockSsim2D = replaceFirst(programStringGetBlockSsim2D, "$HEIGHT$", "" + imageHeight);
+ programStringGetBlockSsim2D = replaceFirst(programStringGetBlockSsim2D, "$BLOCK_SIZE$", "" + blockSize);
+ programStringGetBlockSsim2D = replaceFirst(programStringGetBlockSsim2D, "$BW$", "" + blockWidth);
+ programStringGetBlockSsim2D = replaceFirst(programStringGetBlockSsim2D, "$BH$", "" + blockHeight);
+ programStringGetBlockSsim2D = replaceFirst(programStringGetBlockSsim2D, "$BRW$", "" + bRW);
+ programStringGetBlockSsim2D = replaceFirst(programStringGetBlockSsim2D, "$BRH$", "" + bRH);
+ programStringGetBlockSsim2D = replaceFirst(programStringGetBlockSsim2D, "$BLOCK_MEAN$", "" + referenceBlock2D.getMean());
+ programStringGetBlockSsim2D = replaceFirst(programStringGetBlockSsim2D, "$BLOCK_STD$", "" + referenceBlock2D.getStd());
+ programStringGetBlockSsim2D = replaceFirst(programStringGetBlockSsim2D, "$EPSILON$", "" + Utils.EPSILON);
+ CLProgram programGetBlockSsim2D = context.createProgram(programStringGetBlockSsim2D).build();
+
+ // Create OpenCL kernel and set args
+ CLKernel kernelGetBlockSsim2D = programGetBlockSsim2D.createCLKernel("kernelGetBlockSsim2D");
+
+ int argn = 0;
+ kernelGetBlockSsim2D.setArg(argn++, clBlockPixels);
+ kernelGetBlockSsim2D.setArg(argn++, localStatistics.getCLImageArray());
+ kernelGetBlockSsim2D.setArg(argn++, localStatistics.getCLLocalMeans());
+ kernelGetBlockSsim2D.setArg(argn++, localStatistics.getCLLocalStds());
+ kernelGetBlockSsim2D.setArg(argn++, clRepetitionMap);
+
+ int localWorkSize = min(device.getMaxWorkGroupSize(), 256);
+ int globalWorkSize = roundUp(localWorkSize, imageSize);
+
+ queue.put2DRangeKernel(kernelGetBlockSsim2D, 0, 0, imageWidth, imageHeight,
+ 0, 0);
+ queue.finish();
+
+ // Read the repetition map back from the device
+ queue.putReadBuffer(clRepetitionMap, true);
+ for (int y=0; y0.0f) {
+ Utils.RelevanceMask relevanceMask = Utils.getRelevanceMask(inputImage2D.getImageArray(), imageWidth,
+ imageHeight, bRW, bRH, localStatistics.getLocalStds(), relevanceConstant);
+ relevanceMaskArray = relevanceMask.getRelevanceMask();
+ repetitionMap = Utils.applyMask2D(repetitionMap, imageWidth, imageHeight, relevanceMaskArray);
+ }
+
+ // Normalize repetition map (avoiding masked pixels)
+ if(normalizeOutput) {
+ repetitionMap = Utils.normalizeImage2D(repetitionMap, imageWidth, imageHeight, bRW, bRH,
+ relevanceMaskArray);
+ }
+
+ // Release memory
+ context.release();
+
+ return repetitionMap;
+ }
+
+
+ /**
+ * Computes the block-wise Normalized Root Mean Square Error (NRMSE) for a 2D image using OpenCL.
+ *
+ * This method calculates the NRMSE between a reference block and blocks of the input image,
+ * generating a repetition map that indicates the similarity (inverted NRMSE) across the image.
+ * It allows for optional relevance masking based on local standard deviations and a relevance constant.
+ *
+ * @param inputImage2D the input 2D image for NRMSE calculation
+ * @param referenceBlock2D the reference block used for comparison in the NRMSE calculation
+ * @param relevanceConstant a constant value used to control the strength of the structural relevance filter
+ * @param normalizeOutput a boolean indicating whether to normalize the output repetition map
+ * @param useDevice a boolean indicating whether to use the user-defined OpenCL device preference
+ * @return a float array representing the repetition map, indicating the inverted NRMSE coefficients
+ * across the image (higher values indicate greater similarity)
+ */
+ public static float[] getBlockNrmse2D(Utils.InputImage2D inputImage2D,
+ Utils.ReferenceBlock2D referenceBlock2D,
+ float relevanceConstant,
+ boolean normalizeOutput,
+ boolean useDevice)
+ {
+ IJ.log("Calculating Structural Repetition Scores...");
+
+ // Cache variables
+ int imageWidth = inputImage2D.getWidth();
+ int imageHeight = inputImage2D.getHeight();
+ int imageSize = inputImage2D.getSize();
+ int blockWidth = referenceBlock2D.getWidth();
+ int blockHeight = referenceBlock2D.getHeight();
+ int blockSize = referenceBlock2D.getSize();
+ int bRW = referenceBlock2D.getRadiusWidth();
+ int bRH = referenceBlock2D.getRadiusHeight();
+
+ // Initialize OpenCL
+ CLUtils.OpenCLResources openCLResources = CLUtils.getOpenCLResources(useDevice);
+
+ // Retrieve OpenCL context, device and queue
+ CLContext context = openCLResources.getContext();
+ CLDevice device = openCLResources.getDevice();
+ CLCommandQueue queue = openCLResources.getQueue();
+
+ // Calculate local statistics
+ CLLocalStatistics localStatistics = CLUtils.getLocalStatistics2D(context, device, queue, inputImage2D,
+ blockWidth, blockHeight, Utils.EPSILON);
+
+ // Create and fill OpenCL buffers
+ CLBuffer clBlockPixels = CLUtils.createAndFillCLBuffer(context, blockSize, READ_ONLY,
+ referenceBlock2D.getPixels());
+ queue.putWriteBuffer(clBlockPixels, true);
+
+ float[] repetitionMap = new float[imageSize];
+ CLBuffer clRepetitionMap = CLUtils.createAndFillCLBuffer(context, imageSize, READ_WRITE,
+ repetitionMap);
+
+ // Build OpenCL program
+ String programStringGetBlockNrmse2D = getResourceAsString(CLUtils.class, "kernelGetBlockNrmse2D.cl");
+ programStringGetBlockNrmse2D = replaceFirst(programStringGetBlockNrmse2D, "$WIDTH$", "" + imageWidth);
+ programStringGetBlockNrmse2D = replaceFirst(programStringGetBlockNrmse2D, "$HEIGHT$", "" + imageHeight);
+ programStringGetBlockNrmse2D = replaceFirst(programStringGetBlockNrmse2D, "$BLOCK_SIZE$", "" + blockSize);
+ programStringGetBlockNrmse2D = replaceFirst(programStringGetBlockNrmse2D, "$BW$", "" + blockWidth);
+ programStringGetBlockNrmse2D = replaceFirst(programStringGetBlockNrmse2D, "$BH$", "" + blockHeight);
+ programStringGetBlockNrmse2D = replaceFirst(programStringGetBlockNrmse2D, "$BRW$", "" + bRW);
+ programStringGetBlockNrmse2D = replaceFirst(programStringGetBlockNrmse2D, "$BRH$", "" + bRH);
+ programStringGetBlockNrmse2D = replaceFirst(programStringGetBlockNrmse2D, "$BLOCK_MEAN$", "" + referenceBlock2D.getMean());
+ programStringGetBlockNrmse2D = replaceFirst(programStringGetBlockNrmse2D, "$EPSILON$", "" + Utils.EPSILON);
+ CLProgram programGetBlockNrmse2D = context.createProgram(programStringGetBlockNrmse2D).build();
+
+ // Create OpenCL kernel and set args
+ CLKernel kernelGetBlockNrmse2D = programGetBlockNrmse2D.createCLKernel("kernelGetBlockNrmse2D");
+
+ int argn = 0;
+ kernelGetBlockNrmse2D.setArg(argn++, clBlockPixels);
+ kernelGetBlockNrmse2D.setArg(argn++, localStatistics.getCLImageArray());
+ kernelGetBlockNrmse2D.setArg(argn++, localStatistics.getCLLocalMeans());
+ kernelGetBlockNrmse2D.setArg(argn++, clRepetitionMap);
+
+ int localWorkSize = min(device.getMaxWorkGroupSize(), 256);
+ int globalWorkSize = roundUp(localWorkSize, imageSize);
+
+ queue.put2DRangeKernel(kernelGetBlockNrmse2D, 0, 0,
+ imageWidth, imageHeight,
+ 0, 0);
+ queue.finish();
+
+ // Read the repetition map back from the device
+ queue.putReadBuffer(clRepetitionMap, true);
+ for (int y=bRH; y0.0f) {
+ float rmse = repetitionMap[index];
+ if (rmse == 0.0f) { // Special case where RMSE is 0, 1/rmse would be undefined but we want perfect similarity
+ repetitionMap[index] = 1.0f;
+ } else {
+ repetitionMap[index] = 1.0f / rmse;
+ }
+ }
+ }
+ }
+
+ // Normalize repetition map (avoiding masked pixels)
+ if(normalizeOutput) {
+ repetitionMap = Utils.normalizeImage2D(repetitionMap, imageWidth, imageHeight, bRW, bRH,
+ relevanceMaskArray);
+ }
+
+ // Release memory
+ context.release();
+
+ return repetitionMap;
+ }
+
+
+ /**
+ * Computes the block-wise absolute difference of standard deviations for a 2D image using OpenCL.
+ *
+ * This method calculates the absolute difference between the standard deviation of a reference block
+ * and the local standard deviations of the input image. The resulting repetition map indicates the
+ * dissimilarity (inverted similarity) across the image, allowing for optional relevance masking
+ * based on local standard deviations and a relevance constant.
+ *
+ * @param inputImage2D the input 2D image for dissimilarity calculation
+ * @param referenceBlock2D the reference block used for comparison in the dissimilarity calculation
+ * @param relevanceConstant a constant value used to control the strength of the structural relevance filter
+ * @param normalizeOutput a boolean indicating whether to normalize the output repetition map
+ * @param useDevice a boolean indicating whether to use the user-defined OpenCL device preference
+ * @return a float array representing the repetition map, indicating the similarity based on
+ * absolute differences in standard deviations (higher values indicate greater similarity)
+ */
+ public static float[] getBlockAbsDiffStds2D(Utils.InputImage2D inputImage2D,
+ Utils.ReferenceBlock2D referenceBlock2D,
+ float relevanceConstant,
+ boolean normalizeOutput,
+ boolean useDevice)
+ {
+ IJ.log("Calculating Structural Repetition Scores...");
+
+ // Cache variables
+ int imageWidth = inputImage2D.getWidth();
+ int imageHeight = inputImage2D.getHeight();
+ int imageSize = inputImage2D.getSize();
+ int blockWidth = referenceBlock2D.getWidth();
+ int blockHeight = referenceBlock2D.getHeight();
+ int blockSize = referenceBlock2D.getSize();
+ int bRW = referenceBlock2D.getRadiusWidth();
+ int bRH = referenceBlock2D.getRadiusHeight();
+
+ // Initialize OpenCL
+ CLUtils.OpenCLResources openCLResources = CLUtils.getOpenCLResources(useDevice);
+
+ // Retrieve OpenCL context, device and queue
+ CLContext context = openCLResources.getContext();
+ CLDevice device = openCLResources.getDevice();
+ CLCommandQueue queue = openCLResources.getQueue();
+
+ // Calculate local statistics
+ CLLocalStatistics localStatistics = CLUtils.getLocalStatistics2D(context, device, queue, inputImage2D,
+ blockWidth, blockHeight, Utils.EPSILON);
+
+ // Create and fill OpenCL buffers
+ CLBuffer clBlockPixels = CLUtils.createAndFillCLBuffer(context, blockSize, READ_ONLY,
+ referenceBlock2D.getPixels());
+ queue.putWriteBuffer(clBlockPixels, true);
+
+ float[] repetitionMap = new float[imageSize];
+ CLBuffer clRepetitionMap = CLUtils.createAndFillCLBuffer(context, imageSize, READ_WRITE,
+ repetitionMap);
+
+ // Build OpenCL program
+ String programStringGetBlockAbsDiffStds2D = getResourceAsString(CLUtils.class, "kernelGetBlockAbsDiffStds2D.cl");
+ programStringGetBlockAbsDiffStds2D = replaceFirst(programStringGetBlockAbsDiffStds2D, "$WIDTH$", "" + imageWidth);
+ programStringGetBlockAbsDiffStds2D = replaceFirst(programStringGetBlockAbsDiffStds2D, "$HEIGHT$", "" + imageHeight);
+ programStringGetBlockAbsDiffStds2D = replaceFirst(programStringGetBlockAbsDiffStds2D, "$BRW$", "" + bRW);
+ programStringGetBlockAbsDiffStds2D = replaceFirst(programStringGetBlockAbsDiffStds2D, "$BRH$", "" + bRH);
+ programStringGetBlockAbsDiffStds2D = replaceFirst(programStringGetBlockAbsDiffStds2D, "$BLOCK_STD$", "" + referenceBlock2D.getStd());
+ programStringGetBlockAbsDiffStds2D = replaceFirst(programStringGetBlockAbsDiffStds2D, "$EPSILON$", "" + Utils.EPSILON);
+ CLProgram programGetBlockAbsDiffStds2D = context.createProgram(programStringGetBlockAbsDiffStds2D).build();
+
+ // Create OpenCL kernel and set args
+ CLKernel kernelGetBlockAbsDiffStds2D = programGetBlockAbsDiffStds2D.createCLKernel("kernelGetBlockAbsDiffStds2D");
+
+ int argn = 0;
+ kernelGetBlockAbsDiffStds2D.setArg(argn++, localStatistics.getCLLocalStds());
+ kernelGetBlockAbsDiffStds2D.setArg(argn++, clRepetitionMap);
+
+ int localWorkSize = min(device.getMaxWorkGroupSize(), 256);
+ int globalWorkSize = roundUp(localWorkSize, imageSize);
+
+ queue.put2DRangeKernel(kernelGetBlockAbsDiffStds2D, 0, 0, imageWidth,
+ imageHeight, 0, 0);
+ queue.finish();
+
+ // Read the repetition map back from the device
+ queue.putReadBuffer(clRepetitionMap, true);
+ for (int y=0; y0.0f) {
+ Utils.RelevanceMask relevanceMask = Utils.getRelevanceMask(inputImage2D.getImageArray(), imageWidth,
+ imageHeight, bRW, bRH, localStatistics.getLocalStds(), relevanceConstant);
+ relevanceMaskArray = relevanceMask.getRelevanceMask();
+ repetitionMap = Utils.applyMask2D(repetitionMap, imageWidth, imageHeight, relevanceMaskArray);
+ }
+
+ // Normalize repetition map (avoiding masked pixels)
+ if(normalizeOutput) {
+ repetitionMap = Utils.normalizeImage2D(repetitionMap, imageWidth, imageHeight, bRW, bRH,
+ relevanceMaskArray);
+ }
+
+ // Release memory
+ context.release();
+
+ return repetitionMap;
+ }
+
+
+ /**
+ * Calculate the 2D repetition map based on the selected metric.
+ *
+ * @param metric The selected metric for repetition calculation.
+ * @param inputImage The input image data.
+ * @param referenceBlock The reference block for comparison.
+ * @param relevanceConstant The relevance constant for calculation.
+ * @param normalizeOutput Whether to normalize the output.
+ * @param useDevice Whether to use the device from preferences.
+ * @return The calculated repetition map.
+ */
+ public static float[] calculateBlockRepetitionMap2D(String metric, Utils.InputImage2D inputImage,
+ Utils.ReferenceBlock2D referenceBlock, float relevanceConstant,
+ boolean normalizeOutput, boolean useDevice)
+ {
+ if (metric.equals(BlockRepetition2D_.METRICS[0])) {
+ return CLUtils.getBlockPearson2D(inputImage, referenceBlock, relevanceConstant, normalizeOutput, useDevice);
+ } else if (metric.equals(BlockRepetition2D_.METRICS[1])) {
+ return CLUtils.getBlockCosineSimilarity2D(inputImage, referenceBlock, relevanceConstant, normalizeOutput, useDevice);
+ } else if (metric.equals(BlockRepetition2D_.METRICS[2])) {
+ return CLUtils.getBlockSsim2D(inputImage, referenceBlock, relevanceConstant, normalizeOutput, useDevice);
+ } else if (metric.equals(BlockRepetition2D_.METRICS[3])) {
+ return CLUtils.getBlockNrmse2D(inputImage, referenceBlock, relevanceConstant, normalizeOutput, useDevice);
+ } else if (metric.equals(BlockRepetition2D_.METRICS[4])) {
+ return CLUtils.getBlockAbsDiffStds2D(inputImage, referenceBlock, relevanceConstant, normalizeOutput, useDevice);
+ } else {
+ return null; // This is here just because the method requires a return statement outside the IF/ELSE clauses.
+ }
+ }
+
+
+ // ----------------------------------------- //
+ // ---- METHODS FOR BLOCK REPETITION 3D ---- //
+ // ----------------------------------------- //
+
+ /**
+ * Calculates the local statistics (mean and standard deviation) for a 3D image using OpenCL.
+ *
+ * @param openCLResources an {@link OpenCLResources} object
+ * @param inputImage3D the input 3D image for which local statistics are computed, given as a flattened array
+ * @param blockSize the block size after removing pixels outside the inbound sphere/spheroid
+ * @param blockRadiusWidth the block radius along the width
+ * @param blockRadiusHeight the block radius along the height
+ * @param blockRadiusDepth the block radius along the depth
+ * @param EPSILON a small value to prevent division by zero in calculations
+ * @return a {@link CLLocalStatistics} object containing the calculated local means and standard deviations,
+ * as well as the OpenCL buffers used during computation
+ */
+ public static CLLocalStatistics getLocalStatistics3D(OpenCLResources openCLResources,
+ Utils.InputImage3D inputImage3D,
+ int blockSize, int blockRadiusWidth, int blockRadiusHeight,
+ int blockRadiusDepth, float EPSILON)
+ {
+
+ IJ.log("Calculating local statistics...");
+
+ // Cache variables
+ int imageWidth = inputImage3D.getWidth();
+ int imageHeight = inputImage3D.getHeight();
+ int imageDepth = inputImage3D.getDepth();
+ int imageSize = inputImage3D.getSize();
+
+ // Create and fill OpenCL buffers
+ CLBuffer clImageArray = CLUtils.createAndFillCLBuffer(openCLResources.getContext(), imageSize, READ_ONLY, inputImage3D.getImageArray());
+ openCLResources.getQueue().putWriteBuffer(clImageArray, true);
+
+ float[] localMeans = new float[inputImage3D.getSize()];
+ CLBuffer clLocalMeans = CLUtils.createAndFillCLBuffer(openCLResources.getContext(), imageSize, READ_WRITE, localMeans);
+ openCLResources.getQueue().putWriteBuffer(clLocalMeans, true);
+
+ float[] localStds = new float[inputImage3D.getSize()];
+ CLBuffer clLocalStds = CLUtils.createAndFillCLBuffer(openCLResources.getContext(), imageSize, READ_WRITE, localStds);
+ openCLResources.getQueue().putWriteBuffer(clLocalStds, true);
+
+ // Create OpenCL program
+ String programStringGetLocalStatistics3D = getResourceAsString(CLUtils.class, "kernelGetLocalStatistics3D.cl");
+ programStringGetLocalStatistics3D = replaceFirst(programStringGetLocalStatistics3D, "$WIDTH$", "" + imageWidth);
+ programStringGetLocalStatistics3D = replaceFirst(programStringGetLocalStatistics3D, "$HEIGHT$", "" + imageHeight);
+ programStringGetLocalStatistics3D = replaceFirst(programStringGetLocalStatistics3D, "$DEPTH$", "" + imageDepth);
+ programStringGetLocalStatistics3D = replaceFirst(programStringGetLocalStatistics3D, "$BLOCK_SIZE$", "" + blockSize);
+ programStringGetLocalStatistics3D = replaceFirst(programStringGetLocalStatistics3D, "$BRW$", "" + blockRadiusWidth);
+ programStringGetLocalStatistics3D = replaceFirst(programStringGetLocalStatistics3D, "$BRH$", "" + blockRadiusHeight);
+ programStringGetLocalStatistics3D = replaceFirst(programStringGetLocalStatistics3D, "$BRZ$", "" + blockRadiusDepth);
+ programStringGetLocalStatistics3D = replaceFirst(programStringGetLocalStatistics3D, "$EPSILON$", "" + EPSILON);
+ CLProgram programGetLocalStatistics3D = openCLResources.getContext().createProgram(programStringGetLocalStatistics3D).build();
+
+ // Crete OpenCL kernel and set args
+ CLKernel kernelGetLocalStatistics3D = programGetLocalStatistics3D.createCLKernel("kernelGetLocalStatistics3D");
+
+ int argn = 0;
+ kernelGetLocalStatistics3D.setArg(argn++, clImageArray);
+ kernelGetLocalStatistics3D.setArg(argn++, clLocalMeans);
+ kernelGetLocalStatistics3D.setArg(argn++, clLocalStds);
+
+ int localWorkSize = min(openCLResources.getDevice().getMaxWorkGroupSize(), 256);
+ int globalWorkSize = roundUp(localWorkSize, imageSize);
+
+ openCLResources.getQueue().put3DRangeKernel(kernelGetLocalStatistics3D,
+ 0, 0, 0,
+ imageWidth, imageHeight, imageDepth,
+ 0, 0, 0);
+ openCLResources.getQueue().finish();
+
+ // Read the local means map back from the device
+ openCLResources.getQueue().putReadBuffer(clLocalMeans, true);
+ for(int z=0; z clBlockPixels = CLUtils.createAndFillCLBuffer(context, blockSize, READ_ONLY,
+ referenceBlock3D.getPixels());
+ queue.putWriteBuffer(clBlockPixels, true);
+
+ float[] repetitionMap = new float[imageSize];
+ CLBuffer clRepetitionMap = CLUtils.createAndFillCLBuffer(context, imageSize, READ_WRITE,
+ repetitionMap);
+
+ // Build OpenCL program
+ String programStringGetBlockPearson3D = getResourceAsString(CLUtils.class, "kernelGetBlockPearson3D.cl");
+ programStringGetBlockPearson3D = replaceFirst(programStringGetBlockPearson3D, "$WIDTH$", "" + imageWidth);
+ programStringGetBlockPearson3D = replaceFirst(programStringGetBlockPearson3D, "$HEIGHT$", "" + imageHeight);
+ programStringGetBlockPearson3D = replaceFirst(programStringGetBlockPearson3D, "$DEPTH$", "" + imageDepth);
+ programStringGetBlockPearson3D = replaceFirst(programStringGetBlockPearson3D, "$BLOCK_SIZE$", "" + blockSize);
+ programStringGetBlockPearson3D = replaceFirst(programStringGetBlockPearson3D, "$BLOCK_WIDTH$", "" + blockWidth);
+ programStringGetBlockPearson3D = replaceFirst(programStringGetBlockPearson3D, "$BLOCK_HEIGHT$", "" + blockHeight);
+ programStringGetBlockPearson3D = replaceFirst(programStringGetBlockPearson3D, "$BLOCK_DEPTH$", "" + blockDepth);
+ programStringGetBlockPearson3D = replaceFirst(programStringGetBlockPearson3D, "$BRW$", "" + blockRadiusWidth);
+ programStringGetBlockPearson3D = replaceFirst(programStringGetBlockPearson3D, "$BRH$", "" + blockRadiusHeight);
+ programStringGetBlockPearson3D = replaceFirst(programStringGetBlockPearson3D, "$BRZ$", "" + blockRadiusDepth);
+ programStringGetBlockPearson3D = replaceFirst(programStringGetBlockPearson3D, "$BLOCK_MEAN$", "" + referenceBlock3D.getMean());
+ programStringGetBlockPearson3D = replaceFirst(programStringGetBlockPearson3D, "$BLOCK_STD$", "" + referenceBlock3D.getStd());
+ programStringGetBlockPearson3D = replaceFirst(programStringGetBlockPearson3D, "$EPSILON$", "" + Utils.EPSILON);
+ CLProgram programGetBlockPearson3D = context.createProgram(programStringGetBlockPearson3D).build();
+
+ // Create OpenCL kernel and set args
+ CLKernel kernelGetBlockPearson3D = programGetBlockPearson3D.createCLKernel("kernelGetBlockPearson3D");
+
+ int argn = 0;
+ kernelGetBlockPearson3D.setArg(argn++, clBlockPixels);
+ kernelGetBlockPearson3D.setArg(argn++, localStatistics.getCLImageArray());
+ kernelGetBlockPearson3D.setArg(argn++, localStatistics.getCLLocalMeans());
+ kernelGetBlockPearson3D.setArg(argn++, localStatistics.getCLLocalStds());
+ kernelGetBlockPearson3D.setArg(argn++, clRepetitionMap);
+
+ int localWorkSize = min(device.getMaxWorkGroupSize(), 256);
+ int globalWorkSize = roundUp(localWorkSize, imageSize);
+
+ queue.put3DRangeKernel(kernelGetBlockPearson3D,
+ 0, 0, 0,
+ imageWidth, imageHeight, imageDepth,
+ 0, 0, 0);
+ queue.finish();
+
+ // Read the repetition map back from the device
+ queue.putReadBuffer(clRepetitionMap, true);
+ for (int z=blockRadiusDepth; z0.0f) {
+ relevanceMaskArray = Utils.getRelevanceMask3D(imageWidth, imageHeight, imageDepth, blockRadiusWidth,
+ blockRadiusHeight, blockRadiusDepth, localStatistics.getLocalStds(), relevanceConstant);
+
+ repetitionMap = Utils.applyMask3D(repetitionMap, imageWidth, imageHeight, imageDepth, relevanceMaskArray);
+ }
+
+ // Normalize repetition map (avoiding masked pixels)
+ if(normalizeOutput) {
+ repetitionMap = Utils.normalizeImage3D(repetitionMap, imageWidth, imageHeight, imageDepth, blockRadiusWidth,
+ blockRadiusHeight, blockRadiusDepth, relevanceMaskArray);
+ }
+
+ // Release memory
+ context.release();
+
+ return repetitionMap;
+ }
+
+
+ /**
+ * Computes the block-wise cosine similarity for a 3D image using OpenCL.
+ *
+ * This method calculates the cosine similarity between a reference block and all blocks
+ * of the input image. The result is stored in a repetition map, indicating the degree
+ * of similarity across the image. Optionally, a relevance mask can be applied to filter
+ * the results based on local standard deviations and a relevance constant.
+ *
+ * @param inputImage3D the input 3D image for similarity calculation
+ * @param referenceBlock3D the reference block used for comparison in the similarity calculation
+ * @param relevanceConstant a constant value used to control the strength of the structural relevance filter
+ * @param normalizeOutput a boolean indicating whether to normalize the output repetition map
+ * @param useDevice a boolean indicating whether to use the user-defined OpenCL device preference
+ * @return a float array representing the repetition map, indicating the cosine similarity
+ * coefficients across the image
+ */
+ public static float[] getBlockCosineSimilarity3D(Utils.InputImage3D inputImage3D,
+ Utils.ReferenceBlock3D referenceBlock3D, float relevanceConstant,
+ boolean normalizeOutput, boolean useDevice)
+ {
+ // Cache variables
+ int imageWidth = inputImage3D.getWidth();
+ int imageHeight = inputImage3D.getHeight();
+ int imageSize = inputImage3D.getSize();
+ int imageDepth = inputImage3D.getDepth();
+ int blockSize = referenceBlock3D.getSize();
+ int blockRadiusWidth = referenceBlock3D.getRadiusWidth();
+ int blockRadiusHeight = referenceBlock3D.getRadiusHeight();
+ int blockRadiusDepth = referenceBlock3D.getRadiusDepth();
+
+ // Initialize OpenCL
+ CLUtils.OpenCLResources openCLResources = CLUtils.getOpenCLResources(useDevice);
+
+ // Retrieve OpenCL context, device and queue
+ CLContext context = openCLResources.getContext();
+ CLDevice device = openCLResources.getDevice();
+ CLCommandQueue queue = openCLResources.getQueue();
+
+ // Calculate local statistics
+ CLLocalStatistics localStatistics = getLocalStatistics3D(openCLResources, inputImage3D, blockSize,
+ blockRadiusWidth, blockRadiusHeight, blockRadiusDepth, Utils.EPSILON);
+
+ // Create and fill OpenCL buffers
+ IJ.log("Calculating Structural Repetition Scores...");
+
+ CLBuffer clBlockPixels = createAndFillCLBuffer(context, blockSize, READ_ONLY,
+ referenceBlock3D.getPixels());
+ queue.putWriteBuffer(clBlockPixels, true);
+
+ float[] repetitionMap = new float[imageSize]; // Create array to hold the repetition map
+ Arrays.fill(repetitionMap, 0.0f); // Fill with zeroes just be sure
+
+ CLBuffer clRepetitionMap = createAndFillCLBuffer(context, imageSize, READ_WRITE,
+ repetitionMap);
+
+ // Build OpenCL program
+ String programStringGetBlockCosineSimilarity3D = getResourceAsString(CLUtils.class, "kernelGetBlockCosineSimilarity3D.cl");
+ programStringGetBlockCosineSimilarity3D = replaceFirst(programStringGetBlockCosineSimilarity3D, "$WIDTH$", "" + imageWidth);
+ programStringGetBlockCosineSimilarity3D = replaceFirst(programStringGetBlockCosineSimilarity3D, "$HEIGHT$", "" + imageHeight);
+ programStringGetBlockCosineSimilarity3D = replaceFirst(programStringGetBlockCosineSimilarity3D, "$DEPTH$", "" + imageDepth);
+ programStringGetBlockCosineSimilarity3D = replaceFirst(programStringGetBlockCosineSimilarity3D, "$BRW$", "" + blockRadiusWidth);
+ programStringGetBlockCosineSimilarity3D = replaceFirst(programStringGetBlockCosineSimilarity3D, "$BRH$", "" + blockRadiusHeight);
+ programStringGetBlockCosineSimilarity3D = replaceFirst(programStringGetBlockCosineSimilarity3D, "$BRZ$", "" + blockRadiusDepth);
+ programStringGetBlockCosineSimilarity3D = replaceFirst(programStringGetBlockCosineSimilarity3D, "$BLOCK_STD$", "" + referenceBlock3D.getStd());
+ programStringGetBlockCosineSimilarity3D = replaceFirst(programStringGetBlockCosineSimilarity3D, "$EPSILON$", "" + Utils.EPSILON);
+ CLProgram programGetBlockCosineSimilarity3D = context.createProgram(programStringGetBlockCosineSimilarity3D).build();
+
+ // Create OpenCL kernel and set args
+ CLKernel kernelGetBlockCosineSimilarity3D = programGetBlockCosineSimilarity3D.createCLKernel("kernelGetBlockCosineSimilarity3D");
+
+ int argn = 0;
+ kernelGetBlockCosineSimilarity3D.setArg(argn++, localStatistics.getCLLocalStds());
+ kernelGetBlockCosineSimilarity3D.setArg(argn++, clRepetitionMap);
+
+ int localWorkSize = min(device.getMaxWorkGroupSize(), 256);
+ int globalWorkSize = roundUp(localWorkSize, imageSize);
+
+ queue.put3DRangeKernel(kernelGetBlockCosineSimilarity3D,
+ 0, 0, 0,
+ imageWidth, imageHeight, imageDepth,
+ 0, 0, 0);
+
+ queue.finish();
+
+ // Read the repetition map back from the device
+ queue.putReadBuffer(clRepetitionMap, true);
+ for(int z=blockRadiusDepth; z0.0f) {
+ relevanceMaskArray = Utils.getRelevanceMask3D(imageWidth, imageHeight, imageDepth, blockRadiusWidth,
+ blockRadiusHeight, blockRadiusDepth, localStatistics.getLocalStds(), relevanceConstant);
+
+ repetitionMap = Utils.applyMask3D(repetitionMap, imageWidth, imageHeight, imageDepth, relevanceMaskArray);
+ }
+
+ // Normalize repetition map (avoiding masked pixels)
+ if(normalizeOutput) {
+ repetitionMap = Utils.normalizeImage3D(repetitionMap, imageWidth, imageHeight, imageDepth, blockRadiusWidth,
+ blockRadiusHeight, blockRadiusDepth, relevanceMaskArray);
+ }
+
+ // Release memory
+ context.release();
+
+ return repetitionMap;
+ }
+
+
+ /**
+ * Computes the block-wise Structural Similarity Index (SSIM) for a 3D image using OpenCL.
+ *
+ * This method calculates the SSIM between a reference block and all blocks of the input image,
+ * generating a repetition map that indicates the similarity strength across the image.
+ * Optionally, a relevance mask can be applied to filter the results based on local
+ * standard deviations and a relevance constant.
+ *
+ * @param inputImage3D the input 3D image for SSIM calculation
+ * @param referenceBlock3D the reference block used for comparison in the SSIM calculation
+ * @param relevanceConstant a constant value used to control the strength of the structural relevance filter
+ * @param normalizeOutput a boolean indicating whether to normalize the output repetition map
+ * @param useDevice a boolean indicating whether to use the user-defined OpenCL device preference
+ * @return a float array representing the repetition map, indicating the SSIM coefficients
+ * across the image
+ */
+ public static float[] getBlockSsim3D(Utils.InputImage3D inputImage3D, Utils.ReferenceBlock3D referenceBlock3D,
+ float relevanceConstant, boolean normalizeOutput, boolean useDevice)
+ {
+ IJ.log("Calculating Structural Repetition Scores...");
+
+ // Cache variables
+ int imageWidth = inputImage3D.getWidth();
+ int imageHeight = inputImage3D.getHeight();
+ int imageDepth = inputImage3D.getDepth();
+ int imageSize = inputImage3D.getSize();
+ int blockWidth = referenceBlock3D.getWidth();
+ int blockHeight = referenceBlock3D.getHeight();
+ int blockDepth = referenceBlock3D.getDepth();
+ int blockSize = referenceBlock3D.getSize();
+ int blockRadiusWidth = referenceBlock3D.getRadiusWidth();
+ int blockRadiusHeight = referenceBlock3D.getRadiusHeight();
+ int blockRadiusDepth = referenceBlock3D.getRadiusDepth();
+
+ // Initialize OpenCL
+ CLUtils.OpenCLResources openCLResources = CLUtils.getOpenCLResources(useDevice);
+
+ // Retrieve OpenCL context, device and queue
+ CLContext context = openCLResources.getContext();
+ CLDevice device = openCLResources.getDevice();
+ CLCommandQueue queue = openCLResources.getQueue();
+
+ // Calculate local statistics
+ CLLocalStatistics localStatistics = CLUtils.getLocalStatistics3D(openCLResources, inputImage3D, blockSize,
+ blockRadiusWidth, blockRadiusHeight, blockRadiusDepth, Utils.EPSILON);
+
+ // Create and fill OpenCL buffers
+ CLBuffer clBlockPixels = CLUtils.createAndFillCLBuffer(context, blockSize, READ_ONLY,
+ referenceBlock3D.getPixels());
+ queue.putWriteBuffer(clBlockPixels, true);
+
+ float[] repetitionMap = new float[imageSize];
+ Arrays.fill(repetitionMap, 0.0f);
+ CLBuffer clRepetitionMap = CLUtils.createAndFillCLBuffer(context, imageSize, READ_WRITE,
+ repetitionMap);
+
+ // Build OpenCL program
+ String programStringGetBlockSsim3D = getResourceAsString(CLUtils.class, "kernelGetBlockSsim3D.cl");
+ programStringGetBlockSsim3D = replaceFirst(programStringGetBlockSsim3D, "$WIDTH$", "" + imageWidth);
+ programStringGetBlockSsim3D = replaceFirst(programStringGetBlockSsim3D, "$HEIGHT$", "" + imageHeight);
+ programStringGetBlockSsim3D = replaceFirst(programStringGetBlockSsim3D, "$DEPTH$", "" + imageDepth);
+ programStringGetBlockSsim3D = replaceFirst(programStringGetBlockSsim3D, "$BLOCK_SIZE$", "" + blockSize);
+ programStringGetBlockSsim3D = replaceFirst(programStringGetBlockSsim3D, "$BW$", "" + blockWidth);
+ programStringGetBlockSsim3D = replaceFirst(programStringGetBlockSsim3D, "$BH$", "" + blockHeight);
+ programStringGetBlockSsim3D = replaceFirst(programStringGetBlockSsim3D, "$BZ$", "" + blockDepth);
+ programStringGetBlockSsim3D = replaceFirst(programStringGetBlockSsim3D, "$BRW$", "" + blockWidth);
+ programStringGetBlockSsim3D = replaceFirst(programStringGetBlockSsim3D, "$BRH$", "" + blockRadiusHeight);
+ programStringGetBlockSsim3D = replaceFirst(programStringGetBlockSsim3D, "$BRZ$", "" + blockRadiusDepth);
+ programStringGetBlockSsim3D = replaceFirst(programStringGetBlockSsim3D, "$BLOCK_MEAN$", "" + referenceBlock3D.getMean());
+ programStringGetBlockSsim3D = replaceFirst(programStringGetBlockSsim3D, "$BLOCK_STD$", "" + referenceBlock3D.getStd());
+ programStringGetBlockSsim3D = replaceFirst(programStringGetBlockSsim3D, "$EPSILON$", "" + Utils.EPSILON);
+ CLProgram programGetBlockSsim3D = context.createProgram(programStringGetBlockSsim3D).build();
+
+ // Create OpenCL kernel and set args
+ CLKernel kernelGetBlockSsim3D = programGetBlockSsim3D.createCLKernel("kernelGetBlockSsim3D");
+
+ int argn = 0;
+ kernelGetBlockSsim3D.setArg(argn++, clBlockPixels);
+ kernelGetBlockSsim3D.setArg(argn++, localStatistics.getCLImageArray());
+ kernelGetBlockSsim3D.setArg(argn++, localStatistics.getCLLocalMeans());
+ kernelGetBlockSsim3D.setArg(argn++, localStatistics.getCLLocalStds());
+ kernelGetBlockSsim3D.setArg(argn++, clRepetitionMap);
+
+ int localWorkSize = min(device.getMaxWorkGroupSize(), 256);
+
+ queue.put3DRangeKernel(kernelGetBlockSsim3D, 0, 0, 0,
+ imageWidth, imageHeight, imageDepth,
+ 0, 0, 0);
+ queue.finish();
+
+ // Read the repetition map back from the device
+ queue.putReadBuffer(clRepetitionMap, true);
+ for(int z=blockRadiusDepth; z0.0f) {
+ relevanceMaskArray = Utils.getRelevanceMask3D(imageWidth, imageHeight, imageDepth, blockRadiusWidth,
+ blockRadiusHeight, blockRadiusDepth, localStatistics.getLocalStds(), relevanceConstant);
+
+ repetitionMap = Utils.applyMask3D(repetitionMap, imageWidth, imageHeight, imageDepth, relevanceMaskArray);
+ }
+
+ // Normalize repetition map (avoiding masked pixels)
+ if(normalizeOutput) {
+ repetitionMap = Utils.normalizeImage3D(repetitionMap, imageWidth, imageHeight, imageDepth, blockRadiusWidth,
+ blockRadiusHeight, blockRadiusDepth, relevanceMaskArray);
+ }
+
+ // Release memory
+ context.release();
+
+ return repetitionMap;
+ }
+
+
+ /**
+ * Computes the block-wise Normalized Root Mean Square Error (NRMSE) for a 3D image using OpenCL.
+ *
+ * This method calculates the NRMSE between a reference block and blocks of the input image,
+ * generating a repetition map that indicates the similarity (inverted NRMSE) across the image.
+ * It allows for optional relevance masking based on local standard deviations and a relevance constant.
+ *
+ * @param inputImage3D the input 3D image for NRMSE calculation
+ * @param referenceBlock3D the reference block used for comparison in the NRMSE calculation
+ * @param relevanceConstant a constant value used to control the strength of the structural relevance filter
+ * @param normalizeOutput a boolean indicating whether to normalize the output repetition map
+ * @param useDevice a boolean indicating whether to use the user-defined OpenCL device preference
+ * @return a float array representing the repetition map, indicating the inverted NRMSE coefficients
+ * across the image (higher values indicate greater similarity)
+ */
+ public static float[] getBlockNrmse3D(Utils.InputImage3D inputImage3D, Utils.ReferenceBlock3D referenceBlock3D,
+ float relevanceConstant, boolean normalizeOutput, boolean useDevice)
+ {
+ IJ.log("Calculating Structural Repetition Scores...");
+
+ // Cache variables
+ int imageWidth = inputImage3D.getWidth();
+ int imageHeight = inputImage3D.getHeight();
+ int imageDepth = inputImage3D.getDepth();
+ int imageSize = inputImage3D.getSize();
+ int blockSize = referenceBlock3D.getSize();
+ int blockRadiusWidth = referenceBlock3D.getRadiusWidth();
+ int blockRadiusHeight = referenceBlock3D.getRadiusHeight();
+ int blockRadiusDepth = referenceBlock3D.getRadiusDepth();
+
+ // Initialize OpenCL
+ CLUtils.OpenCLResources openCLResources = CLUtils.getOpenCLResources(useDevice);
+
+ // Retrieve OpenCL context, device and queue
+ CLContext context = openCLResources.getContext();
+ CLDevice device = openCLResources.getDevice();
+ CLCommandQueue queue = openCLResources.getQueue();
+
+ // Calculate local statistics
+ CLLocalStatistics localStatistics = CLUtils.getLocalStatistics3D(openCLResources, inputImage3D, blockSize,
+ blockRadiusWidth, blockRadiusHeight, blockRadiusDepth, Utils.EPSILON);
+
+ // Create and fill OpenCL buffers
+ CLBuffer clBlockPixels = CLUtils.createAndFillCLBuffer(context, blockSize, READ_ONLY,
+ referenceBlock3D.getPixels());
+ queue.putWriteBuffer(clBlockPixels, true);
+
+ float[] repetitionMap = new float[imageSize];
+ Arrays.fill(repetitionMap, 0.0f);
+ CLBuffer clRepetitionMap = CLUtils.createAndFillCLBuffer(context, imageSize, READ_WRITE,
+ repetitionMap);
+
+ // Build OpenCL program
+ String programStringGetBlockNrmse3D = getResourceAsString(CLUtils.class, "kernelGetBlockNrmse3D.cl");
+ programStringGetBlockNrmse3D = replaceFirst(programStringGetBlockNrmse3D, "$WIDTH$", "" + imageWidth);
+ programStringGetBlockNrmse3D = replaceFirst(programStringGetBlockNrmse3D, "$HEIGHT$", "" + imageHeight);
+ programStringGetBlockNrmse3D = replaceFirst(programStringGetBlockNrmse3D, "$DEPTH$", "" + imageDepth);
+ programStringGetBlockNrmse3D = replaceFirst(programStringGetBlockNrmse3D, "$BLOCK_SIZE$", "" + blockSize);
+ programStringGetBlockNrmse3D = replaceFirst(programStringGetBlockNrmse3D, "$BRW$", "" + blockRadiusWidth);
+ programStringGetBlockNrmse3D = replaceFirst(programStringGetBlockNrmse3D, "$BRH$", "" + blockRadiusHeight);
+ programStringGetBlockNrmse3D = replaceFirst(programStringGetBlockNrmse3D, "$BRZ$", "" + blockRadiusDepth);
+ programStringGetBlockNrmse3D = replaceFirst(programStringGetBlockNrmse3D, "$EPSILON$", "" + Utils.EPSILON);
+ CLProgram programGetBlockNrmse3D = context.createProgram(programStringGetBlockNrmse3D).build();
+
+ // Create OpenCL kernel and set args
+ CLKernel kernelGetBlockNrmse3D = programGetBlockNrmse3D.createCLKernel("kernelGetBlockNrmse3D");
+
+ int argn = 0;
+ kernelGetBlockNrmse3D.setArg(argn++, clBlockPixels);
+ kernelGetBlockNrmse3D.setArg(argn++, localStatistics.getCLImageArray());
+ kernelGetBlockNrmse3D.setArg(argn++, localStatistics.getCLLocalMeans());
+ kernelGetBlockNrmse3D.setArg(argn++, clRepetitionMap);
+
+ queue.put3DRangeKernel(kernelGetBlockNrmse3D, 0, 0, 0,
+ imageWidth, imageHeight, imageDepth,
+ 0, 0, 0);
+ queue.finish();
+
+ // Read the repetition map back from the device
+ queue.putReadBuffer(clRepetitionMap, true);
+ for(int z=blockRadiusDepth; z0.0f) {
+ relevanceMask = Utils.getRelevanceMask3D(imageWidth, imageHeight, imageDepth, blockRadiusWidth,
+ blockRadiusHeight, blockRadiusDepth, localStatistics.getLocalStds(), relevanceConstant);
+
+ repetitionMap = Utils.applyMask3D(repetitionMap, imageWidth, imageHeight, imageDepth, relevanceMask);
+ }
+
+ // Normalize repetition map (avoiding masked pixels)
+ if(normalizeOutput) {
+ if(relevanceConstant>0.0f) {
+ repetitionMap = Utils.normalizeImage3D(repetitionMap,
+ imageWidth, imageHeight, imageDepth, blockRadiusWidth, blockRadiusHeight, blockRadiusDepth,
+ relevanceMask);
+ }else{
+ repetitionMap = Utils.normalizeImage3D(repetitionMap,
+ imageWidth, imageHeight, imageDepth, blockRadiusWidth, blockRadiusHeight, blockRadiusDepth,
+ null);
+ }
+ }
+
+ // Release memory
+ context.release();
+
+ return repetitionMap;
+ }
+
+
+ /**
+ * Calculate the 3D repetition map based on the selected metric.
+ *
+ * @param metric The selected metric for repetition calculation.
+ * @param inputImage The input image data.
+ * @param referenceBlock The reference block for comparison.
+ * @param relevanceConstant The relevance constant for calculation.
+ * @param normalizeOutput Whether to normalize the output.
+ * @param useDevice Whether to use the device from preferences.
+ * @return The calculated repetition map.
+ */
+ public static float[] calculateBlockRepetitionMap3D(String metric, Utils.InputImage3D inputImage,
+ Utils.ReferenceBlock3D referenceBlock, float relevanceConstant,
+ boolean normalizeOutput, boolean useDevice)
+ {
+ if (metric.equals(BlockRepetition3D_.METRICS[0])) {
+ return CLUtils.getBlockPearson3D(inputImage, referenceBlock, relevanceConstant, normalizeOutput, useDevice);
+ } else if (metric.equals(BlockRepetition3D_.METRICS[1])) {
+ return CLUtils.getBlockCosineSimilarity3D(inputImage, referenceBlock, relevanceConstant, normalizeOutput, useDevice);
+ } else if (metric.equals(BlockRepetition3D_.METRICS[2])) {
+ return CLUtils.getBlockSsim3D(inputImage, referenceBlock, relevanceConstant, normalizeOutput, useDevice);
+ } else if (metric.equals(BlockRepetition3D_.METRICS[3])) {
+ return CLUtils.getBlockNrmse3D(inputImage, referenceBlock, relevanceConstant, normalizeOutput, useDevice);
+ //} else if (metric.equals(BlockRepetition3D_.METRICS[4])) {
+ //return CLUtils.getBlockAbsDiffStds3D(inputImage, referenceBlock, relevanceConstant, normalizeOutput, useDevice);
+ } else {
+ return null; // This is here just because the method requires a return statement outside the IF clauses.
+ }
+ }
+
+
+ // ------------------------------------------ //
+ // ---- METHODS FOR GLOBAL REPETITION 2D ---- //
+ // ------------------------------------------ //
+
+ /**
+ * Computes the Global Repetition Map based on Pearson correlations for a 2D image using OpenCL.
+ *
+ * This method calculates the global repetition map based on Pearson correlations. The result is stored in a global
+ * repetition map, indicating the relative repetition of each structural element across the image.
+ * Optionally, a relevance mask can be applied to filter the results based on local standard deviations and a
+ * relevance constant.
+ *
+ * @param inputImage a {@link Utils.InputImage2D} object
+ * @param localStatistics a {@link CLLocalStatistics} object
+ * @param blockWidth the width of the block used for the analysis
+ * @param blockHeight the height of the block used for the analysis
+ * @param blockSize the size of the block used for the analysis, after removing pixels outside the inbound circle/ellipse
+ * @param relevanceMask a {@link Utils.RelevanceMask} object
+ * @param nPixels the number of structurally relevant pixels (i.e., non-masked pixels)
+ * @param normalizeOutput a boolean to either normalize the output or not
+ * @param openCLResources an {@link OpenCLResources} object
+ * @return a float array representing the global repetition map, indicating the degree of repetition of each
+ * structural element across the entire image.
+ */
+ public static float[] getGlobalPearson2D(Utils.InputImage2D inputImage, CLLocalStatistics localStatistics,
+ int blockWidth, int blockHeight, int blockSize,
+ Utils.RelevanceMask relevanceMask, float nPixels, boolean normalizeOutput,
+ OpenCLResources openCLResources
+ ){
+ IJ.log("Calculating Structural Repetition Scores...");
+
+ // Cache variables
+ int imageWidth = inputImage.getWidth();
+ int imageHeight = inputImage.getHeight();
+ int imageSize = inputImage.getSize();
+ int blockRadiusWidth = blockWidth/2;
+ int blockRadiusHeight = blockHeight/2;
+
+ // Create and fill OpenCL buffers
+ float[] repetitionMap = new float[imageSize];
+ CLBuffer clRepetitionMap = CLUtils.createAndFillCLBuffer(openCLResources.getContext(), imageSize,
+ READ_WRITE, repetitionMap);
+
+ float[] weightsSumMap = new float[imageSize];
+ CLBuffer clWeightsSumMap = CLUtils.createAndFillCLBuffer(openCLResources.getContext(), imageSize,
+ READ_WRITE, weightsSumMap);
+
+ // Build OpenCL program
+ String programStringGetGlobalPearson2D = getResourceAsString(CLUtils.class, "kernelGetGlobalPearson2D.cl");
+ programStringGetGlobalPearson2D = replaceFirst(programStringGetGlobalPearson2D, "$WIDTH$", "" + imageWidth);
+ programStringGetGlobalPearson2D = replaceFirst(programStringGetGlobalPearson2D, "$HEIGHT$", "" + imageHeight);
+ programStringGetGlobalPearson2D = replaceFirst(programStringGetGlobalPearson2D, "$BLOCK_SIZE$", "" + blockSize);
+ programStringGetGlobalPearson2D = replaceFirst(programStringGetGlobalPearson2D, "$BRW$", "" + blockRadiusWidth);
+ programStringGetGlobalPearson2D = replaceFirst(programStringGetGlobalPearson2D, "$BRH$", "" + blockRadiusHeight);
+ programStringGetGlobalPearson2D = replaceFirst(programStringGetGlobalPearson2D, "$FILTER_PARAM$", "" + relevanceMask.getNoiseMeanVariance());
+ programStringGetGlobalPearson2D = replaceFirst(programStringGetGlobalPearson2D, "$THRESHOLD$", "" + relevanceMask.getRelevanceThreshold());
+ programStringGetGlobalPearson2D = replaceFirst(programStringGetGlobalPearson2D, "$EPSILON$", "" + Utils.EPSILON);
+ CLProgram programGetGlobalPearson2D = openCLResources.getContext().createProgram(programStringGetGlobalPearson2D).build();
+
+ // Create OpenCL kernel and set args
+ CLKernel kernelGetGlobalPearson2D = programGetGlobalPearson2D.createCLKernel("kernelGetGlobalPearson2D");
+
+ int argn = 0;
+ kernelGetGlobalPearson2D.setArg(argn++, localStatistics.getCLImageArray());
+ kernelGetGlobalPearson2D.setArg(argn++, localStatistics.getCLLocalMeans());
+ kernelGetGlobalPearson2D.setArg(argn++, localStatistics.getCLLocalStds());
+ kernelGetGlobalPearson2D.setArg(argn++, clWeightsSumMap);
+ kernelGetGlobalPearson2D.setArg(argn++, clRepetitionMap);
+
+
+ // Calculate weighted mean Pearson's map
+ int nXBlocks = imageWidth/64 + ((imageWidth%64==0)?0:1);
+ int nYBlocks = imageHeight/64 + ((imageHeight%64==0)?0:1);
+ int totalBlocks = nXBlocks * nYBlocks; // Total number of blocks
+
+ for (int nYB=0; nYB0.0f) {
+ repetitionMap = Utils.applyMask2D(repetitionMap, imageWidth, imageHeight, relevanceMask.getRelevanceMask());
+ }
+
+ // Normalize repetition map (avoiding masked pixels)
+ if(normalizeOutput) {
+ repetitionMap = Utils.normalizeImage2D(repetitionMap, imageWidth, imageHeight, blockRadiusWidth,
+ blockRadiusHeight, relevanceMask.getRelevanceMask());
+ }
+
+ // Release memory
+ openCLResources.getContext().release();
+
+ return repetitionMap;
+ }
+
+
+ /**
+ * Computes the Global Repetition Map based on the Cosine Similarity metric for a 2D image using OpenCL.
+ *
+ * This method calculates the global repetition map based on the Cosine Similarity metric. The result is stored in a global
+ * repetition map, indicating the relative repetition of each structural element across the image.
+ * Optionally, a relevance mask can be applied to filter the results based on local standard deviations and a
+ * relevance constant.
+ *
+ * @param inputImage a {@link Utils.InputImage2D} object
+ * @param localStatistics a {@link CLLocalStatistics} object
+ * @param blockWidth the width of the block used for the analysis
+ * @param blockHeight the height of the block used for the analysis
+ * @param relevanceMask a {@link Utils.RelevanceMask} object
+ * @param nPixels the number of structurally relevant pixels (i.e., non-masked pixels)
+ * @param normalizeOutput a boolean to either normalize the output or not
+ * @param openCLResources an {@link OpenCLResources} object
+ * @return a float array representing the global repetition map, indicating the degree of repetition of each
+ * structural element across the entire image.
+ */
+ public static float[] getGlobalCosineSimilarity2D(Utils.InputImage2D inputImage, CLLocalStatistics localStatistics,
+ int blockWidth, int blockHeight, Utils.RelevanceMask relevanceMask,
+ float nPixels, boolean normalizeOutput, OpenCLResources openCLResources)
+ {
+ IJ.log("Calculating Structural Repetition Scores...");
+
+ // Cache variables
+ int imageWidth = inputImage.getWidth();
+ int imageHeight = inputImage.getHeight();
+ int imageSize = inputImage.getSize();
+ int blockRadiusWidth = blockWidth/2;
+ int blockRadiusHeight = blockHeight/2;
+
+ // Create and fill OpenCL buffers
+ float[] repetitionMap = new float[imageSize];
+ CLBuffer clRepetitionMap = CLUtils.createAndFillCLBuffer(openCLResources.getContext(), imageSize,
+ READ_WRITE, repetitionMap);
+
+ float[] weightsSumMap = new float[imageSize];
+ CLBuffer clWeightsSumMap = CLUtils.createAndFillCLBuffer(openCLResources.getContext(), imageSize,
+ READ_WRITE, weightsSumMap);
+
+ // Build OpenCL program
+ String programStringGetGlobalCosineSimilarity2D = getResourceAsString(CLUtils.class, "kernelGetGlobalCosineSimilarity2D.cl");
+ programStringGetGlobalCosineSimilarity2D = replaceFirst(programStringGetGlobalCosineSimilarity2D, "$WIDTH$", "" + imageWidth);
+ programStringGetGlobalCosineSimilarity2D = replaceFirst(programStringGetGlobalCosineSimilarity2D, "$HEIGHT$", "" + imageHeight);
+ programStringGetGlobalCosineSimilarity2D = replaceFirst(programStringGetGlobalCosineSimilarity2D, "$BRW$", "" + blockRadiusWidth);
+ programStringGetGlobalCosineSimilarity2D = replaceFirst(programStringGetGlobalCosineSimilarity2D, "$BRH$", "" + blockRadiusHeight);
+ programStringGetGlobalCosineSimilarity2D = replaceFirst(programStringGetGlobalCosineSimilarity2D, "$FILTER_PARAM$", "" + relevanceMask.getNoiseMeanVariance());
+ programStringGetGlobalCosineSimilarity2D = replaceFirst(programStringGetGlobalCosineSimilarity2D, "$THRESHOLD$", "" + relevanceMask.getRelevanceThreshold());
+ programStringGetGlobalCosineSimilarity2D = replaceFirst(programStringGetGlobalCosineSimilarity2D, "$EPSILON$", "" + Utils.EPSILON);
+ CLProgram programGetGlobalCosineSimilarity2D = openCLResources.getContext().createProgram(programStringGetGlobalCosineSimilarity2D).build();
+
+ // Create OpenCL kernel and set args
+ CLKernel kernelGetGlobalCosineSimilarity2D = programGetGlobalCosineSimilarity2D.createCLKernel("kernelGetGlobalCosineSimilarity2D");
+
+ int argn = 0;
+ kernelGetGlobalCosineSimilarity2D.setArg(argn++, localStatistics.getCLImageArray());
+ kernelGetGlobalCosineSimilarity2D.setArg(argn++, localStatistics.getCLLocalStds());
+ kernelGetGlobalCosineSimilarity2D.setArg(argn++, clWeightsSumMap);
+ kernelGetGlobalCosineSimilarity2D.setArg(argn++, clRepetitionMap);
+
+ // Calculate weighted mean Pearson's map
+ int nXBlocks = imageWidth/64 + ((imageWidth%64==0)?0:1);
+ int nYBlocks = imageHeight/64 + ((imageHeight%64==0)?0:1);
+ int totalBlocks = nXBlocks * nYBlocks; // Total number of blocks
+
+ for (int nYB=0; nYB0.0f) {
+ repetitionMap = Utils.applyMask2D(repetitionMap, imageWidth, imageHeight, relevanceMask.getRelevanceMask());
+ }
+
+ // Normalize repetition map (avoiding masked pixels)
+ if(normalizeOutput) {
+ repetitionMap = Utils.normalizeImage2D(repetitionMap, imageWidth, imageHeight, blockRadiusWidth,
+ blockRadiusHeight, relevanceMask.getRelevanceMask());
+ }
+
+ // Release memory
+ openCLResources.getContext().release();
+
+ return repetitionMap;
+ }
+
+
+ /**
+ * Computes the Global Repetition Map based on the Structural Similarity Index Metric (SSIM) for a 2D image using OpenCL.
+ *
+ * This method calculates the global repetition map based on the Structural Similarity Index Metric (SSIM). The result is stored in a global
+ * repetition map, indicating the relative repetition of each structural element across the image.
+ * Optionally, a relevance mask can be applied to filter the results based on local standard deviations and a
+ * relevance constant.
+ *
+ * @param inputImage a {@link Utils.InputImage2D} object
+ * @param localStatistics a {@link CLLocalStatistics} object
+ * @param blockWidth the width of the block used for the analysis
+ * @param blockHeight the height of the block used for the analysis
+ * @param blockSize the size of the block (in pixels) after removing pixels outside the inbound circle/ellipse
+ * @param relevanceMask a {@link Utils.RelevanceMask} object
+ * @param nPixels the number of structurally relevant pixels (i.e., non-masked pixels)
+ * @param normalizeOutput a boolean to either normalize the output or not
+ * @param openCLResources an OpenCLResources object
+ * @return a float array representing the global repetition map, indicating the degree of repetition of each
+ * structural element across the entire image.
+ */
+ public static float[] getGlobalSsim2D(Utils.InputImage2D inputImage, CLLocalStatistics localStatistics,
+ int blockWidth, int blockHeight, int blockSize,
+ Utils.RelevanceMask relevanceMask, float nPixels, boolean normalizeOutput,
+ OpenCLResources openCLResources)
+ {
+ IJ.log("Calculating Structural Repetition Scores...");
+
+ // Cache variables
+ int imageWidth = inputImage.getWidth();
+ int imageHeight = inputImage.getHeight();
+ int imageSize = inputImage.getSize();
+ int blockRadiusWidth = blockWidth/2;
+ int blockRadiusHeight = blockHeight/2;
+
+ // Create and fill OpenCL buffers
+ float[] repetitionMap = new float[imageSize];
+ CLBuffer clRepetitionMap = CLUtils.createAndFillCLBuffer(openCLResources.getContext(), imageSize,
+ READ_WRITE, repetitionMap);
+
+ float[] weightsSumMap = new float[imageSize];
+ CLBuffer clWeightsSumMap = CLUtils.createAndFillCLBuffer(openCLResources.getContext(), imageSize,
+ READ_WRITE, weightsSumMap);
+
+ // Build OpenCL program
+ String programStringGetGlobalSsim2D = getResourceAsString(CLUtils.class, "kernelGetGlobalSsim2D.cl");
+ programStringGetGlobalSsim2D = replaceFirst(programStringGetGlobalSsim2D, "$WIDTH$", "" + imageWidth);
+ programStringGetGlobalSsim2D = replaceFirst(programStringGetGlobalSsim2D, "$HEIGHT$", "" + imageHeight);
+ programStringGetGlobalSsim2D = replaceFirst(programStringGetGlobalSsim2D, "$BLOCK_SIZE$", "" + blockSize);
+ programStringGetGlobalSsim2D = replaceFirst(programStringGetGlobalSsim2D, "$BRW$", "" + blockRadiusWidth);
+ programStringGetGlobalSsim2D = replaceFirst(programStringGetGlobalSsim2D, "$BRH$", "" + blockRadiusHeight);
+ programStringGetGlobalSsim2D = replaceFirst(programStringGetGlobalSsim2D, "$FILTER_PARAM$", "" + relevanceMask.getNoiseMeanVariance());
+ programStringGetGlobalSsim2D = replaceFirst(programStringGetGlobalSsim2D, "$THRESHOLD$", "" + relevanceMask.getRelevanceThreshold());
+ programStringGetGlobalSsim2D = replaceFirst(programStringGetGlobalSsim2D, "$EPSILON$", "" + Utils.EPSILON);
+ CLProgram programGetGlobalSsim2D = openCLResources.getContext().createProgram(programStringGetGlobalSsim2D).build();
+
+ // Create OpenCL kernel and set args
+ CLKernel kernelGetGlobalSsim2D = programGetGlobalSsim2D.createCLKernel("kernelGetGlobalSsim2D");
+
+ int argn = 0;
+ kernelGetGlobalSsim2D.setArg(argn++, localStatistics.getCLImageArray());
+ kernelGetGlobalSsim2D.setArg(argn++, localStatistics.getCLLocalMeans());
+ kernelGetGlobalSsim2D.setArg(argn++, localStatistics.getCLLocalStds());
+ kernelGetGlobalSsim2D.setArg(argn++, clWeightsSumMap);
+ kernelGetGlobalSsim2D.setArg(argn++, clRepetitionMap);
+
+ // Calculate weighted mean Pearson's map
+ int nXBlocks = imageWidth/64 + ((imageWidth%64==0)?0:1);
+ int nYBlocks = imageHeight/64 + ((imageHeight%64==0)?0:1);
+ int totalBlocks = nXBlocks * nYBlocks; // Total number of blocks
+
+ for (int nYB=0; nYB0.0f) {
+ repetitionMap = Utils.applyMask2D(repetitionMap, imageWidth, imageHeight, relevanceMask.getRelevanceMask());
+ }
+
+ // Normalize repetition map (avoiding masked pixels)
+ if(normalizeOutput) {
+ repetitionMap = Utils.normalizeImage2D(repetitionMap, imageWidth, imageHeight, blockRadiusWidth,
+ blockRadiusHeight, relevanceMask.getRelevanceMask());
+ }
+
+ // Release memory
+ openCLResources.getContext().release();
+
+ return repetitionMap;
+ }
+
+
+ /**
+ * Computes the Global Repetition Map based on the Normalized Root Mean Squared Error (NRMSE) for a 2D image using OpenCL.
+ *
+ * This method calculates the global repetition map based on the (inverted) Normalized Root Mean Squared Error (NRMSE). The result is stored in a global
+ * repetition map, indicating the relative repetition of each structural element across the image.
+ * Optionally, a relevance mask can be applied to filter the results based on local standard deviations and a
+ * relevance constant.
+ *
+ * @param inputImage a {@link Utils.InputImage2D} object
+ * @param localStatistics a {@link CLLocalStatistics} object
+ * @param blockWidth the width of the block used for the analysis
+ * @param blockHeight the height of the block used for the analysis
+ * @param blockSize the size of the block used for the anaysis, in pixels, after removing the pixels outside the inbound circle/ellipse
+ * @param relevanceMask a {@link Utils.RelevanceMask} object
+ * @param nPixels the number of structurally relevant pixels (i.e., non-masked pixels)
+ * @param normalizeOutput a boolean to either normalize the output or not
+ * @param openCLResources an {@link OpenCLResources} object
+ * @return a float array representing the global repetition map, indicating the degree of repetition of each
+ * structural element across the entire image.
+ */
+ public static float[] getGlobalNrmse2D(Utils.InputImage2D inputImage, CLLocalStatistics localStatistics,
+ int blockWidth, int blockHeight, int blockSize,
+ Utils.RelevanceMask relevanceMask, float nPixels, boolean normalizeOutput,
+ OpenCLResources openCLResources)
+ {
+ IJ.log("Calculating Structural Repetition Scores...");
+
+ // Cache variables
+ int imageWidth = inputImage.getWidth();
+ int imageHeight = inputImage.getHeight();
+ int imageSize = inputImage.getSize();
+ int blockRadiusWidth = blockWidth/2;
+ int blockRadiusHeight = blockHeight/2;
+
+ // Create and fill OpenCL buffers
+ float[] repetitionMap = new float[imageSize];
+ CLBuffer clRepetitionMap = CLUtils.createAndFillCLBuffer(openCLResources.getContext(), imageSize,
+ READ_WRITE, repetitionMap);
+
+ float[] weightsSumMap = new float[imageSize];
+ CLBuffer clWeightsSumMap = CLUtils.createAndFillCLBuffer(openCLResources.getContext(), imageSize,
+ READ_WRITE, weightsSumMap);
+
+ // Build OpenCL program
+ String programStringGetGlobalNrmse2D = getResourceAsString(CLUtils.class, "kernelGetGlobalNrmse2D.cl");
+ programStringGetGlobalNrmse2D = replaceFirst(programStringGetGlobalNrmse2D, "$WIDTH$", "" + imageWidth);
+ programStringGetGlobalNrmse2D = replaceFirst(programStringGetGlobalNrmse2D, "$HEIGHT$", "" + imageHeight);
+ programStringGetGlobalNrmse2D = replaceFirst(programStringGetGlobalNrmse2D, "$BLOCK_SIZE$", "" + blockSize);
+ programStringGetGlobalNrmse2D = replaceFirst(programStringGetGlobalNrmse2D, "$BRW$", "" + blockRadiusWidth);
+ programStringGetGlobalNrmse2D = replaceFirst(programStringGetGlobalNrmse2D, "$BRH$", "" + blockRadiusHeight);
+ programStringGetGlobalNrmse2D = replaceFirst(programStringGetGlobalNrmse2D, "$FILTER_PARAM$", "" + relevanceMask.getNoiseMeanVariance());
+ programStringGetGlobalNrmse2D = replaceFirst(programStringGetGlobalNrmse2D, "$THRESHOLD$", "" + relevanceMask.getRelevanceThreshold());
+ programStringGetGlobalNrmse2D = replaceFirst(programStringGetGlobalNrmse2D, "$EPSILON$", "" + Utils.EPSILON);
+ CLProgram programGetGlobalNrmse2D = openCLResources.getContext().createProgram(programStringGetGlobalNrmse2D).build();
+
+ // Create OpenCL kernel and set args
+ CLKernel kernelGetGlobalNrmse2D = programGetGlobalNrmse2D.createCLKernel("kernelGetGlobalNrmse2D");
+
+ int argn = 0;
+ kernelGetGlobalNrmse2D.setArg(argn++, localStatistics.getCLImageArray());
+ kernelGetGlobalNrmse2D.setArg(argn++, localStatistics.getCLLocalMeans());
+ kernelGetGlobalNrmse2D.setArg(argn++, localStatistics.getCLLocalStds());
+ kernelGetGlobalNrmse2D.setArg(argn++, clWeightsSumMap);
+ kernelGetGlobalNrmse2D.setArg(argn++, clRepetitionMap);
+
+ // Calculate weighted mean Pearson's map
+ int nXBlocks = imageWidth/64 + ((imageWidth%64==0)?0:1);
+ int nYBlocks = imageHeight/64 + ((imageHeight%64==0)?0:1);
+ int totalBlocks = nXBlocks * nYBlocks; // Total number of blocks
+
+ for (int nYB=0; nYB0.0f) {
+ float rmse = repetitionMap[index];
+ if (rmse == 0.0f) { // Special case where RMSE is 0, 1/rmse would be undefined but we want perfect similarity
+ repetitionMap[index] = 1.0f;
+ } else {
+ repetitionMap[index] = 1.0f / rmse;
+ }
+ }
+ }
+ }
+
+ // Apply relevance mask
+ if(relevanceMask.getRelevanceConstant()>0.0f) {
+ repetitionMap = Utils.applyMask2D(repetitionMap, imageWidth, imageHeight, relevanceMask.getRelevanceMask());
+ }
+
+ // Normalize repetition map (avoiding masked pixels)
+ if(normalizeOutput) {
+ repetitionMap = Utils.normalizeImage2D(repetitionMap, imageWidth, imageHeight, blockRadiusWidth,
+ blockRadiusHeight, relevanceMask.getRelevanceMask());
+ }
+
+ // Release memory
+ openCLResources.getContext().release();
+
+ return repetitionMap;
+ }
+
+
+ /**
+ * Computes the Global Repetition Map based on the absolute difference of standard deviations for a 2D image using OpenCL.
+ *
+ * This method calculates the global repetition map based on the absolute difference of standard deviations. The result is stored in a global
+ * repetition map, indicating the relative repetition of each structural element across the image.
+ * Optionally, a relevance mask can be applied to filter the results based on local standard deviations and a
+ * relevance constant.
+ *
+ * @param inputImage a {@link Utils.InputImage2D} object
+ * @param localStatistics a {@link CLLocalStatistics} object
+ * @param blockWidth the width of the block used for the analysis
+ * @param blockHeight the height of the block used for the analysis
+ * @param relevanceMask a {@link Utils.RelevanceMask} object
+ * @param nPixels the number of structurally relevant pixels (i.e., non-masked pixels)
+ * @param normalizeOutput a boolean to either normalize the output or not
+ * @param openCLResources an OpenCLResources object
+ * @return a float array representing the global repetition map, indicating the degree of repetition of each
+ * structural element across the entire image.
+ */
+ public static float[] getGlobalAbsDiffStds2D(Utils.InputImage2D inputImage, CLLocalStatistics localStatistics,
+ int blockWidth, int blockHeight, Utils.RelevanceMask relevanceMask,
+ float nPixels, boolean normalizeOutput, OpenCLResources openCLResources)
+ {
+ IJ.log("Calculating Structural Repetition Scores...");
+
+ // Cache variables
+ int imageWidth = inputImage.getWidth();
+ int imageHeight = inputImage.getHeight();
+ int imageSize = inputImage.getSize();
+ int blockRadiusWidth = blockWidth/2;
+ int blockRadiusHeight = blockHeight/2;
+
+ // Create and fill OpenCL buffers
+ float[] repetitionMap = new float[imageSize];
+ CLBuffer clRepetitionMap = CLUtils.createAndFillCLBuffer(openCLResources.getContext(), imageSize,
+ READ_WRITE, repetitionMap);
+
+ float[] weightsSumMap = new float[imageSize];
+ CLBuffer clWeightsSumMap = CLUtils.createAndFillCLBuffer(openCLResources.getContext(), imageSize,
+ READ_WRITE, weightsSumMap);
+
+ // Build OpenCL program
+ String programStringGetGlobalAbsDiffStds2D = getResourceAsString(CLUtils.class, "kernelGetGlobalAbsDiffStds2D.cl");
+ programStringGetGlobalAbsDiffStds2D = replaceFirst(programStringGetGlobalAbsDiffStds2D, "$WIDTH$", "" + imageWidth);
+ programStringGetGlobalAbsDiffStds2D = replaceFirst(programStringGetGlobalAbsDiffStds2D, "$HEIGHT$", "" + imageHeight);
+ programStringGetGlobalAbsDiffStds2D = replaceFirst(programStringGetGlobalAbsDiffStds2D, "$BRW$", "" + blockRadiusWidth);
+ programStringGetGlobalAbsDiffStds2D = replaceFirst(programStringGetGlobalAbsDiffStds2D, "$BRH$", "" + blockRadiusHeight);
+ programStringGetGlobalAbsDiffStds2D = replaceFirst(programStringGetGlobalAbsDiffStds2D, "$FILTER_PARAM$", "" + relevanceMask.getNoiseMeanVariance());
+ programStringGetGlobalAbsDiffStds2D = replaceFirst(programStringGetGlobalAbsDiffStds2D, "$THRESHOLD$", "" + relevanceMask.getRelevanceThreshold());
+ programStringGetGlobalAbsDiffStds2D = replaceFirst(programStringGetGlobalAbsDiffStds2D, "$EPSILON$", "" + Utils.EPSILON);
+ CLProgram programGetGlobalAbsDiffStds2D = openCLResources.getContext().createProgram(programStringGetGlobalAbsDiffStds2D).build();
+
+ // Create OpenCL kernel and set args
+ CLKernel kernelGetGlobalAbsDiffStds2D = programGetGlobalAbsDiffStds2D.createCLKernel("kernelGetGlobalAbsDiffStds2D");
+
+ int argn = 0;
+ kernelGetGlobalAbsDiffStds2D.setArg(argn++, localStatistics.getCLImageArray());
+ kernelGetGlobalAbsDiffStds2D.setArg(argn++, localStatistics.getCLLocalStds());
+ kernelGetGlobalAbsDiffStds2D.setArg(argn++, clWeightsSumMap);
+ kernelGetGlobalAbsDiffStds2D.setArg(argn++, clRepetitionMap);
+
+ // Calculate weighted mean Pearson's map
+ int nXBlocks = imageWidth/64 + ((imageWidth%64==0)?0:1);
+ int nYBlocks = imageHeight/64 + ((imageHeight%64==0)?0:1);
+ int totalBlocks = nXBlocks * nYBlocks; // Total number of blocks
+
+ for (int nYB=0; nYB0.0f) {
+ float srs = repetitionMap[index];
+ if (srs == 0.0f) { // Special case where SRS is 0, 1/SRS would be undefined but we want perfect similarity
+ repetitionMap[index] = 1.0f;
+ } else {
+ repetitionMap[index] = 1.0f / srs;
+ }
+ }
+ }
+ }
+
+ // Apply relevance mask
+ if(relevanceMask.getRelevanceConstant()>0.0f) {
+ repetitionMap = Utils.applyMask2D(repetitionMap, imageWidth, imageHeight, relevanceMask.getRelevanceMask());
+ }
+
+ // Normalize repetition map (avoiding masked pixels)
+ if(normalizeOutput) {
+ repetitionMap = Utils.normalizeImage2D(repetitionMap, imageWidth, imageHeight, blockRadiusWidth,
+ blockRadiusHeight, relevanceMask.getRelevanceMask());
+ }
+
+ // Release memory
+ openCLResources.getContext().release();
+
+ return repetitionMap;
+ }
+
+
+ /**
+ * Calculate a global repetition map for 2D data, based on the selected metric.
+ *
+ * @param metric The selected metric for repetition calculation.
+ * @param inputImage A {@link Utils.InputImage2D}.
+ * @param blockWidth The width of the block used for the analysis.
+ * @param blockHeight The height of the block used for the analysis.
+ * @param blockSize The size of the block used for the analysis, after removing pixels outside the inbound circle/ellipse.
+ * @param localStatistics A {@link CLLocalStatistics} object.
+ * @param relevanceMask A {@link Utils.RelevanceMask} object
+ * @param nPixels The number of structurally relevant pixels (i.e., non-masked pixels)
+ * @param normalizeOutput A boolean to either normalize the output or not.
+ *
+ * @return The calculated repetition map.
+ */
+ public static float[] calculateGlobalRepetitionMap2D(String metric, Utils.InputImage2D inputImage, int blockWidth,
+ int blockHeight, int blockSize,
+ CLLocalStatistics localStatistics,
+ Utils.RelevanceMask relevanceMask, float nPixels,
+ boolean normalizeOutput, OpenCLResources openCLResources)
+ {
+
+ if(metric.equals("Pearson's R")) {
+ return CLUtils.getGlobalPearson2D(inputImage, localStatistics, blockWidth, blockHeight, blockSize,
+ relevanceMask, nPixels, normalizeOutput, openCLResources);
+ }else if(metric.equals("Cosine similarity")) {
+ return CLUtils.getGlobalCosineSimilarity2D(inputImage, localStatistics, blockWidth, blockHeight,
+ relevanceMask, nPixels, normalizeOutput, openCLResources);
+ }else if(metric.equals("SSIM")){
+ return CLUtils.getGlobalSsim2D(inputImage, localStatistics, blockWidth, blockHeight, blockSize,
+ relevanceMask, nPixels, normalizeOutput, openCLResources);
+ }else if(metric.equals("NRMSE")){
+ return CLUtils.getGlobalNrmse2D(inputImage, localStatistics, blockWidth, blockHeight, blockSize,
+ relevanceMask, nPixels, normalizeOutput, openCLResources);
+ }else if(metric.equals("Abs. diff. of StdDevs")){
+ return CLUtils.getGlobalAbsDiffStds2D(inputImage, localStatistics, blockWidth, blockHeight,
+ relevanceMask, nPixels, normalizeOutput, openCLResources);
+ }else{
+ return null;
+ }
+ }
+
+
+ // ------------------------------------------ //
+ // ---- METHODS FOR GLOBAL REPETITION 3D ---- //
+ // ------------------------------------------ //
+
+
+ /**
+ * Computes the Global Repetition Map based on Pearson correlations for a 3D image using OpenCL.
+ *
+ * This method calculates the global repetition map based on Pearson correlations. The result is stored in a global
+ * repetition map, indicating the relative repetition of each structural element across the image.
+ * Optionally, a relevance mask can be applied to filter the results based on local standard deviations and a
+ * relevance constant.
+ *
+ * @param inputImage a {@link Utils.InputImage3D} object
+ * @param localStatistics a {@link CLLocalStatistics} object
+ * @param blockWidth the width of the block used for the analysis
+ * @param blockHeight the height of the block used for the analysis
+ * @param blockDepth the depth of the block used for the analysis
+ * @param blockSize the size of the block used for the analysis, after removing pixels outside the inbound circle/ellipse
+ * @param relevanceMask a {@link Utils.RelevanceMask} object
+ * @param nPixels the number of structurally relevant pixels (i.e., non-masked pixels)
+ * @param normalizeOutput a boolean to either normalize the output or not
+ * @param openCLResources an {@link OpenCLResources} object
+ * @return a float array representing the global repetition map, indicating the degree of repetition of each
+ * structural element across the entire image.
+ */
+ public static float[] getGlobalPearson3D(Utils.InputImage3D inputImage, CLLocalStatistics localStatistics,
+ int blockWidth, int blockHeight, int blockDepth, int blockSize,
+ Utils.RelevanceMask relevanceMask, float nPixels, boolean normalizeOutput,
+ OpenCLResources openCLResources)
+ {
+ IJ.log("Calculating Structural Repetition Scores...");
+
+ // Cache variables
+ int imageWidth = inputImage.getWidth();
+ int imageHeight = inputImage.getHeight();
+ int imageDepth = inputImage.getDepth();
+ int imageSize = inputImage.getSize();
+ int blockRadiusWidth = blockWidth / 2;
+ int blockRadiusHeight = blockHeight / 2;
+ int blockRadiusDepth = blockDepth / 2;
+
+ // Create and fill OpenCL buffers
+ float[] repetitionMap = new float[imageSize];
+ CLBuffer clRepetitionMap = CLUtils.createAndFillCLBuffer(openCLResources.getContext(), imageSize,
+ READ_WRITE, repetitionMap);
+
+ float[] weightsSumMap = new float[imageSize];
+ CLBuffer clWeightsSumMap = CLUtils.createAndFillCLBuffer(openCLResources.getContext(), imageSize,
+ READ_WRITE, weightsSumMap);
+
+ // Build OpenCL program
+ String programStringGetGlobalPearson3D = getResourceAsString(CLUtils.class, "kernelGetGlobalPearson3D.cl");
+ programStringGetGlobalPearson3D = replaceFirst(programStringGetGlobalPearson3D, "$WIDTH$", "" + imageWidth);
+ programStringGetGlobalPearson3D = replaceFirst(programStringGetGlobalPearson3D, "$HEIGHT$", "" + imageHeight);
+ programStringGetGlobalPearson3D = replaceFirst(programStringGetGlobalPearson3D, "$DEPTH$", "" + imageDepth);
+ programStringGetGlobalPearson3D = replaceFirst(programStringGetGlobalPearson3D, "$BLOCK_SIZE$", "" + blockSize);
+ programStringGetGlobalPearson3D = replaceFirst(programStringGetGlobalPearson3D, "$BRW$", "" + blockRadiusWidth);
+ programStringGetGlobalPearson3D = replaceFirst(programStringGetGlobalPearson3D, "$BRH$", "" + blockRadiusHeight);
+ programStringGetGlobalPearson3D = replaceFirst(programStringGetGlobalPearson3D, "$BRZ$", "" + blockRadiusDepth);
+ programStringGetGlobalPearson3D = replaceFirst(programStringGetGlobalPearson3D, "$FILTER_PARAM$", "" + relevanceMask.getNoiseMeanVariance());
+ programStringGetGlobalPearson3D = replaceFirst(programStringGetGlobalPearson3D, "$THRESHOLD$", "" + relevanceMask.getRelevanceThreshold());
+ programStringGetGlobalPearson3D = replaceFirst(programStringGetGlobalPearson3D, "$EPSILON$", "" + Utils.EPSILON);
+ CLProgram programGetGlobalPearson3D = openCLResources.getContext().createProgram(programStringGetGlobalPearson3D).build();
+
+ // Create OpenCL kernel and set args
+ CLKernel kernelGetGlobalPearson3D = programGetGlobalPearson3D.createCLKernel("kernelGetGlobalPearson3D");
+
+ int argn = 0;
+ kernelGetGlobalPearson3D.setArg(argn++, localStatistics.getCLImageArray());
+ kernelGetGlobalPearson3D.setArg(argn++, localStatistics.getCLLocalMeans());
+ kernelGetGlobalPearson3D.setArg(argn++, localStatistics.getCLLocalStds());
+ kernelGetGlobalPearson3D.setArg(argn++, clWeightsSumMap);
+ kernelGetGlobalPearson3D.setArg(argn++, clRepetitionMap);
+
+
+ // Calculate weighted mean Pearson's map
+ int nXBlocks = imageWidth / 64 + ((imageWidth % 64 == 0) ? 0 : 1);
+ int nYBlocks = imageHeight / 64 + ((imageHeight % 64 == 0) ? 0 : 1);
+ int nZBlocks = imageDepth / blockDepth + ((imageDepth % blockDepth == 0) ? 0 : 1);
+ int totalBlocks = nXBlocks * nYBlocks * nZBlocks;
+
+ for (int nZB = 0; nZB < nZBlocks; nZB++) {
+ int zWorkSize = min(blockDepth, imageDepth - nZB * blockDepth);
+ for (int nYB = 0; nYB < nYBlocks; nYB++) {
+ int yWorkSize = min(64, imageHeight - nYB * 64);
+ for (int nXB = 0; nXB < nXBlocks; nXB++) {
+ int xWorkSize = min(64, imageWidth - nXB * 64);
+ float progressPercentage = ((nZB * nYBlocks * nXBlocks) + (nYB * nXBlocks) + nXB) / (float) totalBlocks * 100;
+ showStatus(String.format("Calculating global repetition... %d%%", Math.round(progressPercentage)));
+ openCLResources.getQueue().put3DRangeKernel(kernelGetGlobalPearson3D,
+ nXB * 64, nYB * 64, nZB * blockDepth,
+ xWorkSize, yWorkSize, zWorkSize, 0, 0, 0);
+ openCLResources.getQueue().finish();
+ }
+ }
+ }
+ showStatus("Calculating global repetition... 100%");
+
+ // Read the repetition map back from the device and calculate the weighted average
+ openCLResources.getQueue().putReadBuffer(clRepetitionMap, true);
+ openCLResources.getQueue().putReadBuffer(clWeightsSumMap, true);
+
+ for(int z=blockRadiusDepth; z0.0f) {
+ repetitionMap = Utils.applyMask3D(repetitionMap, imageWidth, imageHeight, imageDepth,
+ relevanceMask.getRelevanceMask());
+ }
+
+ // Normalize repetition map (avoiding masked pixels)
+ if(normalizeOutput) {
+ repetitionMap = Utils.normalizeImage3D(repetitionMap, imageWidth, imageHeight, imageDepth, blockRadiusWidth,
+ blockRadiusHeight, blockRadiusDepth, relevanceMask.getRelevanceMask());
+ }
+
+ // Release memory
+ openCLResources.getContext().release();
+
+ return repetitionMap;
+ }
+
+
+ /**
+ * Computes the Global Repetition Map based on the Cosine Similarity metric for a 3D image using OpenCL.
+ *
+ * This method calculates the global repetition map based on the Cosine Similarity metric. The result is stored in a global
+ * repetition map, indicating the relative repetition of each structural element across the image.
+ * Optionally, a relevance mask can be applied to filter the results based on local standard deviations and a
+ * relevance constant.
+ *
+ * @param inputImage a {@link Utils.InputImage3D} object
+ * @param localStatistics a {@link CLLocalStatistics} object
+ * @param blockWidth the width of the block used for the analysis
+ * @param blockHeight the height of the block used for the analysis
+ * @param blockDepth the depth of the block used for the analysis
+ * @param relevanceMask a {@link Utils.RelevanceMask} object
+ * @param nPixels the number of structurally relevant pixels (i.e., non-masked pixels)
+ * @param normalizeOutput a boolean to either normalize the output or not
+ * @param openCLResources an {@link OpenCLResources} object
+ * @return a float array representing the global repetition map, indicating the degree of repetition of each
+ * structural element across the entire image.
+ */
+ public static float[] getGlobalCosineSimilarity3D(Utils.InputImage3D inputImage, CLLocalStatistics localStatistics,
+ int blockWidth, int blockHeight, int blockDepth,
+ Utils.RelevanceMask relevanceMask, float nPixels,
+ boolean normalizeOutput, OpenCLResources openCLResources)
+ {
+ IJ.log("Calculating Structural Repetition Scores...");
+
+ // Cache variables
+ int imageWidth = inputImage.getWidth();
+ int imageHeight = inputImage.getHeight();
+ int imageDepth = inputImage.getDepth();
+ int imageSize = inputImage.getSize();
+ int blockRadiusWidth = blockWidth / 2;
+ int blockRadiusHeight = blockHeight / 2;
+ int blockRadiusDepth = blockDepth / 2;
+
+ // Create and fill OpenCL buffers
+ float[] repetitionMap = new float[imageSize];
+ CLBuffer clRepetitionMap = CLUtils.createAndFillCLBuffer(openCLResources.getContext(), imageSize,
+ READ_WRITE, repetitionMap);
+
+ float[] weightsSumMap = new float[imageSize];
+ CLBuffer clWeightsSumMap = CLUtils.createAndFillCLBuffer(openCLResources.getContext(), imageSize,
+ READ_WRITE, weightsSumMap);
+
+ // Build OpenCL program
+ String programStringGetGlobalCosineSimilarity3D = getResourceAsString(CLUtils.class, "kernelGetGlobalCosineSimilarity3D.cl");
+ programStringGetGlobalCosineSimilarity3D = replaceFirst(programStringGetGlobalCosineSimilarity3D, "$WIDTH$", "" + imageWidth);
+ programStringGetGlobalCosineSimilarity3D = replaceFirst(programStringGetGlobalCosineSimilarity3D, "$HEIGHT$", "" + imageHeight);
+ programStringGetGlobalCosineSimilarity3D = replaceFirst(programStringGetGlobalCosineSimilarity3D, "$DEPTH$", "" + imageDepth);
+ programStringGetGlobalCosineSimilarity3D = replaceFirst(programStringGetGlobalCosineSimilarity3D, "$BRW$", "" + blockRadiusWidth);
+ programStringGetGlobalCosineSimilarity3D = replaceFirst(programStringGetGlobalCosineSimilarity3D, "$BRH$", "" + blockRadiusHeight);
+ programStringGetGlobalCosineSimilarity3D = replaceFirst(programStringGetGlobalCosineSimilarity3D, "$BRZ$", "" + blockRadiusDepth);
+ programStringGetGlobalCosineSimilarity3D = replaceFirst(programStringGetGlobalCosineSimilarity3D, "$FILTER_PARAM$", "" + relevanceMask.getNoiseMeanVariance());
+ programStringGetGlobalCosineSimilarity3D = replaceFirst(programStringGetGlobalCosineSimilarity3D, "$THRESHOLD$", "" + relevanceMask.getRelevanceThreshold());
+ programStringGetGlobalCosineSimilarity3D = replaceFirst(programStringGetGlobalCosineSimilarity3D, "$EPSILON$", "" + Utils.EPSILON);
+ CLProgram programGetGlobalCosineSimilarity3D = openCLResources.getContext().createProgram(programStringGetGlobalCosineSimilarity3D).build();
+
+ // Create OpenCL kernel and set args
+ CLKernel kernelGetGlobalCosineSimilarity3D = programGetGlobalCosineSimilarity3D.createCLKernel("kernelGetGlobalCosineSimilarity3D");
+
+ int argn = 0;
+ kernelGetGlobalCosineSimilarity3D.setArg(argn++, localStatistics.getCLImageArray());
+ kernelGetGlobalCosineSimilarity3D.setArg(argn++, localStatistics.getCLLocalStds());
+ kernelGetGlobalCosineSimilarity3D.setArg(argn++, clWeightsSumMap);
+ kernelGetGlobalCosineSimilarity3D.setArg(argn++, clRepetitionMap);
+
+ // Calculate weighted mean Pearson's map
+ int nXBlocks = imageWidth / 64 + ((imageWidth % 64 == 0) ? 0 : 1);
+ int nYBlocks = imageHeight / 64 + ((imageHeight % 64 == 0) ? 0 : 1);
+ int nZBlocks = imageDepth / blockDepth + ((imageDepth % blockDepth == 0) ? 0 : 1);
+ int totalBlocks = nXBlocks * nYBlocks * nZBlocks;
+
+ for (int nZB = 0; nZB < nZBlocks; nZB++) {
+ int zWorkSize = min(blockDepth, imageDepth - nZB * blockDepth);
+ for (int nYB = 0; nYB < nYBlocks; nYB++) {
+ int yWorkSize = min(64, imageHeight - nYB * 64);
+ for (int nXB = 0; nXB < nXBlocks; nXB++) {
+ int xWorkSize = min(64, imageWidth - nXB * 64);
+ float progressPercentage = ((nZB * nYBlocks * nXBlocks) + (nYB * nXBlocks) + nXB) / (float) totalBlocks * 100;
+ showStatus(String.format("Calculating global repetition... %d%%", Math.round(progressPercentage)));
+ openCLResources.getQueue().put3DRangeKernel(kernelGetGlobalCosineSimilarity3D,
+ nXB * 64, nYB * 64, nZB * blockDepth,
+ xWorkSize, yWorkSize, zWorkSize, 0, 0, 0);
+ openCLResources.getQueue().finish();
+ }
+ }
+ }
+ showStatus("Calculating global repetition... 100%");
+
+ // Read the repetition map back from the device and calculate the weighted average
+ openCLResources.getQueue().putReadBuffer(clRepetitionMap, true);
+ openCLResources.getQueue().putReadBuffer(clWeightsSumMap, true);
+
+ for(int z=blockRadiusDepth; z0.0f) {
+ repetitionMap = Utils.applyMask3D(repetitionMap, imageWidth, imageHeight, imageDepth,
+ relevanceMask.getRelevanceMask());
+ }
+
+ // Normalize repetition map (avoiding masked pixels)
+ if(normalizeOutput) {
+ repetitionMap = Utils.normalizeImage3D(repetitionMap, imageWidth, imageHeight, imageDepth, blockRadiusWidth,
+ blockRadiusHeight, blockRadiusDepth, relevanceMask.getRelevanceMask());
+ }
+
+ // Release memory
+ openCLResources.getContext().release();
+
+ return repetitionMap;
+ }
+
+
+ /**
+ * Computes the Global Repetition Map based on the Structural Similarity Index metric (SSIM) for a 3D image using OpenCL.
+ *
+ * This method calculates the global repetition map based on the Structural Similarity Index metric (SSIM). The result is stored in a global
+ * repetition map, indicating the relative repetition of each structural element across the image.
+ * Optionally, a relevance mask can be applied to filter the results based on local standard deviations and a
+ * relevance constant.
+ *
+ * @param inputImage a {@link Utils.InputImage3D} object
+ * @param localStatistics a {@link CLLocalStatistics} object
+ * @param blockWidth the width of the block used for the analysis
+ * @param blockHeight the height of the block used for the analysis
+ * @param blockDepth the depth of the block used for the analysis
+ * @param blockSize the size of the block used for the analysis, after removing pixels outside the inbound circle/ellipse
+ * @param relevanceMask a {@link Utils.RelevanceMask} object
+ * @param nPixels the number of structurally relevant pixels (i.e., non-masked pixels)
+ * @param normalizeOutput a boolean to either normalize the output or not
+ * @param openCLResources an {@link OpenCLResources} object
+ * @return a float array representing the global repetition map, indicating the degree of repetition of each
+ * structural element across the entire image.
+ */
+ public static float[] getGlobalSsim3D(Utils.InputImage3D inputImage, CLLocalStatistics localStatistics,
+ int blockWidth, int blockHeight, int blockDepth, int blockSize,
+ Utils.RelevanceMask relevanceMask, float nPixels, boolean normalizeOutput,
+ OpenCLResources openCLResources)
+ {
+ IJ.log("Calculating Structural Repetition Scores...");
+
+ // Cache variables
+ int imageWidth = inputImage.getWidth();
+ int imageHeight = inputImage.getHeight();
+ int imageDepth = inputImage.getDepth();
+ int imageSize = inputImage.getSize();
+ int blockRadiusWidth = blockWidth / 2;
+ int blockRadiusHeight = blockHeight / 2;
+ int blockRadiusDepth = blockDepth / 2;
+
+ // Create and fill OpenCL buffers
+ float[] repetitionMap = new float[imageSize];
+ CLBuffer clRepetitionMap = CLUtils.createAndFillCLBuffer(openCLResources.getContext(), imageSize,
+ READ_WRITE, repetitionMap);
+
+ float[] weightsSumMap = new float[imageSize];
+ CLBuffer clWeightsSumMap = CLUtils.createAndFillCLBuffer(openCLResources.getContext(), imageSize,
+ READ_WRITE, weightsSumMap);
+
+ // Build OpenCL program
+ String programStringGetGlobalSsim3D = getResourceAsString(CLUtils.class, "kernelGetGlobalSsim3D.cl");
+ programStringGetGlobalSsim3D = replaceFirst(programStringGetGlobalSsim3D, "$WIDTH$", "" + imageWidth);
+ programStringGetGlobalSsim3D = replaceFirst(programStringGetGlobalSsim3D, "$HEIGHT$", "" + imageHeight);
+ programStringGetGlobalSsim3D = replaceFirst(programStringGetGlobalSsim3D, "$DEPTH$", "" + imageDepth);
+ programStringGetGlobalSsim3D = replaceFirst(programStringGetGlobalSsim3D, "$BLOCK_SIZE$", "" + blockSize);
+ programStringGetGlobalSsim3D = replaceFirst(programStringGetGlobalSsim3D, "$BRW$", "" + blockRadiusWidth);
+ programStringGetGlobalSsim3D = replaceFirst(programStringGetGlobalSsim3D, "$BRH$", "" + blockRadiusHeight);
+ programStringGetGlobalSsim3D = replaceFirst(programStringGetGlobalSsim3D, "$BRZ$", "" + blockRadiusDepth);
+ programStringGetGlobalSsim3D = replaceFirst(programStringGetGlobalSsim3D, "$FILTER_PARAM$", "" + relevanceMask.getNoiseMeanVariance());
+ programStringGetGlobalSsim3D = replaceFirst(programStringGetGlobalSsim3D, "$THRESHOLD$", "" + relevanceMask.getRelevanceThreshold());
+ programStringGetGlobalSsim3D = replaceFirst(programStringGetGlobalSsim3D, "$EPSILON$", "" + Utils.EPSILON);
+ CLProgram programGetGlobalSsim3D = openCLResources.getContext().createProgram(programStringGetGlobalSsim3D).build();
+
+ // Create OpenCL kernel and set args
+ CLKernel kernelGetGlobalSsim3D = programGetGlobalSsim3D.createCLKernel("kernelGetGlobalSsim3D");
+
+ int argn = 0;
+ kernelGetGlobalSsim3D.setArg(argn++, localStatistics.getCLImageArray());
+ kernelGetGlobalSsim3D.setArg(argn++, localStatistics.getCLLocalMeans());
+ kernelGetGlobalSsim3D.setArg(argn++, localStatistics.getCLLocalStds());
+ kernelGetGlobalSsim3D.setArg(argn++, clWeightsSumMap);
+ kernelGetGlobalSsim3D.setArg(argn++, clRepetitionMap);
+
+
+ // Calculate weighted mean Pearson's map
+ int nXBlocks = imageWidth / 64 + ((imageWidth % 64 == 0) ? 0 : 1);
+ int nYBlocks = imageHeight / 64 + ((imageHeight % 64 == 0) ? 0 : 1);
+ int nZBlocks = imageDepth / blockDepth + ((imageDepth % blockDepth == 0) ? 0 : 1);
+ int totalBlocks = nXBlocks * nYBlocks * nZBlocks;
+
+ for (int nZB = 0; nZB < nZBlocks; nZB++) {
+ int zWorkSize = min(blockDepth, imageDepth - nZB * blockDepth);
+ for (int nYB = 0; nYB < nYBlocks; nYB++) {
+ int yWorkSize = min(64, imageHeight - nYB * 64);
+ for (int nXB = 0; nXB < nXBlocks; nXB++) {
+ int xWorkSize = min(64, imageWidth - nXB * 64);
+ float progressPercentage = ((nZB * nYBlocks * nXBlocks) + (nYB * nXBlocks) + nXB) / (float) totalBlocks * 100;
+ showStatus(String.format("Calculating global repetition... %d%%", Math.round(progressPercentage)));
+ openCLResources.getQueue().put3DRangeKernel(kernelGetGlobalSsim3D,
+ nXB * 64, nYB * 64, nZB * blockDepth,
+ xWorkSize, yWorkSize, zWorkSize, 0, 0, 0);
+ openCLResources.getQueue().finish();
+ }
+ }
+ }
+ showStatus("Calculating global repetition... 100%");
+
+ // Read the repetition map back from the device and calculate the weighted average
+ openCLResources.getQueue().putReadBuffer(clRepetitionMap, true);
+ openCLResources.getQueue().putReadBuffer(clWeightsSumMap, true);
+
+ for(int z=blockRadiusDepth; z0.0f) {
+ repetitionMap = Utils.applyMask3D(repetitionMap, imageWidth, imageHeight, imageDepth,
+ relevanceMask.getRelevanceMask());
+ }
+
+ // Normalize repetition map (avoiding masked pixels)
+ if(normalizeOutput) {
+ repetitionMap = Utils.normalizeImage3D(repetitionMap, imageWidth, imageHeight, imageDepth, blockRadiusWidth,
+ blockRadiusHeight, blockRadiusDepth, relevanceMask.getRelevanceMask());
+ }
+
+ // Release memory
+ openCLResources.getContext().release();
+
+ return repetitionMap;
+ }
+
+
+ /**
+ * Computes the Global Repetition Map based on the Normalized Root Mean Squared Error (NRMSE) for a 3D image using OpenCL.
+ *
+ * This method calculates the global repetition map based on the Normalized Root Mean Squared Error (NRMSE). The result is stored in a global
+ * repetition map, indicating the relative repetition of each structural element across the image.
+ * Optionally, a relevance mask can be applied to filter the results based on local standard deviations and a
+ * relevance constant.
+ *
+ * @param inputImage a {@link Utils.InputImage3D} object
+ * @param localStatistics a {@link CLLocalStatistics} object
+ * @param blockWidth the width of the block used for the analysis
+ * @param blockHeight the height of the block used for the analysis
+ * @param blockDepth the depth of the block used for the analysis
+ * @param blockSize the size of the block used for the analysis, after removing pixels outside the inbound circle/ellipse
+ * @param relevanceMask a {@link Utils.RelevanceMask} object
+ * @param nPixels the number of structurally relevant pixels (i.e., non-masked pixels)
+ * @param normalizeOutput a boolean to either normalize the output or not
+ * @param openCLResources an {@link OpenCLResources} object
+ * @return a float array representing the global repetition map, indicating the degree of repetition of each
+ * structural element across the entire image.
+ */
+ public static float[] getGlobalNrmse3D(Utils.InputImage3D inputImage, CLLocalStatistics localStatistics,
+ int blockWidth, int blockHeight, int blockDepth, int blockSize,
+ Utils.RelevanceMask relevanceMask, float nPixels, boolean normalizeOutput,
+ OpenCLResources openCLResources)
+ {
+ IJ.log("Calculating Structural Repetition Scores...");
+
+ // Cache variables
+ int imageWidth = inputImage.getWidth();
+ int imageHeight = inputImage.getHeight();
+ int imageDepth = inputImage.getDepth();
+ int imageSize = inputImage.getSize();
+ int blockRadiusWidth = blockWidth / 2;
+ int blockRadiusHeight = blockHeight / 2;
+ int blockRadiusDepth = blockDepth / 2;
+
+ // Create and fill OpenCL buffers
+ float[] repetitionMap = new float[imageSize];
+ CLBuffer clRepetitionMap = CLUtils.createAndFillCLBuffer(openCLResources.getContext(), imageSize,
+ READ_WRITE, repetitionMap);
+
+ float[] weightsSumMap = new float[imageSize];
+ CLBuffer clWeightsSumMap = CLUtils.createAndFillCLBuffer(openCLResources.getContext(), imageSize,
+ READ_WRITE, weightsSumMap);
+
+ // Build OpenCL program
+ String programStringGetGlobalNrmse3D = getResourceAsString(CLUtils.class, "kernelGetGlobalNrmse3D.cl");
+ programStringGetGlobalNrmse3D = replaceFirst(programStringGetGlobalNrmse3D, "$WIDTH$", "" + imageWidth);
+ programStringGetGlobalNrmse3D = replaceFirst(programStringGetGlobalNrmse3D, "$HEIGHT$", "" + imageHeight);
+ programStringGetGlobalNrmse3D = replaceFirst(programStringGetGlobalNrmse3D, "$DEPTH$", "" + imageDepth);
+ programStringGetGlobalNrmse3D = replaceFirst(programStringGetGlobalNrmse3D, "$BLOCK_SIZE$", "" + blockSize);
+ programStringGetGlobalNrmse3D = replaceFirst(programStringGetGlobalNrmse3D, "$BRW$", "" + blockRadiusWidth);
+ programStringGetGlobalNrmse3D = replaceFirst(programStringGetGlobalNrmse3D, "$BRH$", "" + blockRadiusHeight);
+ programStringGetGlobalNrmse3D = replaceFirst(programStringGetGlobalNrmse3D, "$BRZ$", "" + blockRadiusDepth);
+ programStringGetGlobalNrmse3D = replaceFirst(programStringGetGlobalNrmse3D, "$FILTER_PARAM$", "" + relevanceMask.getNoiseMeanVariance());
+ programStringGetGlobalNrmse3D = replaceFirst(programStringGetGlobalNrmse3D, "$THRESHOLD$", "" + relevanceMask.getRelevanceThreshold());
+ programStringGetGlobalNrmse3D = replaceFirst(programStringGetGlobalNrmse3D, "$EPSILON$", "" + Utils.EPSILON);
+ CLProgram programGetGlobalNrmse3D = openCLResources.getContext().createProgram(programStringGetGlobalNrmse3D).build();
+
+ // Create OpenCL kernel and set args
+ CLKernel kernelGetGlobalNrmse3D = programGetGlobalNrmse3D.createCLKernel("kernelGetGlobalNrmse3D");
+
+ int argn = 0;
+ kernelGetGlobalNrmse3D.setArg(argn++, localStatistics.getCLImageArray());
+ kernelGetGlobalNrmse3D.setArg(argn++, localStatistics.getCLLocalMeans());
+ kernelGetGlobalNrmse3D.setArg(argn++, localStatistics.getCLLocalStds());
+ kernelGetGlobalNrmse3D.setArg(argn++, clWeightsSumMap);
+ kernelGetGlobalNrmse3D.setArg(argn++, clRepetitionMap);
+
+
+ // Calculate weighted mean Pearson's map
+ int nXBlocks = imageWidth / 64 + ((imageWidth % 64 == 0) ? 0 : 1);
+ int nYBlocks = imageHeight / 64 + ((imageHeight % 64 == 0) ? 0 : 1);
+ int nZBlocks = imageDepth / blockDepth + ((imageDepth % blockDepth == 0) ? 0 : 1);
+ int totalBlocks = nXBlocks * nYBlocks * nZBlocks;
+
+ for (int nZB = 0; nZB < nZBlocks; nZB++) {
+ int zWorkSize = min(blockDepth, imageDepth - nZB * blockDepth);
+ for (int nYB = 0; nYB < nYBlocks; nYB++) {
+ int yWorkSize = min(64, imageHeight - nYB * 64);
+ for (int nXB = 0; nXB < nXBlocks; nXB++) {
+ int xWorkSize = min(64, imageWidth - nXB * 64);
+ float progressPercentage = ((nZB * nYBlocks * nXBlocks) + (nYB * nXBlocks) + nXB) / (float) totalBlocks * 100;
+ showStatus(String.format("Calculating global repetition... %d%%", Math.round(progressPercentage)));
+ openCLResources.getQueue().put3DRangeKernel(kernelGetGlobalNrmse3D,
+ nXB * 64, nYB * 64, nZB * blockDepth,
+ xWorkSize, yWorkSize, zWorkSize, 0, 0, 0);
+ openCLResources.getQueue().finish();
+ }
+ }
+ }
+ showStatus("Calculating global repetition... 100%");
+
+ // Read the repetition map back from the device and calculate the weighted average
+ openCLResources.getQueue().putReadBuffer(clRepetitionMap, true);
+ openCLResources.getQueue().putReadBuffer(clWeightsSumMap, true);
+
+ for(int z=blockRadiusDepth; z 0.0f) {
+ float rmse = repetitionMap[index];
+ if (rmse == 0.0f) { // Special case where RMSE is 0, 1/rmse would be undefined but we want perfect similarity
+ repetitionMap[index] = 1.0f;
+ } else {
+ repetitionMap[index] = 1.0f / rmse;
+ }
+ }
+ }
+ }
+ }
+
+ // Apply relevance mask
+ if(relevanceMask.getRelevanceConstant()>0.0f) {
+ repetitionMap = Utils.applyMask3D(repetitionMap, imageWidth, imageHeight, imageDepth,
+ relevanceMask.getRelevanceMask());
+ }
+
+ // Normalize repetition map (avoiding masked pixels)
+ if(normalizeOutput) {
+ repetitionMap = Utils.normalizeImage3D(repetitionMap, imageWidth, imageHeight, imageDepth, blockRadiusWidth,
+ blockRadiusHeight, blockRadiusDepth, relevanceMask.getRelevanceMask());
+ }
+
+ // Release memory
+ openCLResources.getContext().release();
+
+ return repetitionMap;
+ }
+
+
+ /**
+ * Computes the Global Repetition Map based on the absolute difference of standard deviations for a 3D image using OpenCL.
+ *
+ * This method calculates the global repetition map based on the absolute difference of standard deviations. The result is stored in a global
+ * repetition map, indicating the relative repetition of each structural element across the image.
+ * Optionally, a relevance mask can be applied to filter the results based on local standard deviations and a
+ * relevance constant.
+ *
+ * @param inputImage a {@link Utils.InputImage3D} object
+ * @param localStatistics a {@link CLLocalStatistics} object
+ * @param blockWidth the width of the block used for the analysis
+ * @param blockHeight the height of the block used for the analysis
+ * @param blockDepth the depth of the block used for the analysis
+ * @param blockSize the size of the block used for the analysis, after removing pixels outside the inbound circle/ellipse
+ * @param relevanceMask a {@link Utils.RelevanceMask} object
+ * @param nPixels the number of structurally relevant pixels (i.e., non-masked pixels)
+ * @param normalizeOutput a boolean to either normalize the output or not
+ * @param openCLResources an {@link OpenCLResources} object
+ * @return a float array representing the global repetition map, indicating the degree of repetition of each
+ * structural element across the entire image.
+ */
+ public static float[] getGlobalAbsDiffStds3D(Utils.InputImage3D inputImage, CLLocalStatistics localStatistics,
+ int blockWidth, int blockHeight, int blockDepth, int blockSize,
+ Utils.RelevanceMask relevanceMask, float nPixels, boolean normalizeOutput,
+ OpenCLResources openCLResources)
+ {
+ IJ.log("Calculating Structural Repetition Scores...");
+
+ // Cache variables
+ int imageWidth = inputImage.getWidth();
+ int imageHeight = inputImage.getHeight();
+ int imageDepth = inputImage.getDepth();
+ int imageSize = inputImage.getSize();
+ int blockRadiusWidth = blockWidth / 2;
+ int blockRadiusHeight = blockHeight / 2;
+ int blockRadiusDepth = blockDepth / 2;
+
+ // Create and fill OpenCL buffers
+ float[] repetitionMap = new float[imageSize];
+ CLBuffer clRepetitionMap = CLUtils.createAndFillCLBuffer(openCLResources.getContext(), imageSize,
+ READ_WRITE, repetitionMap);
+
+ float[] weightsSumMap = new float[imageSize];
+ CLBuffer clWeightsSumMap = CLUtils.createAndFillCLBuffer(openCLResources.getContext(), imageSize,
+ READ_WRITE, weightsSumMap);
+
+ // Build OpenCL program
+ String programStringGetGlobalAbsDiffStds3D = getResourceAsString(CLUtils.class, "kernelGetGlobalAbsDiffStds3D.cl");
+ programStringGetGlobalAbsDiffStds3D = replaceFirst(programStringGetGlobalAbsDiffStds3D, "$WIDTH$", "" + imageWidth);
+ programStringGetGlobalAbsDiffStds3D = replaceFirst(programStringGetGlobalAbsDiffStds3D, "$HEIGHT$", "" + imageHeight);
+ programStringGetGlobalAbsDiffStds3D = replaceFirst(programStringGetGlobalAbsDiffStds3D, "$DEPTH$", "" + imageDepth);
+ programStringGetGlobalAbsDiffStds3D = replaceFirst(programStringGetGlobalAbsDiffStds3D, "$BLOCK_SIZE$", "" + blockSize);
+ programStringGetGlobalAbsDiffStds3D = replaceFirst(programStringGetGlobalAbsDiffStds3D, "$BRW$", "" + blockRadiusWidth);
+ programStringGetGlobalAbsDiffStds3D = replaceFirst(programStringGetGlobalAbsDiffStds3D, "$BRH$", "" + blockRadiusHeight);
+ programStringGetGlobalAbsDiffStds3D = replaceFirst(programStringGetGlobalAbsDiffStds3D, "$BRZ$", "" + blockRadiusDepth);
+ programStringGetGlobalAbsDiffStds3D = replaceFirst(programStringGetGlobalAbsDiffStds3D, "$FILTER_PARAM$", "" + relevanceMask.getNoiseMeanVariance());
+ programStringGetGlobalAbsDiffStds3D = replaceFirst(programStringGetGlobalAbsDiffStds3D, "$THRESHOLD$", "" + relevanceMask.getRelevanceThreshold());
+ programStringGetGlobalAbsDiffStds3D = replaceFirst(programStringGetGlobalAbsDiffStds3D, "$EPSILON$", "" + Utils.EPSILON);
+ CLProgram programGetGlobalAbsDiffStds3D = openCLResources.getContext().createProgram(programStringGetGlobalAbsDiffStds3D).build();
+
+ // Create OpenCL kernel and set args
+ CLKernel kernelGetGlobalAbsDiffStds3D = programGetGlobalAbsDiffStds3D.createCLKernel("kernelGetGlobalAbsDiffStds3D");
+
+ int argn = 0;
+ kernelGetGlobalAbsDiffStds3D.setArg(argn++, localStatistics.getCLImageArray());
+ kernelGetGlobalAbsDiffStds3D.setArg(argn++, localStatistics.getCLLocalMeans());
+ kernelGetGlobalAbsDiffStds3D.setArg(argn++, localStatistics.getCLLocalStds());
+ kernelGetGlobalAbsDiffStds3D.setArg(argn++, clWeightsSumMap);
+ kernelGetGlobalAbsDiffStds3D.setArg(argn++, clRepetitionMap);
+
+
+ // Calculate weighted mean Pearson's map
+ int nXBlocks = imageWidth / 64 + ((imageWidth % 64 == 0) ? 0 : 1);
+ int nYBlocks = imageHeight / 64 + ((imageHeight % 64 == 0) ? 0 : 1);
+ int nZBlocks = imageDepth / blockDepth + ((imageDepth % blockDepth == 0) ? 0 : 1);
+ int totalBlocks = nXBlocks * nYBlocks * nZBlocks;
+
+ for (int nZB = 0; nZB < nZBlocks; nZB++) {
+ int zWorkSize = min(blockDepth, imageDepth - nZB * blockDepth);
+ for (int nYB = 0; nYB < nYBlocks; nYB++) {
+ int yWorkSize = min(64, imageHeight - nYB * 64);
+ for (int nXB = 0; nXB < nXBlocks; nXB++) {
+ int xWorkSize = min(64, imageWidth - nXB * 64);
+ float progressPercentage = ((nZB * nYBlocks * nXBlocks) + (nYB * nXBlocks) + nXB) / (float) totalBlocks * 100;
+ showStatus(String.format("Calculating global repetition... %d%%", Math.round(progressPercentage)));
+ openCLResources.getQueue().put3DRangeKernel(kernelGetGlobalAbsDiffStds3D,
+ nXB * 64, nYB * 64, nZB * blockDepth,
+ xWorkSize, yWorkSize, zWorkSize, 0, 0, 0);
+ openCLResources.getQueue().finish();
+ }
+ }
+ }
+ showStatus("Calculating global repetition... 100%");
+
+ // Read the repetition map back from the device and calculate the weighted average
+ openCLResources.getQueue().putReadBuffer(clRepetitionMap, true);
+ openCLResources.getQueue().putReadBuffer(clWeightsSumMap, true);
+
+ for(int z=blockRadiusDepth; z 0.0f) {
+ float diffStd = repetitionMap[index];
+ if (diffStd == 0.0f) { // Special case where diffStd is 0, 1/diffStd would be undefined but we want perfect similarity
+ repetitionMap[index] = 1.0f;
+ } else {
+ repetitionMap[index] = 1.0f / diffStd;
+ }
+ }
+ }
+ }
+ }
+
+ // Apply relevance mask
+ if(relevanceMask.getRelevanceConstant()>0.0f) {
+ repetitionMap = Utils.applyMask3D(repetitionMap, imageWidth, imageHeight, imageDepth,
+ relevanceMask.getRelevanceMask());
+ }
+
+ // Normalize repetition map (avoiding masked pixels)
+ if(normalizeOutput) {
+ repetitionMap = Utils.normalizeImage3D(repetitionMap, imageWidth, imageHeight, imageDepth, blockRadiusWidth,
+ blockRadiusHeight, blockRadiusDepth, relevanceMask.getRelevanceMask());
+ }
+
+ // Release memory
+ openCLResources.getContext().release();
+
+ return repetitionMap;
+ }
+
+
+ /**
+ * Calculate a global repetition map for 3D data, based on the selected metric.
+ *
+ * @param metric The selected metric for repetition calculation.
+ * @param inputImage A {@link Utils.InputImage2D}.
+ * @param blockWidth The width of the block used for the analysis.
+ * @param blockHeight The height of the block used for the analysis.
+ * @param blockDepth The depth of the block used for the analysis.
+ * @param blockSize The size of the block used for the analysis, after removing pixels outside the inbound circle/ellipse.
+ * @param localStatistics A {@link CLLocalStatistics} object.
+ * @param relevanceMask A {@link Utils.RelevanceMask} object
+ * @param nPixels The number of structurally relevant pixels (i.e., non-masked pixels)
+ * @param normalizeOutput A boolean to either normalize the output or not.
+ *
+ * @return The calculated repetition map.
+ */
+ public static float[] calculateGlobalRepetitionMap3D(String metric, Utils.InputImage3D inputImage, int blockWidth,
+ int blockHeight, int blockDepth, int blockSize,
+ CLLocalStatistics localStatistics,
+ Utils.RelevanceMask relevanceMask, float nPixels,
+ boolean normalizeOutput, OpenCLResources openCLResources)
+ {
+
+ if(metric.equals("Pearson's R")) {
+ return CLUtils.getGlobalPearson3D(inputImage, localStatistics, blockWidth, blockHeight, blockDepth,
+ blockSize, relevanceMask, nPixels, normalizeOutput, openCLResources);
+ }else if(metric.equals("Cosine similarity")) {
+ return CLUtils.getGlobalCosineSimilarity3D(inputImage, localStatistics, blockWidth, blockHeight, blockDepth,
+ relevanceMask, nPixels, normalizeOutput, openCLResources);
+ }else if(metric.equals("SSIM")){
+ return CLUtils.getGlobalSsim3D(inputImage, localStatistics, blockWidth, blockHeight, blockDepth, blockSize,
+ relevanceMask, nPixels, normalizeOutput, openCLResources);
+ }else if(metric.equals("NRMSE")){
+ return CLUtils.getGlobalNrmse3D(inputImage, localStatistics, blockWidth, blockHeight, blockDepth, blockSize,
+ relevanceMask, nPixels, normalizeOutput, openCLResources);
+ }else if(metric.equals("Abs. diff. of StdDevs")){
+ return CLUtils.getGlobalAbsDiffStds3D(inputImage, localStatistics, blockWidth, blockHeight, blockDepth,
+ blockSize, relevanceMask, nPixels, normalizeOutput, openCLResources);
+ }else{
+ return null;
+ }
+ }
+
+
+}
diff --git a/src/main/java/CalculateLocalMeans_.java b/src/main/java/CalculateLocalMeans_.java
index c7b39a6..a0186c9 100644
--- a/src/main/java/CalculateLocalMeans_.java
+++ b/src/main/java/CalculateLocalMeans_.java
@@ -261,7 +261,7 @@ public void run(String s) {
clLocalStds = context.createFloatBuffer(wh, READ_WRITE);
// Create OpenCL program
- String programStringGetPatchMeans = getResourceAsString(BlockRedundancy2D_.class, "kernelGetPatchMeans2D.cl");
+ String programStringGetPatchMeans = getResourceAsString(BlockRepetition2D_.class, "kernelGetLocalStatistics2D.cl");
programStringGetPatchMeans = replaceFirst(programStringGetPatchMeans, "$WIDTH$", "" + w);
programStringGetPatchMeans = replaceFirst(programStringGetPatchMeans, "$HEIGHT$", "" + h);
programStringGetPatchMeans = replaceFirst(programStringGetPatchMeans, "$PATCH_SIZE$", "" + patchSize);
diff --git a/src/main/java/GATMinimizer2D.java b/src/main/java/GATMinimizer2D.java
index be6e12e..dc0dc02 100644
--- a/src/main/java/GATMinimizer2D.java
+++ b/src/main/java/GATMinimizer2D.java
@@ -13,7 +13,7 @@
public class GATMinimizer2D implements UserFunction {
-
+//TODO: THIS BUGS OUT WHEN IMAGE IS TOO SMAL DUE TO HOW WE DEFINE BLOCK SIZE
public double gain, sigma, offset;
private final int width, height;
//public boolean isCalculated = false;
diff --git a/src/main/java/GATMinimizer3D.java b/src/main/java/GATMinimizer3D.java
index 730a7cb..0f7b253 100644
--- a/src/main/java/GATMinimizer3D.java
+++ b/src/main/java/GATMinimizer3D.java
@@ -10,6 +10,8 @@
import ij.measure.Minimizer;
import ij.measure.UserFunction;
+
+import static java.lang.Math.max;
import static java.lang.Math.sqrt;
@@ -95,8 +97,9 @@ public double userFunction(double[] params, double v) {
}
// Define the dimensions of the window used to estimate the noise variance (adjusted to image size to avoid out of bounds)
- int blockWidth = width/6; // bounding box width
- int blockHeight = height/6; // bounding box height
+ // max() is used to prevent block dimensions == 0
+ int blockWidth = max(width/6, 1); // bounding box width
+ int blockHeight = max(height/6, 1); // bounding box height
// Get number of blocks
int nBlocksX = width / blockWidth;
diff --git a/src/main/java/GlobalRedundancy.java b/src/main/java/GlobalRedundancy.java
deleted file mode 100644
index 6313ff6..0000000
--- a/src/main/java/GlobalRedundancy.java
+++ /dev/null
@@ -1,621 +0,0 @@
-/**
- * Calculates the Global Repetition Map. Each local neighbourhood is used as a reference for a round of Block Repetition.
- * Each pairwise comparison is weighted based on the similarity between reference and test blocks, and the resulting Block Repetition Map is averaged.
- * The average value is plotted at the center position of the reference neighbourhood.
- *
- * @author Afonso Mendes
- *
- **/
-
-
-import com.jogamp.opencl.*;
-import ij.IJ;
-import ij.ImagePlus;
-import ij.measure.UserFunction;
-import ij.plugin.LutLoader;
-import ij.process.FloatProcessor;
-import ij.process.ImageProcessor;
-import ij.process.LUT;
-
-import java.awt.image.IndexColorModel;
-import java.io.ByteArrayOutputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.nio.FloatBuffer;
-import java.util.*;
-import static com.jogamp.opencl.CLMemory.Mem.READ_ONLY;
-import static com.jogamp.opencl.CLMemory.Mem.READ_WRITE;
-import static ij.IJ.showStatus;
-import static java.lang.Math.*;
-
-public class GlobalRedundancy implements Runnable, UserFunction{
-
- // ------------------------ //
- // ---- OpenCL formats ---- //
- // ------------------------ //
-
- static private CLContext context;
- static private CLCommandQueue queue;
- static private CLProgram programGetLocalMeans, programGetPearsonMap, programGetDiffStdMap;
- static private CLKernel kernelGetLocalMeans, kernelGetPearsonMap, kernelGetDiffStdMap;
-
- private CLBuffer clRefPixels, clLocalMeans, clLocalStds, clPearsonMap, clDiffStdMap, clWeightsSumMap;
-
-
- // -------------------------- //
- // ---- Image parameters ---- //
- // -------------------------- //
-
- public float[] refPixels;
- public int w, h, wh, bW, bH, patchSize, bRW, bRH, sizeWithoutBorders, scaleFactor, level, w0, h0;
- public float filterConstant, EPSILON;
- public String metric;
-
- public GlobalRedundancy(float[] refPixels, int w, int h, int bW, int bH, int bRW, int bRH, int patchSize, float EPSILON, CLContext context,
- CLCommandQueue queue, int scaleFactor, float filterConstant, int level, int w0, int h0, String metric){
-
- this.refPixels = refPixels;
- this.w = w;
- this.h = h;
- wh = w * h;
- this.bW = bW;
- this.bH = bH;
- this.bRW = bRW;
- this.bRH = bRH;
- sizeWithoutBorders = (w-bRW*2)*(h-bRH*2);
- this.patchSize = patchSize; // Number of pixels in an elliptical patch
- this.EPSILON = EPSILON;
- this.context = context;
- this.queue = queue;
- this.scaleFactor = scaleFactor;
- this.filterConstant = filterConstant;
- this.level = level;
- this.w0 = w0;
- this.h0 = h0;
- this.metric = metric;
- }
-
- @Override
- public void run(){
-
- IJ.log("Calculating at scale level "+level+"...");
-
-
- // ------------------------------------------------------------------------------------------------------ //
- // ---- Write input image (variance-stabilized, mean-subtracted and normalized) to the OpenCL device ---- //
- // ------------------------------------------------------------------------------------------------------ //
-
- clRefPixels = context.createFloatBuffer(wh, READ_ONLY);
- fillBufferWithFloatArray(clRefPixels, refPixels);
- queue.putWriteBuffer(clRefPixels, true);
-
-
- // ------------------------------------------------------------------ //
- // ---- Calculate local means and local standard deviations maps ---- //
- // ------------------------------------------------------------------ //
-
- float[] localMeans = new float[wh];
- Arrays.fill(localMeans, 0.0f);
-
- float[] localStds = new float[wh];
- Arrays.fill(localStds, 0.0f);
-
- // Create OpenCL program
- String programStringGetLocalMeans = getResourceAsString(RedundancyMap_.class, "kernelGetLocalMeans.cl");
- programStringGetLocalMeans = replaceFirst(programStringGetLocalMeans, "$WIDTH$", "" + w);
- programStringGetLocalMeans = replaceFirst(programStringGetLocalMeans, "$HEIGHT$", "" + h);
- programStringGetLocalMeans = replaceFirst(programStringGetLocalMeans, "$PATCH_SIZE$", "" + patchSize);
- programStringGetLocalMeans = replaceFirst(programStringGetLocalMeans, "$BRW$", "" + bRW);
- programStringGetLocalMeans = replaceFirst(programStringGetLocalMeans, "$BRH$", "" + bRH);
- programStringGetLocalMeans = replaceFirst(programStringGetLocalMeans, "$EPSILON$", "" + EPSILON);
- programGetLocalMeans = context.createProgram(programStringGetLocalMeans).build();
- System.out.println(programGetLocalMeans.getBuildLog());
- // Create, fill and write OpenCL buffers
- clLocalMeans = context.createFloatBuffer(wh, READ_WRITE);
- fillBufferWithFloatArray(clLocalMeans, localMeans);
- queue.putWriteBuffer(clLocalMeans, true);
-
- clLocalStds = context.createFloatBuffer(wh, READ_WRITE);
- fillBufferWithFloatArray(clLocalStds, localStds);
- queue.putWriteBuffer(clLocalStds, true);
-
- // Create OpenCL kernel and set kernel arguments
- kernelGetLocalMeans = programGetLocalMeans.createCLKernel("kernelGetLocalMeans");
-
- int argn = 0;
- kernelGetLocalMeans.setArg(argn++, clRefPixels);
- kernelGetLocalMeans.setArg(argn++, clLocalMeans);
- kernelGetLocalMeans.setArg(argn++, clLocalStds);
-
- // Calculate local means and standard deviations map in the GPU
- showStatus("Calculating local means...");
- queue.put2DRangeKernel(kernelGetLocalMeans, 0, 0, w, h, 0, 0);
- queue.finish();
-
- // Read the local standard deviations map from the OpenCL device
- queue.putReadBuffer(clLocalStds, true);
- for (int y=bRH; y clBuffer, float[] pixels) {
- FloatBuffer buffer = clBuffer.getBuffer();
- for(int n=0; n 0.0f) {
+ nPixels += 1.0f;
+ }
+ }
+ }
+
+ // Calculate global repetition map
+ float[] repetitionMap;
+ repetitionMap = CLUtils.calculateGlobalRepetitionMap2D(metric, inputImage, blockWidth, blockHeight, blockSize,
+ localStatistics, relevanceMask, nPixels, normalizeOutput, openCLResources);
+
+ // Display results
+ Utils.displayResults2D(inputImage, repetitionMap);
+
+ // Stop timer
+ long elapsedTime = System.currentTimeMillis() - start;
+ IJ.log("Finished!");
+ IJ.log("Elapsed time: " + elapsedTime/1000 + " sec");
+ IJ.log("--------");
+
+ }
+}
diff --git a/src/main/java/GlobalRepetition3D_.java b/src/main/java/GlobalRepetition3D_.java
index fd17e37..fd9a0ad 100644
--- a/src/main/java/GlobalRepetition3D_.java
+++ b/src/main/java/GlobalRepetition3D_.java
@@ -1,764 +1,158 @@
+/**
+ * This class calculates block repetition maps for 3D data.
+ *
+ * @author Afonso Mendes
+ */
import com.jogamp.opencl.*;
import ij.*;
import ij.gui.NonBlockingGenericDialog;
-import ij.measure.Calibration;
-import ij.plugin.LutLoader;
import ij.plugin.PlugIn;
-import ij.process.FloatProcessor;
-import ij.process.LUT;
-
-import java.awt.image.IndexColorModel;
-import java.io.ByteArrayOutputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.nio.FloatBuffer;
-import java.util.Arrays;
-
-import static com.jogamp.opencl.CLMemory.Mem.READ_ONLY;
-import static com.jogamp.opencl.CLMemory.Mem.READ_WRITE;
-import static ij.IJ.showStatus;
-import static java.lang.Math.*;
+import static ij.WindowManager.getIDList;
+import static ij.WindowManager.getImageCount;
public class GlobalRepetition3D_ implements PlugIn {
- static private CLContext context;
- static private CLPlatform clPlatformMaxFlop;
- static private CLCommandQueue queue;
- static private CLProgram programGetLocalMeans3D, programGetCosineSimMap3D, programGetRelevanceMap3D;
-
- static private CLKernel kernelGetLocalMeans3D, kernelGetCosineSimMap3D, kernelGetRelevanceMap3D;
- private CLBuffer clRefPixels, clLocalMeans, clLocalStds, clCosineSimMap, clRelevanceMap, clWeightsSumMap;
+ // Define constants for metric choices
+ public static final String[] METRICS = {
+ "Pearson's R",
+ "Cosine similarity",
+ "SSIM",
+ "NRMSE",
+ "Abs. diff. of StdDevs"
+ };
@Override
public void run(String s) {
- float EPSILON = 0.0000001f;
-
// -------------------- //
// ---- Dialog box ---- //
// -------------------- //
- // Define metric possibilities
- String[] metrics = new String[2];
- metrics[0] = "Pearson's R";
- metrics[1] = "Cosine similarity";
+ // Get all open image titles
+ int nImages = getImageCount();
+ if (nImages < 1) {
+ IJ.error("No images found. Open an image and try again.");
+ return;
+ }
+
+ // Get image IDs from titles
+ int[] ids = getIDList();
+ String[] titles = new String[nImages];
+ for (int i = 0; i < nImages; i++) {
+ titles[i] = WindowManager.getImage(ids[i]).getTitle();
+ }
// Initialize dialog box
NonBlockingGenericDialog gd = new NonBlockingGenericDialog("SReD: Global Repetition (3D)");
- gd.addNumericField("Block width (px): ", 3, 2);
- gd.addNumericField("Block height (px): ", 3, 2);
- gd.addNumericField("Block depth (px): ", 3, 2);
- gd.addSlider("Filter constant: ", 0.0f, 10.0f, 0.0, 0.1f);
- gd.addChoice("Metric:", metrics, metrics[1]);
+ gd.addChoice("Image:", titles, titles[0]);
+ gd.addNumericField("Block width (px):", 5);
+ gd.addNumericField("Block height (px):", 5);
+ gd.addNumericField("Block depth (px):", 5);
+ gd.addNumericField("Relevance constant:", 0.0f);
+ gd.addChoice("Metric:", METRICS, METRICS[0]);
gd.addCheckbox("Normalize output?", true);
gd.addCheckbox("Use device from preferences?", false);
-
+ gd.addHelp("https://github.com/HenriquesLab/SReD/wiki");
gd.showDialog();
if(gd.wasCanceled()) return;
// Get dialog parameters
- int bW = (int) gd.getNextNumber(); // Patch width
- int bH = (int) gd.getNextNumber(); // Patch height
- int bZ = (int) gd.getNextNumber(); // Patch depth
-
- float filterConstant = (float) gd.getNextNumber();
-
+ String imageTitle = gd.getNextChoice();
+ int imageID = Utils.getImageIDByTitle(titles, ids, imageTitle);
+ int blockWidth = (int) gd.getNextNumber();
+ int blockHeight = (int) gd.getNextNumber();
+ int blockDepth = (int) gd.getNextNumber();
+ float relevanceConstant = (float) gd.getNextNumber();
String metric = gd.getNextChoice();
-
boolean normalizeOutput = gd.getNextBoolean();
-
boolean useDevice = gd.getNextBoolean();
- // Check if patch dimensions are odd, otherwise kill program
- if(bW%2==0 || bH%2==0) {
- IJ.error("Block dimensions must be odd (e.g., 3x3 or 5x5). Please try again.");
- return;
- }
-
- // Check if patch has at least 3 slices, otherwise kill program
- if(bZ<3) {
- IJ.error("Block must have at least 3 slices. Please try again.");
+ // Check if block dimensions are odd, otherwise kill program
+ if(blockWidth%2==0 || blockHeight%2==0 || blockDepth%2==0) {
+ IJ.error("Block dimensions must be odd. Please try again.");
return;
}
- // Calculate block radius
- int bRW = bW/2; // Patch radius (x-axis)
- int bRH = bH/2; // Patch radius (y-axis)
- int bRZ = bZ/2; // Patch radius (z-axis)
-
- // Get ImagePlus and ImageStack
- ImagePlus imp0 = WindowManager.getCurrentImage();
- if(imp0==null) {
- IJ.error("Image not found. Try again.");
+ // Check if block has at least 3 slices, otherwise kill program
+ if(blockDepth<3) {
+ IJ.error("Block depth must be at least 3. Please try again.");
return;
}
- ImageStack ims0 = imp0.getStack();
-
- // Get calibration parameters
- Calibration calibration = imp0.getCalibration();
-
- // Get image dimensions
- int w = ims0.getWidth();
- int h = ims0.getHeight();
- int z = ims0.getSize();
- int wh = w*h;
- int whz = w*h*z;
-
-
- // --------------------- //
- // ---- Start timer ---- //
- // --------------------- //
+ IJ.log("--------");
+ IJ.log("SReD is running, please wait...");
- IJ.log("SReD has started, please wait.");
+ // Start timer
long start = System.currentTimeMillis();
-
- // ---------------------------------- //
- // ---- Stabilize noise variance ---- //
- // ---------------------------------- //
-
- IJ.log("Stabilizing noise variance of the image...");
-
- float[][] refPixels = new float[z][wh];
- for(int n=0; nimgMax) {
- imgMax = pixelValue;
- }
- }
-
- // Normalize
- for(int i=0; i nFlops) {
- nFlops = clDevice.getMaxComputeUnits() * clDevice.getMaxClockFrequency();
- clPlatformMaxFlop = allPlatform;
- }
- }
- }
- IJ.log("--------");
-
- // Create OpenCL context
- context = CLContext.create(clPlatformMaxFlop);
-
- // Choose the best device (i.e., Filter out CPUs if GPUs are available (FLOPS calculation was giving
- // higher ratings to CPU vs. GPU))
- //TODO: Rate devices on Mandelbrot and chooose the best, GPU will perform better
- CLDevice[] allDevices = context.getDevices();
-
- boolean hasGPU = false;
- for(int i=0; i 0.0f) {
+ nPixels += 1.0f;
}
}
}
}
+ // Calculate global repetition map
+ float[] repetitionMap;
+ repetitionMap = CLUtils.calculateGlobalRepetitionMap3D(metric, inputImage, blockWidth, blockHeight, blockDepth,
+ blockSize, localStatistics, relevanceMask, nPixels, normalizeOutput, openCLResources);
+ // Display results
+ Utils.displayResults3D(inputImage, repetitionMap);
-
- // --------------------------------------------------------------- //
- // ---- Calculate block repetition map with the chosen metric ---- //
- // --------------------------------------------------------------- //
-
- float[] repetitionMap = new float[whz];
- Arrays.fill(repetitionMap, 0.0f);
-
- float[] weightSumMap = new float[whz];
- Arrays.fill(weightSumMap, 0.0f);
-
- if(metric==metrics[1]) { // Cosine similarity
- showStatus("Calculating global repetition... 0%");
-
- // Build OpenCL program
- String programStringGetCosineSim3D = getResourceAsString(GlobalRepetition3D_.class, "kernelGetCosineSimMap3D.cl");
- programStringGetCosineSim3D = replaceFirst(programStringGetCosineSim3D, "$WIDTH$", "" + w);
- programStringGetCosineSim3D = replaceFirst(programStringGetCosineSim3D, "$HEIGHT$", "" + h);
- programStringGetCosineSim3D = replaceFirst(programStringGetCosineSim3D, "$DEPTH$", "" + z);
- programStringGetCosineSim3D = replaceFirst(programStringGetCosineSim3D, "$PATCH_SIZE$", "" + patchSize);
- programStringGetCosineSim3D = replaceFirst(programStringGetCosineSim3D, "$BRW$", "" + bRW);
- programStringGetCosineSim3D = replaceFirst(programStringGetCosineSim3D, "$BRH$", "" + bRH);
- programStringGetCosineSim3D = replaceFirst(programStringGetCosineSim3D, "$BRZ$", "" + bRZ);
- programStringGetCosineSim3D = replaceFirst(programStringGetCosineSim3D, "$FILTERPARAM$", "" + noiseVar);
- programStringGetCosineSim3D = replaceFirst(programStringGetCosineSim3D, "$THRESHOLD$", "" + threshold);
- programStringGetCosineSim3D = replaceFirst(programStringGetCosineSim3D, "$EPSILON$", "" + EPSILON);
- programGetCosineSimMap3D = context.createProgram(programStringGetCosineSim3D).build();
-
- // Fill OpenCL buffers
- clCosineSimMap = context.createFloatBuffer(whz, READ_WRITE);
- fillBufferWithFloatArray(clCosineSimMap, repetitionMap);
- queue.putWriteBuffer(clCosineSimMap, true);
-
- clWeightsSumMap = context.createFloatBuffer(whz, READ_WRITE);
- fillBufferWithFloatArray(clWeightsSumMap, weightSumMap);
- queue.putWriteBuffer(clWeightsSumMap, true);
-
- // Create kernel and set args
- kernelGetCosineSimMap3D = programGetCosineSimMap3D.createCLKernel("kernelGetCosineSimMap3D");
-
- argn = 0;
- kernelGetCosineSimMap3D.setArg(argn++, clRefPixels);
- kernelGetCosineSimMap3D.setArg(argn++, clLocalStds);
- kernelGetCosineSimMap3D.setArg(argn++, clWeightsSumMap);
- kernelGetCosineSimMap3D.setArg(argn++, clCosineSimMap);
-
- // Calculate Pearson's correlation coefficient
- currentBlock = 1.0f; // Restart the counter
- for(int nZB=0; nZB clBuffer, float[] pixels) {
- FloatBuffer buffer = clBuffer.getBuffer();
- for (int n = 0; n < pixels.length; n++) {
- buffer.put(n, pixels[n]);
- }
- }
-
- // Read a kernel from the resources
- public static String getResourceAsString(Class c, String resourceName) {
- InputStream programStream = c.getResourceAsStream("/" + resourceName);
- String programString = "";
-
- try {
- programString = inputStreamToString(programStream);
- } catch (IOException var5) {
- var5.printStackTrace();
- }
-
- return programString;
- }
-
- public static String inputStreamToString(InputStream inputStream) throws IOException {
- ByteArrayOutputStream result = new ByteArrayOutputStream();
- byte[] buffer = new byte[1024];
- int length;
- while ((length = inputStream.read(buffer)) != -1) {
- result.write(buffer, 0, length);
- }
- return result.toString("UTF-8");
- }
-
- public static String replaceFirst(String source, String target, String replacement) {
- int index = source.indexOf(target);
- if (index == -1) {
- return source;
- }
-
- return source.substring(0, index)
- .concat(replacement)
- .concat(source.substring(index + target.length()));
}
-
- // Get mean and variance of a patch
- public double[] getMeanAndVarBlock3D(float[] pixels, int width, int height, int depth, int xStart, int yStart, int zStart, int xEnd, int yEnd, int zEnd) {
- double mean = 0;
- double var;
-
- double sq_sum = 0;
-
- int bWidth = xEnd-xStart;
- int bHeight = yEnd - yStart;
- int bDepth = zEnd - zStart;
- int bWHZ = bWidth*bHeight*bDepth;
-
- for(int z=zStart; z nFlops) {
- nFlops = clDevice.getMaxComputeUnits() * clDevice.getMaxClockFrequency();
- clPlatformMaxFlop = allPlatform;
- }
- }
- }
- IJ.log("--------");
-
- // Create context
- context = CLContext.create(clPlatformMaxFlop);
-
- // Choose the best device (i.e., Filter out CPUs if GPUs are available (FLOPS calculation was giving
- // higher ratings to CPU vs. GPU))
- //TODO: Rate devices on Mandelbrot and chooose the best, GPU will perform better
- CLDevice[] allDevices = context.getDevices();
-
- boolean hasGPU = false;
- for (int i = 0; i < allDevices.length; i++) {
- if (allDevices[i].getType() == CLDevice.Type.GPU) {
- hasGPU = true;
- }
- }
- CLDevice chosenDevice;
- if (hasGPU) {
- chosenDevice = context.getMaxFlopsDevice(CLDevice.Type.GPU);
- } else {
- chosenDevice = context.getMaxFlopsDevice();
- }
-
- // Get chosen device from preferences
- if(useDevice){
- String deviceName = Prefs.get("SReD.OpenCL.device", null);
- for (CLDevice device : allDevices) {
- if (device.getName().equals(deviceName)) {
- chosenDevice = device;
- break;
- }
- }
- }
-
- IJ.log("Chosen device: " + chosenDevice.getName());
- IJ.log("--------");
-
- // Create command queue
- queue = chosenDevice.createCommandQueue();
-
-
- // ------------------------------------------------- //
- // ---- Get reference image and some parameters ---- //
- // ------------------------------------------------- //
-
- ImagePlus imp0 = WindowManager.getCurrentImage();
- if (imp0 == null) {
- IJ.error("No image found. Please open an image and try again.");
- return;
- }
-
- FloatProcessor fp0 = imp0.getProcessor().convertToFloatProcessor();
- float[] refPixels0 = (float[]) fp0.getPixels();
- int w0 = fp0.getWidth();
- int h0 = fp0.getHeight();
-
-
- //int elementCount = w*h;
- //int localWorkSize = min(chosenDevice.getMaxWorkGroupSize(), 256);
- //int globalWorkSize = roundUp(localWorkSize, elementCount);
-
-
- // ---------------------------------- //
- // ---- Calculate Redundancy Map ---- //
- // ---------------------------------- //
-
- IJ.log("Calculating redundancy...please wait...");
-
- int rounds = 5; // How many scale levels should be analyzed
- if(multiScale == 0){
-
- int scaleFactor = 1;
-
- if(useTime == 0){
-
- // --------------------------------------------------------------------------- //
- // ---- Stabilize noise variance using the Generalized Anscombe transform ---- //
- // --------------------------------------------------------------------------- //
- GATMinimizer2D minimizer = new GATMinimizer2D(refPixels0, w0, h0, 0, 100, 0);
- minimizer.run();
-
- refPixels0 = VarianceStabilisingTransform2D_.getGAT(refPixels0, minimizer.gain, minimizer.sigma, minimizer.offset);
-
-
- // ------------------- //
- // ---- Normalize ---- //
- // ------------------- //
-
- // Cast to "double" type
- double[] refPixelsDouble = new double[w0*h0];
- for(int i=0; iimgMax){
- imgMax = pixelValue;
- }
- }
-
- // Remap pixels
- for(int i=0; iimgMax){
- imgMax = pixelValue;
- }
- }
-
- // Normalize
- for(int i=0; i1) {
- // Sequential blur and downscale until reaching the desired dimensions (skip first iteration)
- for(int j=0; j featureVectorList = new ArrayList<>();
- for(double[] featureVector : featureVectors){
- featureVectorList.add(new DoublePoint(featureVector));
- }
-
- int optimalK = 1;
- double previousWCSS = Double.MAX_VALUE;
- for(int k=1; k<=maxK; k++){
- KMeansPlusPlusClusterer kMeans = new KMeansPlusPlusClusterer<>(k, 1000, new EuclideanDistance());
- List> clusters = kMeans.cluster(featureVectorList);
- double wcss = 0.0;
- for (CentroidCluster cluster : clusters) {
- for (DoublePoint featureVector : cluster.getPoints()) {
- double distance = new EuclideanDistance().compute(cluster.getCenter().getPoint(), featureVector.getPoint());
- wcss += distance * distance;
- }
- }
- if (wcss > previousWCSS) {
- break;
- }
- previousWCSS = wcss;
- optimalK = k;
- }
- return optimalK;
- }
-
- public static float[] applyGaussianBlur(float[] input, int width, int height, float sigma) {
- // Create a Gaussian kernel
- int size = (int) Math.ceil(sigma * 3) * 2 + 1;
- float[] kernel = new float[size];
- float sum = 0;
- for (int i = 0; i < size; i++) {
- float x = (float) (i - (size - 1) / 2.0);
- kernel[i] = (float) Math.exp(-x * x / (2 * sigma * sigma));
- sum += kernel[i];
- }
- for (int i = 0; i < size; i++) {
- kernel[i] /= sum;
- }
-
- // Create a temporary array for the blurred image
- float[] temp = new float[input.length];
-
- // Blur each row
- for (int y = 0; y < height; y++) {
- for (int x = 0; x < width; x++) {
- float sum1 = 0;
- float sum2 = 0;
- for (int i = 0; i < size; i++) {
- int x1 = x + i - (size - 1) / 2;
- if(x1 < 0 || x1 >= width) {
- continue;
- }
- int index = y*width+x1;
- float value = input[index];
- float weight = kernel[i];
- sum1 += value * weight;
- sum2 += weight;
- }
- int index = y * width + x;
- temp[index] = sum1 / sum2;
- }
- }
-
- // Blur each column
- for (int x = 0; x < width; x++) {
- for (int y = 0; y < height; y++) {
- float sum1 = 0;
- float sum2 = 0;
- for (int i = 0; i < size; i++) {
- int y1 = y + i - (size - 1) / 2;
- if (y1 < 0 || y1 >= height) {
- continue;
- }
- int index = y1 * width + x;
- float value = temp[index];
- float weight = kernel[i];
- sum1 += value * weight;
- sum2 += weight;
- }
- int index = y * width + x;
- input[index] = sum1 / sum2;
- }
- }
-
- return input;
- }
-}
\ No newline at end of file
diff --git a/src/main/java/RelevanceMaskStack2D_.java b/src/main/java/RelevanceMaskStack2D_.java
index 6beeabf..2c7be2d 100644
--- a/src/main/java/RelevanceMaskStack2D_.java
+++ b/src/main/java/RelevanceMaskStack2D_.java
@@ -199,28 +199,28 @@ public void run(String s) {
// Write input image to the OpenCL device
clRefPixels = context.createFloatBuffer(wh, READ_ONLY);
- GlobalRedundancy.fillBufferWithFloatArray(clRefPixels, refPixels);
+ CLUtils.fillBufferWithFloatArray(clRefPixels, refPixels);
queue.putWriteBuffer(clRefPixels, true);
// Create OpenCL program
- String programStringGetLocalStatistics = GlobalRedundancy.getResourceAsString(RelevanceMask2D_.class, "kernelGetRelevanceMask2D.cl");
- programStringGetLocalStatistics = GlobalRedundancy.replaceFirst(programStringGetLocalStatistics, "$WIDTH$", "" + w);
- programStringGetLocalStatistics = GlobalRedundancy.replaceFirst(programStringGetLocalStatistics, "$HEIGHT$", "" + h);
- programStringGetLocalStatistics = GlobalRedundancy.replaceFirst(programStringGetLocalStatistics, "$PATCH_SIZE$", "" + patchSize);
- programStringGetLocalStatistics = GlobalRedundancy.replaceFirst(programStringGetLocalStatistics, "$BRW$", "" + bRW);
- programStringGetLocalStatistics = GlobalRedundancy.replaceFirst(programStringGetLocalStatistics, "$BRH$", "" + bRH);
- programStringGetLocalStatistics = GlobalRedundancy.replaceFirst(programStringGetLocalStatistics, "$EPSILON$", "" + EPSILON);
+ String programStringGetLocalStatistics = CLUtils.getResourceAsString(RelevanceMask2D_.class, "kernelGetRelevanceMask2D.cl");
+ programStringGetLocalStatistics = CLUtils.replaceFirst(programStringGetLocalStatistics, "$WIDTH$", "" + w);
+ programStringGetLocalStatistics = CLUtils.replaceFirst(programStringGetLocalStatistics, "$HEIGHT$", "" + h);
+ programStringGetLocalStatistics = CLUtils.replaceFirst(programStringGetLocalStatistics, "$PATCH_SIZE$", "" + patchSize);
+ programStringGetLocalStatistics = CLUtils.replaceFirst(programStringGetLocalStatistics, "$BRW$", "" + bRW);
+ programStringGetLocalStatistics = CLUtils.replaceFirst(programStringGetLocalStatistics, "$BRH$", "" + bRH);
+ programStringGetLocalStatistics = CLUtils.replaceFirst(programStringGetLocalStatistics, "$EPSILON$", "" + EPSILON);
programGetLocalStatistics = context.createProgram(programStringGetLocalStatistics).build();
// Create, fill and write buffers
float[] localMeans = new float[wh];
clLocalMeans = context.createFloatBuffer(wh, READ_WRITE);
- GlobalRedundancy.fillBufferWithFloatArray(clLocalMeans, localMeans);
+ CLUtils.fillBufferWithFloatArray(clLocalMeans, localMeans);
queue.putWriteBuffer(clLocalMeans, true);
float[] localStds = new float[wh];
clLocalStds = context.createFloatBuffer(wh, READ_WRITE);
- GlobalRedundancy.fillBufferWithFloatArray(clLocalStds, localStds);
+ CLUtils.fillBufferWithFloatArray(clLocalStds, localStds);
queue.putWriteBuffer(clLocalStds, true);
// Create kernel and set kernel arguments
diff --git a/src/main/java/Utils.java b/src/main/java/Utils.java
new file mode 100644
index 0000000..764babc
--- /dev/null
+++ b/src/main/java/Utils.java
@@ -0,0 +1,1768 @@
+import ij.IJ;
+import ij.ImagePlus;
+import ij.ImageStack;
+import ij.WindowManager;
+import ij.measure.Calibration;
+import ij.plugin.LutLoader;
+import ij.process.FloatProcessor;
+import ij.process.ImageProcessor;
+import ij.process.LUT;
+import java.awt.image.IndexColorModel;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Arrays;
+import static java.lang.Math.*;
+
+public class Utils {
+
+ public static float EPSILON = 0.0000001f;
+
+ // Private constructor to prevent instantiation
+ private Utils(){
+ throw new UnsupportedOperationException("Utility class");
+ }
+
+
+ // -------------------------- //
+ // ---- UNSORTED OBJECTS ---- //
+ // -------------------------- //
+
+ /**
+ * Custom object to hold a relevance mask.
+ */
+ public static class RelevanceMask {
+ private final float[] relevanceMask;
+ private final float relevanceConstant;
+ private final float threshold;
+ private final float noiseMeanVariance;
+
+
+ /**
+ * Constructs a {@link RelevanceMask} object.
+ *
+ * @param relevanceMask A flattened array of float values representing the binary relevance mask (values are either 0.0 or 1.0)
+ * @param threshold The relevance threshold used to calculate the relevance mask.
+ * @param relevanceConstant The relevance constant used to calculate the relevance mask.
+ * @param noiseMeanVariance The mean variance of the noise in the input image used to calculate the relevance mask.
+ */
+ public RelevanceMask(float[] relevanceMask, float relevanceConstant, float threshold, float noiseMeanVariance) {
+ this.relevanceMask = relevanceMask;
+ this.threshold = threshold;
+ this.relevanceConstant = relevanceConstant;
+ this.noiseMeanVariance = noiseMeanVariance;
+ }
+
+
+ /**
+ * Returns the binary relevance mask as flattened 1D array.
+ *
+ * @return A flattened 1D array of float values containing the relevance mask.
+ */
+ public float[] getRelevanceMask() {
+ return relevanceMask;
+ }
+
+
+ /**
+ * Returns the relevance constant used to calculate the relevance mask.
+ *
+ * @return The relevance constant used to calculate the relevance mask.
+ */
+ public float getRelevanceConstant() {
+ return relevanceConstant;
+ }
+
+
+ /**
+ * Returns the relevance threshold used to calculate the relevance mask (relevance constant * noiseMeanVariance).
+ *
+ * @return The relevance constant used to calculate the relevance mask.
+ */
+ public float getRelevanceThreshold() {
+ return threshold;
+ }
+
+
+ /**
+ * Returns the relevance threshold used to calculate the relevance mask (relevance constant * noiseMeanVariance).
+ *
+ * @return The relevance constant used to calculate the relevance mask.
+ */
+ public float getNoiseMeanVariance() {
+ return noiseMeanVariance;
+ }
+ }
+
+
+ // ----------------------------------------- //
+ // ---- OBJECTS FOR BLOCK REPETITION 2D ---- //
+ // ----------------------------------------- //
+
+ /**
+ * This class represents a 2D reference block after processing an input square or rectangle.
+ * Processing involves discarding pixels outside the inbound ellipse and normalizing the range.
+ * It provides accessor methods to retrieve block dimensions and statistical information.
+ */
+ public static class ReferenceBlock2D {
+ private final float[] pixels;
+ private final int width;
+ private final int height;
+ private final int radiusWidth;
+ private final int radiusHeight;
+ private final int size;
+ private final float mean;
+ private final float std;
+
+ /**
+ * Constructs a ReferenceBlock2D object with the specified parameters.
+ *
+ * @param pixels A 1D array of float values containing the normalized block pixels.
+ * @param width Block width in pixels.
+ * @param height Block height in pixels.
+ * @param radiusWidth Block radius along its width in pixels.
+ * @param radiusHeight Block radius along its height in pixels.
+ * @param size Total number of pixels in the block.
+ * @param mean Mean value of the block pixels.
+ * @param std Standard deviation of the block pixels.
+ */
+ public ReferenceBlock2D(float[] pixels, int width, int height, int radiusWidth,
+ int radiusHeight, int size, float mean, float std){
+
+ this.pixels = pixels;
+ this.width = width;
+ this.height = height;
+ this.radiusWidth = radiusWidth;
+ this.radiusHeight = radiusHeight;
+ this.size = size;
+ this.mean = mean;
+ this.std = std;
+ }
+
+
+ /**
+ * Returns the normalized block pixels.
+ *
+ * @return A 1D array of float values containing the normalized block pixels retained
+ * after processing.
+ */
+ public float[] getPixels() {
+ return pixels;
+ }
+
+ /**
+ * Returns the block width in pixels.
+ *
+ * @return Block width.
+ */
+ public int getWidth() {
+ return width;
+ }
+
+ /**
+ * Returns the block height in pixels.
+ *
+ * @return Block height.
+ */
+ public int getHeight() {
+ return height;
+ }
+
+ /**
+ * Returns the block radius along its width in pixels.
+ *
+ * @return Block width radius.
+ */
+ public int getRadiusWidth() {
+ return radiusWidth;
+ }
+
+ /**
+ * Returns the block radius along its height in pixels.
+ *
+ * @return Block height radius.
+ */
+ public int getRadiusHeight() {
+ return radiusHeight;
+ }
+
+ /**
+ * Returns the total number of pixels in the block.
+ *
+ * @return Size of the block.
+ */
+ public int getSize() {
+ return size;
+ }
+
+ /**
+ * Returns the mean value of the block pixels.
+ *
+ * @return Mean of the block.
+ */
+ public float getMean() {
+ return mean;
+ }
+
+ /**
+ * Returns the standard deviation of the block pixels.
+ *
+ * @return Standard deviation of the block.
+ */
+ public float getStd() {
+ return std;
+ }
+ }
+
+
+ /**
+ * Custom object to hold a 2D input image along with its dimensions and statistics.
+ */
+ public static class InputImage2D
+ {
+ private final float[] imageArray;
+ private final int width;
+ private final int height;
+ private final int size;
+
+ /**
+ * Constructs an InputImage2D object with the specified image data and dimensions.
+ *
+ * @param imageArray A 1D array of float values representing the input image pixels.
+ * @param width The width of the image in pixels.
+ * @param height The height of the image in pixels.
+ * @param size The total number of pixels in the image.
+ */
+ public InputImage2D(float[] imageArray, int width, int height, int size) {
+ this.imageArray = imageArray;
+ this.width = width;
+ this.height = height;
+ this.size = size;
+ }
+
+ /**
+ * Returns the input image pixels as a 1D array.
+ *
+ * @return A 1D array of float values containing the input image pixels.
+ */
+ public float[] getImageArray() {
+ return imageArray;
+ }
+
+ /**
+ * Returns the width of the image in pixels.
+ *
+ * @return The width of the image.
+ */
+ public int getWidth() {
+ return width;
+ }
+
+ /**
+ * Returns the height of the image in pixels.
+ *
+ * @return The height of the image.
+ */
+ public int getHeight() {
+ return height;
+ }
+
+ /**
+ * Returns the total number of pixels in the image.
+ *
+ * @return The size of the image.
+ */
+ public int getSize() {
+ return size;
+ }
+ }
+
+
+ // ----------------------------------------- //
+ // ---- OBJECTS FOR BLOCK REPETITION 3D ---- //
+ // ----------------------------------------- //
+
+ /**
+ * This class represents a 3D reference block after processing an input square or rectangle.
+ * Processing involves discarding pixels outside the inbound spheroid and normalizing the range.
+ * It provides accessor methods to retrieve block dimensions and statistical information.
+ */
+ public static class ReferenceBlock3D
+ {
+ private final float[] pixels;
+ private final int width;
+ private final int height;
+ private final int depth;
+ private final int radiusWidth;
+ private final int radiusHeight;
+ private final int radiusDepth;
+ private final int size;
+ private final float mean;
+ private final float std;
+
+ /**
+ * Constructs a ReferenceBlock3D object with the specified parameters.
+ *
+ * @param pixels A 1D array of float values containing the normalized block pixels.
+ * @param width Block width in pixels.
+ * @param height Block height in pixels.
+ * @param radiusWidth Block radius along its width in pixels.
+ * @param radiusHeight Block radius along its height in pixels.
+ * @param size Total number of pixels in the block.
+ * @param mean Mean value of the block pixels.
+ * @param std Standard deviation of the block pixels.
+ */
+ public ReferenceBlock3D(float[] pixels, int width, int height, int depth, int radiusWidth,
+ int radiusHeight, int radiusDepth, int size, float mean, float std){
+
+ this.pixels = pixels;
+ this.width = width;
+ this.height = height;
+ this.depth = depth;
+ this.radiusWidth = radiusWidth;
+ this.radiusHeight = radiusHeight;
+ this.radiusDepth = radiusDepth;
+ this.size = size;
+ this.mean = mean;
+ this.std = std;
+ }
+
+
+ /**
+ * Returns the normalized block pixels.
+ *
+ * @return A 1D array of float values containing the normalized block pixels retained
+ * after processing.
+ */
+ public float[] getPixels() {
+ return pixels;
+ }
+
+ /**
+ * Returns the block width in pixels.
+ *
+ * @return Block width.
+ */
+ public int getWidth() {
+ return width;
+ }
+
+ /**
+ * Returns the block height in pixels.
+ *
+ * @return Block height.
+ */
+ public int getHeight() {
+ return height;
+ }
+
+ /**
+ * Returns the block depth in pixels.
+ *
+ * @return Block height.
+ */
+ public int getDepth() {
+ return depth;
+ }
+
+ /**
+ * Returns the block radius along its width in pixels.
+ *
+ * @return Block width radius.
+ */
+ public int getRadiusWidth() {
+ return radiusWidth;
+ }
+
+ /**
+ * Returns the block radius along its height in pixels.
+ *
+ * @return Block height radius.
+ */
+ public int getRadiusHeight() {
+ return radiusHeight;
+ }
+
+ /**
+ * Returns the block radius along its height in pixels.
+ *
+ * @return Block height radius.
+ */
+ public int getRadiusDepth() {
+ return radiusDepth;
+ }
+
+ /**
+ * Returns the total number of pixels in the block.
+ *
+ * @return Size of the block.
+ */
+ public int getSize() {
+ return size;
+ }
+
+ /**
+ * Returns the mean value of the block pixels.
+ *
+ * @return Mean of the block.
+ */
+ public float getMean() {
+ return mean;
+ }
+
+ /**
+ * Returns the standard deviation of the block pixels.
+ *
+ * @return Standard deviation of the block.
+ */
+ public float getStd() {
+ return std;
+ }
+ }
+
+ /**
+ * Custom object to hold a 3D input image along with its dimensions and statistics.
+ */
+ public static class InputImage3D
+ {
+ private final float[] imageArray;
+ private final int width;
+ private final int height;
+ private final int depth;
+ private final int size;
+ private final Calibration calibration;
+ private final float gain;
+ private final float sigma;
+ private final float offset;
+
+ /**
+ * Constructs an InputImage2D object with the specified image data and dimensions.
+ *
+ * @param imageArray A 1D array of float values representing the input image pixels.
+ * @param width The width of the image in pixels.
+ * @param height The height of the image in pixels.
+ * @param size The total number of pixels in the image.
+ */
+ public InputImage3D(float[] imageArray, int width, int height, int depth, int size, Calibration calibration,
+ float gain, float sigma, float offset)
+ {
+ this.imageArray = imageArray;
+ this.width = width;
+ this.height = height;
+ this.depth = depth;
+ this.size = size;
+ this.calibration = calibration;
+ this.gain = gain;
+ this.sigma = sigma;
+ this.offset = offset;
+ }
+
+ /**
+ * Returns the input image pixels as a 1D array.
+ *
+ * @return A 1D array of float values containing the input image pixels.
+ */
+ public float[] getImageArray() {
+ return imageArray;
+ }
+
+ /**
+ * Returns the width of the image in pixels.
+ *
+ * @return The width of the image.
+ */
+ public int getWidth() {
+ return width;
+ }
+
+ /**
+ * Returns the height of the image in pixels.
+ *
+ * @return The height of the image.
+ */
+ public int getHeight() {
+ return height;
+ }
+
+ /**
+ * Returns the depth of the image in pixels.
+ *
+ * @return The height of the image.
+ */
+ public int getDepth() {
+ return depth;
+ }
+
+ /**
+ * Returns the total number of pixels in the image.
+ *
+ * @return The size of the image.
+ */
+ public int getSize() {
+ return size;
+ }
+
+ /**
+ * Returns the ImageJ calibration object of the image.
+ *
+ * @return The calibration object of the image.
+ */
+ public Calibration getCalibration(){
+ return calibration;
+ }
+
+ /**
+ * Returns the optimised "gain" parameter used in the VST.
+ *
+ * @return The calibration object of the image.
+ */
+ public float getGain(){
+ return gain;
+ }
+
+ /**
+ * Returns the optimised "sigma" parameter used in the VST.
+ *
+ * @return The calibration object of the image.
+ */
+ public float getSigma(){
+ return sigma;
+ }
+
+ /**
+ * Returns the optimised "offset" parameter used in the VST.
+ *
+ * @return The calibration object of the image.
+ */
+ public float getOffset(){
+ return offset;
+ }
+ }
+
+
+ // ----------------------------------------- //
+ // ---- METHODS FOR BLOCK REPETITION 2D ---- //
+ // ----------------------------------------- //
+
+ /**
+ * Retrieves a ReferenceBlock2D object from a 2D image containing a reference block.
+ *
+ * If the blockID or the block's dimensions are invalid (e.g., not odd), the function will return {@code null}.
+ * The block image must be 8-bit, 16-bit, or 32-bit; RGB is not supported.
+ *
+ * @param blockID The ImageJ/Fiji window ID of the image containing the reference block.
+ * @return A ReferenceBlock2D object, or {@code null} if the input image is invalid (i.e., window not found or even block dimensions).
+ */
+ public static ReferenceBlock2D getReferenceBlock2D(int blockID)
+ {
+ ImagePlus imp = WindowManager.getImage(blockID);
+
+ if (imp == null) {
+ IJ.error("Block image not found. Try again.");
+ return null;
+ }
+
+ ImageProcessor ip = imp.getProcessor();
+ FloatProcessor fp = ip.convertToFloatProcessor();
+ float[] blockProcessor = (float[]) fp.getPixels();
+ int bW = fp.getWidth(); // Block width
+ int bH = fp.getHeight(); // Block height
+ IJ.log("Block dimensions: " + bW + "x" + bH);
+
+ // Check if block dimensions are odd; return null if not
+ if (bW % 2 == 0 || bH % 2 == 0) {
+ IJ.error("Block dimensions must be odd (e.g., 3x3 or 5x5). Please try again.");
+ return null;
+ }
+
+ // Calculate block radius
+ int bRW = bW / 2; // Patch radius (x-axis)
+ int bRH = bH / 2; // Patch radius (y-axis)
+
+ // Get final block size (after removing pixels outside inbound circle/ellipse)
+ int blockSize = 0;
+ for (int j = 0; j < bH; j++) {
+ for (int i = 0; i < bW; i++) {
+ float dx = (float) (i - bRW);
+ float dy = (float) (j - bRH);
+ if (((dx * dx) / (float) (bRW * bRW)) + ((dy * dy) / (float) (bRH * bRH)) <= 1.0f) {
+ blockSize++;
+ }
+ }
+ }
+
+ // Get flattened block array (keeping only the pixels within the inbound circle/ellipse)
+ float[] blockArray = new float[blockSize];
+ int index = 0;
+ for (int j = 0; j < bH; j++) {
+ for (int i = 0; i < bW; i++) {
+ float dx = (float) (i - bRW);
+ float dy = (float) (j - bRH);
+ if (((dx * dx) / (float) (bRW * bRW)) + ((dy * dy) / (float) (bRH * bRH)) <= 1.0f) {
+ blockArray[index] = blockProcessor[j * bW + i];
+ index++;
+ }
+ }
+ }
+
+ // Calculate block min and max
+ float blockMin = Float.MAX_VALUE; // Initialize as a very large number
+ float blockMax = -Float.MAX_VALUE; // Initialize as a very small number
+
+ for (int i = 0; i < blockSize; i++) {
+ blockMin = Math.min(blockMin, blockArray[i]);
+ blockMax = Math.max(blockMax, blockArray[i]);
+ }
+
+ // Normalize and calculate mean
+ float blockMean = 0.0f;
+ for (int i = 0; i < blockSize; i++) {
+ blockArray[i] = (blockArray[i] - blockMin) / (blockMax - blockMin + EPSILON);
+ blockMean += blockArray[i];
+ }
+ blockMean /= (float) blockSize;
+
+ // Subtract mean
+ for (int i = 0; i < blockSize; i++) {
+ blockArray[i] = blockArray[i] - blockMean;
+ }
+
+ // Calculate block standard deviation
+ float blockStd = 0.0f;
+ for (int i = 0; i < blockSize; i++) {
+ blockStd += (blockArray[i] - blockMean) * (blockArray[i] - blockMean);
+ }
+ blockStd = (float) Math.sqrt(blockStd / ((float) (blockSize - 1)));
+
+ return new ReferenceBlock2D(blockArray, bW, bH, bRW, bRH, blockSize, blockMean, blockStd);
+ }
+
+
+ /**
+ * Retrieves an InputImage2D object from a 2D image, optionally stabilizing noise variance
+ * and normalizing the output.
+ *
+ * If the imageID is invalid, the function will return {@code null}.
+ *
+ * @param imageID The ImageJ/Fiji window ID of the image to be retrieved.
+ * @param stabiliseNoiseVariance If {@code true}, applies variance stabilization to the image.
+ * @param normalizeOutput If {@code true}, normalizes the pixel values of the image.
+ * @return An InputImage2D object, or {@code null} if the input image is invalid (i.e., window not found).
+ */
+ public static InputImage2D getInputImage2D(int imageID, boolean stabiliseNoiseVariance, boolean normalizeOutput)
+ {
+ // Get ImagePlus object
+ ImagePlus imp = WindowManager.getImage(imageID);
+
+ // Check if image dimensions are odd; return null if not found
+ if (imp == null) {
+ IJ.error("Image not found. Try again.");
+ return null;
+ }
+
+ // Get processors and dimensions
+ ImageProcessor ip = imp.getProcessor();
+ FloatProcessor fp = ip.convertToFloatProcessor();
+ float[] imageArray = (float[]) fp.getPixels();
+ int width = fp.getWidth();
+ int height = fp.getHeight();
+ int size = width * height;
+ IJ.log("Image dimensions: "+width+"x"+height);
+
+ float gain = 0.0f; // Placeholder
+ float sigma = 0.0f; // Placeholder
+ float offset = 0.0f; // Placeholder
+
+ if (stabiliseNoiseVariance) {
+ IJ.log("Stabilizing noise variance of the image...");
+ GATMinimizer2D minimizer = new GATMinimizer2D(imageArray, width, height, 1, 10, 100);
+ minimizer.run();
+ gain = (float) minimizer.gain;
+ sigma = (float) minimizer.sigma;
+ offset = (float) minimizer.offset;
+
+ imageArray = VarianceStabilisingTransform2D_.getGAT(imageArray, gain, sigma, offset);
+ }
+
+ if (normalizeOutput) {
+ // Get min and max
+ float imageMin = Float.MAX_VALUE;
+ float imageMax = -Float.MAX_VALUE;
+ for (int i = 0; i < size; i++) {
+ float pixelValue = imageArray[i];
+ imageMin = Math.min(imageMin, pixelValue);
+ imageMax = Math.max(imageMax, pixelValue);
+ }
+
+ // Normalize
+ for (int i = 0; i < size; i++) {
+ imageArray[i] = (imageArray[i] - imageMin) / (imageMax - imageMin + EPSILON);
+ }
+ }
+
+ return new InputImage2D(imageArray, width, height, size);
+ }
+
+ /**
+ * Retrieves an InputImage2D object from a 2D image, optionally stabilizing noise variance
+ * and normalizing the output.
+ *
+ * NOTE: This is an OVERLOAD method that takes an image array instead of an ImageJ image ID uses specific GAT parameter values to calculate the VST.
+ *
+ * If the imageID is invalid, the function will return {@code null}.
+ *
+ * @param imageArray An image array (float).
+ * @param imageWidth The width of the image.
+ * @param imageHeight The height of the image.
+ * @param stabiliseNoiseVariance If {@code true}, applies variance stabilization to the image.
+ * @param gain The gain to be used in the VST
+ * @param sigma The sigma to be used in the VST
+ * @param offset The offset to be used in the VST
+ * @param normalizeOutput If {@code true}, normalizes the pixel values of the image.
+ * @return An InputImage2D object, or {@code null} if the input image is invalid (i.e., window not found).
+ */
+ public static InputImage2D getInputImage2D(float[] imageArray, int imageWidth, int imageHeight,
+ boolean stabiliseNoiseVariance, float gain, float sigma, float offset,
+ boolean normalizeOutput)
+ {
+
+ int imageSize = imageWidth * imageHeight;
+
+ if (stabiliseNoiseVariance) {
+ IJ.log("Stabilizing noise variance of the image...");
+ imageArray = VarianceStabilisingTransform2D_.getGAT(imageArray, gain, sigma, offset);
+ }
+
+ if (normalizeOutput) {
+ // Get min and max
+ float imageMin = Float.MAX_VALUE;
+ float imageMax = -Float.MAX_VALUE;
+ for (int i = 0; i < imageSize; i++) {
+ float pixelValue = imageArray[i];
+ imageMin = Math.min(imageMin, pixelValue);
+ imageMax = Math.max(imageMax, pixelValue);
+ }
+
+ // Normalize
+ for (int i = 0; i < imageSize; i++) {
+ imageArray[i] = (imageArray[i] - imageMin) / (imageMax - imageMin + EPSILON);
+ }
+ }
+
+ return new InputImage2D(imageArray, imageWidth, imageHeight, imageSize);
+ }
+
+
+ /**
+ * Calculates the mean noise variance for a 2D reference pixel array.
+ *
+ * This method divides the reference pixel array into blocks and computes local variances.
+ * It then returns the average of the lowest 3% of these variances, scaled based on a specified formula.
+ *
+ * @param refPixels A 1D array (float32) containing the reference pixel values.
+ * @param w The width of the image (in pixels).
+ * @param h The height of the image (in pixels).
+ * @param wh The total number of pixels in the image (width * height).
+ * @return The calculated mean noise variance.
+ */
+ public static float getMeanNoiseVar2D(float[] refPixels, int w, int h, int wh)
+ {
+ int blockWidth, blockHeight;
+ int CIF = 352 * 288; // Resolution of a CIF file
+
+ // Determine block dimensions based on image size
+ if (wh <= CIF) {
+ blockWidth = 8;
+ blockHeight = 8;
+ } else {
+ blockWidth = 16;
+ blockHeight = 16;
+ }
+
+ // Calculate the number of blocks in each dimension
+ int nBlocksX = w / blockWidth; // number of blocks in each row
+ int nBlocksY = h / blockHeight; // number of blocks in each column
+ int nBlocks = nBlocksX * nBlocksY; // total number of blocks
+ float[] localVars = new float[nBlocks];
+ Arrays.fill(localVars, 0.0f);
+
+ // Calculate local variances
+ int index = 0;
+ for (int y = 0; y < nBlocksY; y++) {
+ for (int x = 0; x < nBlocksX; x++) {
+ float[] meanVar = getMeanAndVarBlock2D(refPixels, w, x*blockWidth, y*blockHeight, (x+1)*blockWidth, (y+1)*blockHeight);
+ localVars[index] = meanVar[1]; // Store variance
+ index++;
+ }
+ }
+
+ // Sort the local variances
+ Arrays.sort(localVars);
+
+ // Get the 3% lowest variances and calculate their average
+ int nVars = (int) (0.03f * (float) nBlocks + 1.0f); // Number of blocks corresponding to 3% of the total
+ float noiseVar = 0.0f;
+
+ for (int i = 0; i < nVars; i++) {
+ noiseVar += localVars[i];
+ }
+
+ // Calculate average of the lowest variances
+ noiseVar = Math.abs(noiseVar / (float) nVars);
+ noiseVar = (1.0f + 0.001f * (noiseVar - 40.0f)) * noiseVar;
+
+ return noiseVar;
+ }
+
+
+ /**
+ * Calculates the mean and variance of a block of pixels from a 1D pixel array in a single pass.
+ *
+ * This method computes the mean and variance for a specified rectangular block of pixels
+ * defined by its start and end coordinates.
+ *
+ * @param imageArray A 1D array (float32) containing the pixel values.
+ * @param imageWidth The width of the image (in pixels), used for indexing into the pixel array.
+ * @param xStart The starting x-coordinate (inclusive) of the block.
+ * @param yStart The starting y-coordinate (inclusive) of the block.
+ * @param xEnd The ending x-coordinate (exclusive) of the block.
+ * @param yEnd The ending y-coordinate (exclusive) of the block.
+ * @return A float array where the first element is the mean and the second element is the variance of the block.
+ */
+ public static float[] getMeanAndVarBlock2D(float[] imageArray, int imageWidth, int xStart, int yStart, int xEnd,
+ int yEnd)
+ {
+ float mean = 0.0f;
+ float var;
+ float sq_sum = 0.0f;
+
+ int blockWidth = xEnd - xStart; // Block width
+ int blockHeight = yEnd - yStart; // Block height
+ int blockSize = blockWidth * blockHeight; // Total number of pixels in the block
+
+ // Calculate the sum of pixel values and the sum of squared pixel values
+ for (int y=yStart; yThis method modifies the input image array by applying a provided mask,
+ * which scales each pixel in the image according to the corresponding value in the mask.
+ *
+ * @param imageArray An input image array (float32).
+ * @param imageWidth The width of the input image array.
+ * @param imageHeight The height of the input image array.
+ * @param mask A user-provided mask (float32).
+ * @return A masked image array (the modified input image array).
+ */
+ public static float[] applyMask2D(float[] imageArray, int imageWidth, int imageHeight, float[] mask)
+ {
+ // Apply mask
+ for (int y = 0; y < imageHeight; y++) {
+ for (int x = 0; x < imageWidth; x++) {
+ imageArray[y * imageWidth + x] *= mask[y * imageWidth + x];
+ }
+ }
+
+ return imageArray;
+ }
+
+
+ /**
+ * Calculate relevance mask based on structural relevance.
+ *
+ * This method generates a binary mask where the center pixels of each local neighborhood are assigned a value of zero
+ * if the local variance is below or equal to the relevance threshold, which is determined by the mean noise variance
+ * multiplied by a user-defined relevance constant.
+ *
+ * @param imageArray An input image array (float) used to calculate the mean noise variance and the relevance mask.
+ * @param imageWidth The width of the input image array.
+ * @param imageHeight The height of the input image array.
+ * @param blockRadiusWidth The width radius of the block used in the repetition analysis.
+ * @param blockRadiusHeight The height radius of the block used in the repetition analysis.
+ * @param localStds The local standard deviations array calculated with {@link CLUtils.getLocalStatistics2D()}.
+ * @param relevanceConstant A user-provided relevance constant to control the strength of the relevance filter.
+ * @return A {@link RelevanceMask} object.
+ */
+ public static RelevanceMask getRelevanceMask(float[] imageArray, int imageWidth, int imageHeight,
+ int blockRadiusWidth, int blockRadiusHeight, float[] localStds,
+ float relevanceConstant)
+ {
+ // Calculate noise mean variance
+ int imageSize = imageWidth * imageHeight;
+
+ float noiseMeanVariance = getMeanNoiseVar2D(imageArray, imageWidth, imageHeight, imageSize);
+ float relevanceThreshold = noiseMeanVariance*relevanceConstant;
+
+ // Calculate relevance mask
+ float[] relevanceMask = new float[imageSize];
+ Arrays.fill(relevanceMask, 0.0f); // Fill with zeros just to be sure
+ for (int y = blockRadiusHeight; y < imageHeight - blockRadiusHeight; y++) {
+ for (int x = blockRadiusWidth; x < imageWidth - blockRadiusWidth; x++) {
+ if ((localStds[y * imageWidth + x] * localStds[y * imageWidth + x]) <= relevanceThreshold) {
+ relevanceMask[y * imageWidth + x] = 0.0f; // Assign zero if local variance is below threshold
+ } else {
+ relevanceMask[y * imageWidth + x] = 1.0f; // Assign one if local variance is above threshold
+ }
+ }
+ }
+
+ return new RelevanceMask(relevanceMask, relevanceConstant, relevanceThreshold, noiseMeanVariance); // Return the relevance mask
+ }
+
+
+ /**
+ * Normalize an image (in-place) to a specified range, with the option to avoid masked pixels.
+ *
+ * This method calculates the minimum and maximum pixel values of the input image while excluding
+ * any masked pixels, and then normalizes the image based on these values.
+ *
+ * @param imageArray The input image array (float) to normalize.
+ * @param imageWidth The width of the input image array.
+ * @param imageHeight The height of the input image array.
+ * @param borderWidth The width of the border to exclude from the normalization calculation.
+ * @param borderHeight The height of the border to exclude from the normalization calculation.
+ * @param mask A binary mask (float) where masked pixels will be excluded from the calculation.
+ * @return A normalized image array (float) where pixel values are remapped to the range [0, 1].
+ */
+ public static float[] normalizeImage2D(float[] imageArray, int imageWidth, int imageHeight, int borderWidth,
+ int borderHeight, float[] mask)
+ {
+ // Find min and max
+ float imageMin = Float.MAX_VALUE;
+ float imageMax = -Float.MAX_VALUE;
+
+ if(mask==null) {
+ for (int y=borderHeight; y 0.0f) {
+ imageMin = min(imageArray[y*imageWidth+x], imageMin);
+ imageMax = max(imageArray[y*imageWidth+x], imageMax);
+ }
+ }
+ }
+ }
+
+ // Remap pixels
+ float[] normalizedArray = imageArray.clone();
+ if(mask==null) {
+ for (int y=borderHeight; y0.0f) {
+ normalizedArray[y*imageWidth+x] = (normalizedArray[y*imageWidth+x]-imageMin)/(imageMax-imageMin+EPSILON);
+ }
+ }
+ }
+ }
+ return normalizedArray;
+ }
+
+
+ // ----------------------------------------- //
+ // ---- METHODS FOR BLOCK REPETITION 3D ---- //
+ // ----------------------------------------- //
+
+ /**
+ * Retrieves a ReferenceBlock3D object from a 3D image containing a reference block.
+ *
+ * If the blockID or the block's dimensions are invalid (e.g., not odd), the function will return {@code null}.
+ * The block image must be 8-bit, 16-bit, or 32-bit; RGB is not supported.
+ *
+ * @param blockID The ImageJ/Fiji window ID of the image containing the reference block.
+ * @return A ReferenceBlock2D object, or {@code null} if the input image is invalid (i.e., window not found or even block dimensions).
+ */
+ public static ReferenceBlock3D getReferenceBlock3D(int blockID)
+ {
+ ImagePlus imp = WindowManager.getImage(blockID);
+
+ // Check if image is found
+ if (imp == null) {
+ IJ.error("Block image not found. Try again.");
+ throw new IllegalArgumentException("Block image not found.");
+ }
+
+ // Get block dimensions
+ ImageStack ims = imp.getStack();
+ int bW = ims.getWidth(); // Block width
+ int bH = ims.getHeight(); // Block height
+ int bZ = ims.getSize(); // block depth
+ int bRW = bW / 2; // Patch radius (x-axis)
+ int bRH = bH / 2; // Patch radius (y-axis)
+ int bRZ = bZ / 2; // Patch radius (z-axis)
+ IJ.log("Block dimensions: " + bW + "x" + bH + "x" + bZ);
+
+ // Check if block dimensions are odd
+ if (bW % 2 == 0 || bH % 2 == 0 || bZ % 2 == 0) {
+ IJ.error("Block dimensions must be odd (e.g., 3x3x3 or 5x5x5). Please try again.");
+ throw new IllegalArgumentException("Block dimensions must be odd.");
+ }
+
+ // Check if patch has at least 3 slices
+ if (bZ < 3) {
+ IJ.error("Block must have at least 3 slices. Please try again.");
+ throw new IllegalArgumentException("Reference block must have at least 3 slices.");
+ }
+
+ // Get final block size (after removing pixels outside the sphere/ellipsoid)
+ int blockSize = 0;
+ for (int z = 0; z < bZ; z++) {
+ for (int y = 0; y < bH; y++) {
+ for (int x = 0; x < bW; x++) {
+ float dx = (float) (x - bRW);
+ float dy = (float) (y - bRH);
+ float dz = (float) (z - bRZ);
+ if (((dx * dx) / (float) (bRW * bRW)) + ((dy * dy) / (float) (bRH * bRH)) + ((dz * dz) / (float) (bRZ * bRZ)) <= 1.0f) {
+ blockSize++;
+ }
+ }
+ }
+ }
+
+ // Get block array
+ float[][] blockArray = new float[bZ][bW * bH];
+ for (int z = 0; z < bZ; z++) {
+ for (int y = 0; y < bH; y++) {
+ for (int x = 0; x < bW; x++) {
+ blockArray[z][y*bW+x] = ims.getProcessor(z+1).convertToFloatProcessor().getf(x,y);
+ }
+ }
+ }
+
+ // Get flattened block array (keeping only the pixels within the inbound spheroid)
+ float[] blockArray1D = new float[blockSize];
+ int index = 0;
+ for (int z=0; zIf the imageID is invalid, the function will return {@code null}.
+ *
+ * @param imageID The ImageJ/Fiji window ID of the image to be retrieved.
+ * @param stabiliseNoiseVariance If {@code true}, applies variance stabilization to the image.
+ * @param normalizeOutput If {@code true}, normalizes the pixel values of the image.
+ * @return An InputImage3D object, or {@code null} if the input image is invalid (i.e., window not found).
+ */
+ public static InputImage3D getInputImage3D(int imageID, boolean stabiliseNoiseVariance, boolean normalizeOutput)
+ {
+ // Get ImagePlus object
+ ImagePlus imp = WindowManager.getImage(imageID);
+
+ // Check if image dimensions are odd; return null if not found
+ if (imp == null) {
+ IJ.error("Image not found. Try again.");
+ return null;
+ }
+
+ // Get calibration parameters
+ Calibration calibration = imp.getCalibration();
+
+ // Get ImageStack object
+ ImageStack ims = imp.getStack();
+
+ // Get processors and dimensions
+ //float[] imageArray = (float[]) fp.getPixels();
+ int width = ims.getWidth();
+ int height = ims.getHeight();
+ int depth = ims.getSize();
+ int size = width * height * depth;
+ IJ.log("Image dimensions: "+width+"x"+height+"x"+depth);
+
+ // Check if image has at least 3 slices, otherwise kill program
+ if (depth < 3) {
+ IJ.error("Image must have at least 3 slices. Please try again.");
+ return null;
+ }
+
+ // Get image stack array (float[z][wh])
+ float[][] stackArray = new float[depth][width*height];
+ for(int z=0; zIf the imageID is invalid, the function will return {@code null}.
+ *
+ * @param imageArray An image array of the image (float).
+ * @param stabiliseNoiseVariance If {@code true}, applies variance stabilization to the image.
+ * @param normalizeOutput If {@code true}, normalizes the pixel values of the image.
+ * @return An InputImage3D object, or {@code null} if the input image is invalid (i.e., window not found).
+ */
+ public static InputImage3D getInputImage3D(float[] imageArray, int width, int height, int depth, boolean stabiliseNoiseVariance, boolean normalizeOutput)
+ {
+
+ int size = width*height*depth;
+
+ // Stabilise noise variance
+ float gain = 0.0f; // Placeholder
+ float sigma = 0.0f; // Placeholder
+ float offset = 0.0f; // Placeholder
+
+ if (stabiliseNoiseVariance) {
+ IJ.log("Stabilizing noise variance of the image...");
+
+ // Get image stack array (float[z][wh])
+ float[][] stackArray = new float[depth][width*height];
+ for(int z=0; zThis method computes the mean and variance for a specified rectangular block of pixels
+ * defined by its start and end coordinates.
+ *
+ * @param pixels A 1D array (float32) containing the pixel values.
+ * @param imageWidth The width of the image (in pixels), used for indexing into the pixel array.
+ * @param imageHeight The height of the image (in pixels), used for indexing into the pixel array.
+ * @param xStart The starting x-coordinate (inclusive) of the block.
+ * @param yStart The starting y-coordinate (inclusive) of the block.
+ * @param zStart The starting z-coordinate (incusive) of the block.
+ * @param xEnd The ending x-coordinate (exclusive) of the block.
+ * @param yEnd The ending y-coordinate (exclusive) of the block.
+ * @param zEnd The ending z-coordinate (exclusive) of the block.
+ * @return A float array where the first element is the mean and the second element is the variance of the block.
+ */
+ public static float[] getMeanAndVarBlock3D(float[] pixels, int imageWidth, int imageHeight, int xStart, int yStart,
+ int zStart, int xEnd, int yEnd, int zEnd)
+ {
+ float mean = 0.0f;
+ float var;
+ float sq_sum = 0.0f;
+
+ int blockWidth = xEnd - xStart; // Block width
+ int blockHeight = yEnd - yStart; // Block height
+ int blockDepth = zEnd - zStart; // Block depth
+ int blockSize = blockWidth * blockHeight * blockDepth; // Total number of pixels in the block
+
+ // Calculate the sum of pixel values and the sum of squared pixel values
+ for(int z=zStart; zThis method divides the reference pixel array into blocks and computes local variances.
+ * It then returns the average of the lowest 3% of these variances, scaled based on a specified formula.
+ *
+ * @param refPixels A flattened 1-D array (float32) containing the reference pixel values.
+ * @param imageWidth The width of the image (in pixels).
+ * @param imageHeight The height of the image (in pixels).
+ * @param imageDepth the depth of the image (in pixels).
+ * @param imageSize The total number of pixels in the image (width * height * depth).
+ * @return The calculated mean noise variance.
+ *
+ public static float getMeanNoiseVar3D(float[] imageArray, int imageWidth, int imageHeight, int imageDepth, int imageSize) {
+ int blockWidth, blockHeight, blockDepth;
+ int CIF = 352 * 288; // Resolution of a CIF file
+
+ // Determine block dimensions based on image size
+ if (imageWidth*imageHeight <= CIF) {
+ blockWidth = 8;
+ blockHeight = 8;
+ blockDepth = 5; // TODO: this was arbitrary, didn't want to use more slices
+ } else {
+ blockWidth = 16;
+ blockHeight = 16;
+ blockDepth = 5; // TODO: this was arbitrary, didn't want to use more slices
+ }
+
+ // Calculate the number of blocks in each dimension
+ int nBlocksX = imageWidth / blockWidth; // number of blocks in each row
+ int nBlocksY = imageHeight / blockHeight; // number of blocks in each column
+ int nBlocksZ = imageDepth / blockDepth; // number of bocks in depth
+ int nBlocks = nBlocksX * nBlocksY * nBlocksZ; // total number of blocks
+ float[] localVars = new float[nBlocks];
+ Arrays.fill(localVars, 0.0f);
+
+ // Calculate local variances
+ int index = 0;
+ for(int z=0; zThis method generates a 3D binary mask where the center pixels of each local neighborhood are assigned a value of zero
+ * if the local variance is below or equal to the relevance threshold, which is determined by the mean noise variance
+ * multiplied by a user-defined relevance constant.
+ *
+ * @param imageWidth The width of the input image.
+ * @param imageHeight The height of the input image.
+ * @param imageDepth The depth of the input image.
+ * @param blockRadiusWidth The width radius of the block used in the repetition analysis.
+ * @param blockRadiusHeight The height radius of the block used in the repetition analysis.
+ * @param blockRadiusDepth The depth radius of the block used in the repetition analysis.
+ * @param localStds The local standard deviations array calculated with {@link CLUtils.getLocalStatistics3D()}.
+ * @param relevanceConstant A user-provided relevance constant to control the strength of the relevance filter.
+ * @return A binary mask (float) where center pixels of each local neighbourhood are assigned a value of zero if the local variance is below or equal
+ * to the relevance threshold; otherwise, they are assigned a value of one.
+ */
+ public static float[] getRelevanceMask3D(int imageWidth, int imageHeight, int imageDepth, int blockRadiusWidth,
+ int blockRadiusHeight, int blockRadiusDepth, float[] localStds,
+ float relevanceConstant)
+ {
+ // Calculate noise mean variance
+ float noiseMeanVariance = getMeanNoiseVar3D(imageWidth, imageHeight, imageDepth, blockRadiusWidth,
+ blockRadiusHeight, blockRadiusDepth, localStds);
+
+ // Calculate relevance mask
+ float[] relevanceMask = new float[imageWidth*imageHeight*imageDepth];
+ Arrays.fill(relevanceMask, 0.0f); // Fill with zeros just to be sure
+
+ for(int z=blockRadiusDepth; zThis method modifies the input image array by applying a provided mask,
+ * which scales each pixel in the image according to the corresponding value in the mask.
+ *
+ * @param imageArray An input image array (float32).
+ * @param imageWidth The width of the input image.
+ * @param imageHeight The height of the input image.
+ * @param imageDepth The depth of the input image.
+ * @param mask A user-provided binary mask (float32).
+ * @return A masked image array (the modified input image array).
+ */
+ public static float[] applyMask3D(float[] imageArray, int imageWidth, int imageHeight, int imageDepth, float[] mask)
+ {
+ // Apply mask
+ for(int z=0; zThis method calculates the minimum and maximum pixel values of the input image while excluding
+ * any masked pixels, and then normalizes the image based on these values.
+ *
+ * @param imageArray The input image array (float) to normalize.
+ * @param imageWidth The width of the input image array.
+ * @param imageHeight The height of the input image array.
+ * @param borderWidth The width of the border to exclude from the normalization calculation.
+ * @param borderHeight The height of the border to exclude from the normalization calculation.
+ * @param mask A binary mask (float) where masked pixels will be excluded from the calculation.
+ * @return A normalized image array (float) where pixel values are remapped to the range [0, 1].
+ */
+ public static float[] normalizeImage3D(float[] imageArray, int imageWidth, int imageHeight, int imageDepth, int borderWidth,
+ int borderHeight, int borderDepth, float[] mask)
+ {
+
+ // Find min and max
+ float imageMin = Float.MAX_VALUE;
+ float imageMax = -Float.MAX_VALUE;
+
+ if(mask==null) {
+ for (int z = borderDepth; z < imageDepth - borderDepth; z++) {
+ for (int y = borderHeight; y < imageHeight - borderHeight; y++) {
+ for (int x = borderWidth; x < imageWidth - borderWidth; x++) {
+ int index = imageWidth * imageHeight * z + y * imageWidth + x;
+ imageMin = min(imageArray[index], imageMin);
+ imageMax = max(imageArray[index], imageMax);
+ }
+ }
+ }
+ }else{
+ for (int z = borderDepth; z < imageDepth - borderDepth; z++) {
+ for (int y = borderHeight; y < imageHeight - borderHeight; y++) {
+ for (int x = borderWidth; x < imageWidth - borderWidth; x++) {
+ int index = imageWidth * imageHeight * z + y * imageWidth + x;
+ if (mask[index] > 0.0f) {
+ imageMin = min(imageArray[index], imageMin);
+ imageMax = max(imageArray[index], imageMax);
+ }
+ }
+ }
+ }
+ }
+
+ // Remap pixels
+ float[] normalizedImage = imageArray.clone();
+ if(mask==null) {
+ for (int z = borderDepth; z < imageDepth - borderDepth; z++) {
+ for (int y = borderHeight; y < imageHeight - borderHeight; y++) {
+ for (int x = borderWidth; x < imageWidth - borderWidth; x++) {
+ int index = imageWidth * imageHeight * z + y * imageWidth + x;
+ normalizedImage[index] = (imageArray[index] - imageMin) / (imageMax - imageMin + EPSILON);
+ }
+ }
+ }
+ }else{
+ for (int z = borderDepth; z < imageDepth - borderDepth; z++) {
+ for (int y = borderHeight; y < imageHeight - borderHeight; y++) {
+ for (int x = borderWidth; x < imageWidth - borderWidth; x++) {
+ int index = imageWidth * imageHeight * z + y * imageWidth + x;
+ if (mask[index] > 0.0f) {
+ normalizedImage[index] = (imageArray[index] - imageMin) / (imageMax - imageMin + EPSILON);
+ }
+ }
+ }
+ }
+ }
+
+ return normalizedImage;
+ }
+
+
+ // -------------------------- //
+ // ---- UNSORTED METHODS ---- //
+ // -------------------------- //
+
+ /**
+ * Normalizes an array to its range.
+ *
+ * @param array
+ * @return A copy of the original array, normalized to the range
+ */
+ public static float[] normalizeArray(float[] array)
+ {
+
+ // Cache variables
+ int arrayLength = array.length;
+
+ // Get min max
+ float arrayMin = Float.MAX_VALUE;
+ float arrayMax = -Float.MAX_VALUE;
+ for(int i=0; iThis method computes the mean, variance, and standard deviation of the input array.
+ *
+ * @param a The input array of float values.
+ * @return An array of float containing the mean, variance, and standard deviation in that order.
+ * Returns an array of zeros if the input array is empty.
+ */
+ private float[] meanVarStd(float[] a) {
+ int n = a.length;
+ if (n == 0) return new float[]{0, 0, 0}; // Return zeros for empty array
+
+ double sum = 0.0;
+ double sq_sum = 0.0;
+
+ // Calculate sum and squared sum
+ for (int i = 0; i < n; i++) {
+ sum += a[i];
+ sq_sum += a[i] * a[i];
+ }
+
+ double mean = sum / n; // Calculate mean
+ double variance = Math.abs(sq_sum / n - mean * mean); // Calculate variance
+ // abs() solves a bug where negative zeros appeared
+
+ return new float[]{(float) mean, (float) variance, (float) Math.sqrt(variance)}; // Return mean, variance, and std
+ }
+
+
+ /**
+ * Retrieve the image ID corresponding to a given title.
+ *
+ * @param titles Array of image titles.
+ * @param ids Array of image IDs.
+ * @param title The title to match.
+ * @return The matching image ID, or 0 if not found.
+ */
+ public static int getImageIDByTitle(String[] titles, int[] ids, String title) {
+ for (int i = 0; i < titles.length; i++) {
+ if (titles[i].equals(title)) {
+ return ids[i]; // Return the corresponding ID if title matches
+ }
+ }
+ throw new IllegalArgumentException("Title not found: " + title); // Throw exception if title not found
+ }
+
+
+ /**
+ * Display the results as a 2D image.
+ *
+ * @param inputImage The input image used for display dimensions.
+ * @param repetitionMap The calculated repetition map to display.
+ */
+ public static void displayResults2D(Utils.InputImage2D inputImage, float[] repetitionMap) {
+ FloatProcessor fp1 = new FloatProcessor(inputImage.getWidth(), inputImage.getHeight(), repetitionMap);
+ ImagePlus imp1 = new ImagePlus("Repetition Map", fp1);
+
+ // Apply SReD LUT
+ InputStream lutStream = Utils.class.getResourceAsStream("/luts/sred-jet.lut");
+ if (lutStream == null) {
+ IJ.error("Could not load SReD LUT. Using default LUT.");
+ } else {
+ try {
+ // Load LUT file
+ IndexColorModel icm = LutLoader.open(lutStream);
+ byte[] r = new byte[256];
+ byte[] g = new byte[256];
+ byte[] b = new byte[256];
+ icm.getReds(r);
+ icm.getGreens(g);
+ icm.getBlues(b);
+ LUT lut = new LUT(8, 256, r, g, b);
+
+ // Apply LUT to image
+ imp1.getProcessor().setLut(lut);
+ } catch (IOException e) {
+ IJ.error("Could not load SReD LUT: " + e.getMessage());
+ }
+ }
+
+ // Display results
+ imp1.show();
+ }
+
+
+ /**
+ * Display the results as a 3D image.
+ *
+ * @param inputImage The input image used for display dimensions.
+ * @param repetitionMap The calculated repetition map to display.
+ */
+ public static void displayResults3D(Utils.InputImage3D inputImage, float[] repetitionMap) {
+
+ int imageWidth = inputImage.getWidth();
+ int imageHeight = inputImage.getHeight();
+ int imageDepth = inputImage.getDepth();
+ ImageStack ims = new ImageStack(imageWidth, imageHeight, imageDepth);
+
+ for(int z=0; z=w-bRW || gy=h-bRH){
+ diff_std_map[gy*w+gx] = 0.0f;
+ return;
+ }
+
+
+ // -------------------------------------------------------------- //
+ // ---- Calculate absolute difference of standard deviations ---- //
+ // -------------------------------------------------------------- //
+
+ float test_std = local_stds[gy*w+gx];
+
+ diff_std_map[gy*w+gx] = fabs((float)ref_std - (float)test_std);
+}
diff --git a/src/main/resources/kernelGetPatchCosineSim2D.cl b/src/main/resources/kernelGetBlockCosineSimilarity2D.cl
similarity index 55%
rename from src/main/resources/kernelGetPatchCosineSim2D.cl
rename to src/main/resources/kernelGetBlockCosineSimilarity2D.cl
index d132013..9d2f041 100644
--- a/src/main/resources/kernelGetPatchCosineSim2D.cl
+++ b/src/main/resources/kernelGetBlockCosineSimilarity2D.cl
@@ -3,18 +3,20 @@
#define h $HEIGHT$
#define bRW $BRW$
#define bRH $BRH$
-#define ref_std $PATCH_STD$
+#define ref_std $BLOCK_STD$
#define EPSILON $EPSILON$
-kernel void kernelGetPatchCosineSim2D(
+
+kernel void kernelGetBlockCosineSimilarity2D(
global float* local_stds,
- global float* diff_std_map
+ global float* cosine_similarity_map
){
int gx = get_global_id(0);
int gy = get_global_id(1);
- // Bound check (avoids borders dynamically based on patch dimensions)
+ // Bound check (avoids borders dynamically based on block dimensions)
if(gx=w-bRW || gy=h-bRH){
+ cosine_similarity_map[gy*w+gx] = 0.0f;
return;
}
@@ -25,6 +27,6 @@ kernel void kernelGetPatchCosineSim2D(
float test_std = local_stds[gy*w+gx];
- float similarity = (ref_std*test_std) / (sqrt(ref_std*ref_std)*sqrt(test_std*test_std)+EPSILON);
- diff_std_map[gy*w+gx] = (float)fmax(0.0f, similarity);
+ float similarity = (ref_std*test_std) / (sqrt((float)ref_std*(float)ref_std)*sqrt((float)test_std*(float)test_std)+EPSILON);
+ cosine_similarity_map[gy*w+gx] = (float)fmax(0.0f, similarity);
}
diff --git a/src/main/resources/kernelGetBlockCosineSimilarity3D.cl b/src/main/resources/kernelGetBlockCosineSimilarity3D.cl
new file mode 100644
index 0000000..ff714ae
--- /dev/null
+++ b/src/main/resources/kernelGetBlockCosineSimilarity3D.cl
@@ -0,0 +1,31 @@
+//#pragma OPENCL EXTENSION cl_khr_fp64 : enable
+#define imageWidth $WIDTH$
+#define imageHeight $HEIGHT$
+#define imageDepth $DEPTH$
+#define bRW $BRW$
+#define bRH $BRH$
+#define bRZ $BRZ$
+#define ref_std $BLOCK_STD$
+#define EPSILON $EPSILON$
+
+kernel void kernelGetBlockCosineSimilarity3D(
+ global float* local_stds,
+ global float* cosine_similarity_map
+){
+
+ int gx = get_global_id(0);
+ int gy = get_global_id(1);
+ int gz = get_global_id(2);
+
+ // Bound check (avoids borders dynamically based on block dimensions)
+ if(gx=imageWidth-bRW || gy=imageHeight-bRH || gz=imageDepth-bRZ){
+ cosine_similarity_map[imageWidth*imageHeight*gz+gy*imageWidth+gx] = 0.0f;
+ return;
+ }
+
+
+ // Calculate cosine similarity
+ float test_std = local_stds[imageWidth*imageHeight*gz+gy*imageWidth+gx];
+ float similarity = (ref_std*test_std) / (sqrt(ref_std*ref_std)*sqrt(test_std*test_std)+EPSILON);
+ cosine_similarity_map[imageWidth*imageHeight*gz+gy*imageWidth+gx] = (float)fmax(0.0f, similarity);
+}
diff --git a/src/main/resources/kernelGetPatchRmse2D.cl b/src/main/resources/kernelGetBlockNrmse2D.cl
similarity index 61%
rename from src/main/resources/kernelGetPatchRmse2D.cl
rename to src/main/resources/kernelGetBlockNrmse2D.cl
index ca39c4b..7f3d344 100644
--- a/src/main/resources/kernelGetPatchRmse2D.cl
+++ b/src/main/resources/kernelGetBlockNrmse2D.cl
@@ -1,16 +1,16 @@
//#pragma OPENCL EXTENSION cl_khr_fp64 : enable
#define w $WIDTH$
#define h $HEIGHT$
-#define patch_size $PATCH_SIZE$
+#define block_size $BLOCK_SIZE$
#define bW $BW$
#define bH $BH$
#define bRW $BRW$
#define bRH $BRH$
-#define ref_mean $PATCH_MEAN$
+#define ref_mean $BLOCK_MEAN$
#define EPSILON $EPSILON$
-kernel void kernelGetPatchRmse2D(
- global float* patch_pixels,
+kernel void kernelGetBlockNrmse2D(
+ global float* block_pixels,
global float* ref_pixels,
global float* local_means,
global float* rmse_map
@@ -19,8 +19,9 @@ kernel void kernelGetPatchRmse2D(
int gx = get_global_id(0);
int gy = get_global_id(1);
- // Bound check (avoids borders dynamically based on patch dimensions)
+ // Bound check (avoids borders dynamically based on block dimensions)
if(gx=w-bRW || gy=h-bRH){
+ rmse_map[gy*w+gx] = 0.0f;
return;
}
@@ -29,35 +30,35 @@ kernel void kernelGetPatchRmse2D(
// ---- Get mean-subtracted reference block ---- //
// --------------------------------------------- //
- __local float ref_patch[patch_size]; // Make a local copy to avoid slower reads from global memory
+ __local float ref_block[block_size]; // Make a local copy to avoid slower reads from global memory
- for(int i=0; i=image_width-bRW || gy=image_height-bRH || gz=image_depth-bRZ){
+ rmse_map[image_width*image_height*gz+gy*image_width+gx] = 0.0f;
+ return;
+ }
+
+
+ // Get mean-subtracted reference block
+ __local float ref_block[block_size]; // Make a local copy to avoid slower reads from global memory
+
+ for(int i=0; i=w-bRW || gy=h-bRH){
+ pearson_map[gy*w+gx] = 0.0f;
return;
}
@@ -31,35 +31,35 @@ kernel void kernelGetPatchPearson2D(
// ---- Get mean-subtracted reference block ---- //
// --------------------------------------------- //
- __local float ref_patch[patch_size]; // Make a local copy to avoid slower reads from global memory
+ __local float ref_block[block_size]; // Make a local copy to avoid slower reads from global memory
- for(int i=0; i=imageWidth-bRW || gy=imageHeight-bRH || gz=imageDepth-bRZ){
+ pearson_map[imageWidth*imageHeight*gz+gy*imageWidth+gx] = 0.0f;
+ return;
+ }
+
+ // Get mean-subtracted and normalized reference block from buffer
+ __local float ref_block[block_size]; // Make a local copy to avoid slower reads from global memory
+
+ for(int i=0; i=image_width-bRW || gy=image_height-bRH){
+ ssim_map[gy*image_width+gx] = 0.0f;
+ return;
+ }
+
+ // Get mean-subtracted reference block
+ __local float ref_block[block_size]; // Make a local copy to avoid slower reads from global memory
+
+ for(int i=0; i=image_width-bRW || gy=image_height-bRH || gz=image_depth-bRZ){
+ ssim_map[image_width*image_height*gz+gy*image_width+gx] = 0.0f;
+ return;
+ }
+
+
+ // --------------------------------------------- //
+ // ---- Get mean-subtracted reference block ---- //
+ // --------------------------------------------- //
+
+ __local float ref_block[block_size]; // Make a local copy to avoid slower reads from global memory
+
+ for(int i=0; i=w-bRW || gy=h-bRH){
- return;
- }
-
-
- // ------------------------------------------------------------ //
- // ---- Check to avoid blocks with no structural relevance ---- //
- // ------------------------------------------------------------ //
-
- float ref_std = local_stds[gy*w+gx];
- float ref_var = (float)ref_std*(float)ref_std;
-
- if(ref_var=image_width-bRW || gy=image_height-bRH){
+ diff_std_map[gy*image_width+gx] = 0.0f;
+ return;
+ }
+
+ // Check to avoid blocks with no structural relevance
+ float ref_std = local_stds[gy*image_width+gx];
+ float ref_var = (float)ref_std*(float)ref_std;
+
+ if(ref_var=image_width-bRW || gy=image_height-bRH || gz=image_depth-bRZ){
+ diff_std_map[image_width*image_height*gz+gy*image_width+gx] = 0.0f;
+ return;
+ }
+
+ // Check to avoid blocks with no structural relevance
+ int ref_index = image_width*image_height*gz+gy*image_width+gx;
+ float ref_std = local_stds[ref_index];
+ float ref_var = ref_std*ref_std;
+
+ if(ref_var=image_width-bRW || gy=image_height-bRH){
+ cosine_sim_map[gy*image_width+gx] = 0.0f;
+ return;
+ }
+
+ // Check to avoid blocks with no structural relevance
+ float ref_std = local_stds[gy*image_width+gx];
+ float ref_var = (float)ref_std*(float)ref_std;
+
+ if(ref_var=w-bRW || gy=h-bRH || gz=z-bRZ){
+ // Bound check to avoid borders
+ if(gx=image_width-bRW || gy=image_height-bRH || gz=image_depth-bRZ){
+ cosine_sim_map[image_width*image_height*gz+gy*image_width+gx] = 0.0f;
return;
}
-
- // ------------------------------------------------------------ //
- // ---- Check to avoid blocks with no structural relevance ---- //
- // ------------------------------------------------------------ //
-
- int ref_index = w * h * gz + gy * w + gx;
-
+ // Check to avoid blocks with no structural relevance
+ int ref_index = image_width*image_height*gz+gy*image_width+gx;
float ref_std = local_stds[ref_index];
float ref_var = ref_std*ref_std;
@@ -48,26 +37,22 @@ kernel void kernelGetCosineSimMap3D(
return;
}
-
- // -------------------------------------------------------------------- //
- // ---- Calculate similarity between the reference and test blocks ---- //
- // -------------------------------------------------------------------- //
-
+ // Calculate similarity between the reference and test blocks
float weight_sum = 0.0f;
float cosine_similarity_sum = 0.0f;
- for(int n=bRZ; n=image_width-bRW || gy=image_height-bRH){
+ return;
+ }
+
+ // Check to avoid blocks with no structural relevance
+ float ref_std = local_stds[gy*image_width+gx];
+ float ref_var = (float)ref_std*(float)ref_std;
+
+ if(ref_var=image_width-bRW || gy=image_height-bRH || gz=image_depth-bRZ){
+ rmse_map[image_width*image_height*gz+gy*image_width+gx] = 0.0f;
+ return;
+ }
+
+ // Check to avoid blocks with no structural relevance
+ int ref_index = image_width*image_height*gz+gy*image_width+gx;
+ float ref_std = local_stds[ref_index];
+ float ref_var = ref_std*ref_std;
+
+ if(ref_var=w-bRW || gy=h-bRH){
- return;
- }
-
-
- // ------------------------------------------------------------ //
- // ---- Check to avoid blocks with no structural relevance ---- //
- // ------------------------------------------------------------ //
-
- float ref_std = local_stds[gy*w+gx];
- float ref_var = (float)ref_std*(float)ref_std;
-
- if(ref_var=image_width-bRW || gy=image_height-bRH){
+ pearson_map[gy*image_width+gx] = 0.0f;
+ return;
+ }
+
+ // Check to avoid blocks with no structural relevance
+ float ref_std = local_stds[gy*image_width+gx];
+ float ref_var = (float)ref_std*(float)ref_std;
+
+ if(ref_var=image_width-bRW || gy=image_height-bRH || gz=image_depth-bRZ){
+ pearson_map[image_width*image_height*gz+gy*image_width+gx] = 0.0f;
+ return;
+ }
+
+ // Check to avoid blocks with no structural relevance
+ int ref_index = image_width*image_height*gz+gy*image_width+gx;
+ float ref_std = local_stds[ref_index];
+ float ref_var = ref_std*ref_std;
+
+ if(ref_var=image_width-bRW || gy=image_height-bRH){
+ ssim_map[gy*image_width+gx] = 0.0f;
+ return;
+ }
+
+ // Check to avoid blocks with no structural relevance
+ float ref_std = local_stds[gy*image_width+gx];
+ float ref_var = (float)ref_std*(float)ref_std;
+
+ if(ref_var=image_width-bRW || gy=image_height-bRH || gz=image_depth-bRZ){
+ ssim_map[image_width*image_height*gz+gy*image_width+gx] = 0.0f;
+ return;
+ }
+
+ // Check to avoid blocks with no structural relevance
+ int ref_index = image_width*image_height*gz+gy*image_width+gx;
+ float ref_std = local_stds[ref_index];
+ float ref_var = ref_std*ref_std;
+
+ if(ref_var=image_width-bRW || gy=image_height-bRH){
+ local_means[gy*image_width+gx] = 0.0f;
+ local_stds[gy*image_width+gx] = 0.0f;
+ return;
+ }
+
+ // Get block pixels
+ float block[block_size];
+ int index = 0;
+ for(int y=gy-bRH; y<=gy+bRH; y++){
+ for(int x=gx-bRW; x<=gx+bRW; x++){
+ // Extract only pixels within the inbound circle/ellipse
+ float dx = (float)(x-gx);
+ float dy = (float)(y-gy);
+ if(((dx*dx)/(float)(bRW*bRW))+((dy*dy)/(float)(bRH*bRH)) <= 1.0f){
+ block[index] = ref_pixels[y*image_width+x];
+ index++;
+ }
+ }
+ }
+
+ // Calculate block mean
+ float mean = 0.0f;
+ for(int i=0; i=image_width-bRW || gy=image_height-bRH || gz=image_depth-bRZ){
+ local_means[image_width*image_height*gz+gy*image_width+gx] = 0.0f;
+ local_stds[image_width*image_height*gz+gy*image_width+gx] = 0.0f;
+ return;
+ }
+
+ // Get block pixels
+ float block[block_size];
+ int index = 0;
+ for(int z=gz-bRZ; z<=gz+bRZ; z++){
+ for(int y=gy-bRH; y<=gy+bRH; y++){
+ for(int x=gx-bRW; x<=gx+bRW; x++){
+ // Extract only pixels within the inbound spheroid
+ float dx = (float)(x-gx);
+ float dy = (float)(y-gy);
+ float dz = (float)(z-gz);
+ if(((dx*dx)/(float)(bRW*bRW))+((dy*dy)/(float)(bRH*bRH))+((dz*dz)/(float)(bRZ*bRZ)) <= 1.0f){
+ block[index] = ref_pixels[image_width*image_height*z+y*image_width+x];
+ index++;
+ }
+ }
+ }
+ }
+
+ // Calculate block mean
+ float mean = 0.0f;
+ for(int i=0; i=w-bRW || y0=h-bRH){
- return;
- }
-
- // Get reference patch max and min
- float min_x = ref_pixels[y0*w+x0];
- float max_x = ref_pixels[y0*w+x0];
-
- for(int j0=y0-bRH; j0<=y0+bRH; j0++){
- for(int i0=x0-bRW; i0<=x0+bRW; i0++){
- if(ref_pixels[j0*w+i0] < min_x){
- min_x = ref_pixels[j0*w+i0];
- }
- if(ref_pixels[j0*w+i0] > max_x){
- max_x = ref_pixels[j0*w+i0];
- }
- }
- }
-
- // Get normalized reference patch
- float ref_patch[patch_size] = {0.0f};
- float ref_mean = local_means[y0*w+x0];
- float ref_std = local_stds[y0*w+x0];
-
- int ref_counter = 0;
- for(int j0=y0-bRH; j0<=y0+bRH; j0++){
- for(int i0=x0-bRW; i0<=x0+bRW; i0++){
- ref_patch[ref_counter] = (ref_pixels[j0*w+i0] - ref_mean);
- ref_counter++;
- }
- }
-
- // For each comparison pixel...
- float weight = 0.0f;
- for(int y1=bRH; y1 max_y){
- max_y = ref_pixels[j1*w+i1];
- }
- }
- }
-
- // Get normalized comparison patch
- float comp_patch[patch_size];
- float comp_mean = local_means[y1*w+x1];
- float comp_std = local_stds[y1*w+x1];
- int comp_counter = 0;
- for(int j1=y1-bRH; j1<=y1+bRH; j1++){
- for(int i1=x1-bRW; i1<=x1+bRW; i1++){
- comp_patch[comp_counter] = (ref_pixels[j1*w+i1] - comp_mean);
- comp_counter++;
- }
- }
-
- // Calculate weight
- weight = getExpDecayWeight(local_stds[y0*w+x0], local_stds[y1*w+x1]);
-
- // Calculate NRMSE and MAE
- float nrmse = 0.0f;
- float mae = 0.0f;
-
- for(int i=0; i=w-bRW || gy=h-bRH){
- return;
- }
-
-
- // -------------------------- //
- // ---- Get patch pixels ---- //
- // -------------------------- //
-
- float patch[patch_size];
- int index = 0;
- for(int j=gy-bRH; j<=gy+bRH; j++){
- for(int i=gx-bRW; i<=gx+bRW; i++){
- // Extract only pixels within the inbound circle/ellipse
- float dx = (float)(i-gx);
- float dy = (float)(j-gy);
- if(((dx*dx)/(float)(bRW*bRW))+((dy*dy)/(float)(bRH*bRH)) <= 1.0f){
- patch[index] = ref_pixels[j*w+i];
- index++;
- }
- }
- }
-
-
- // ------------------------------ //
- // ---- Calculate patch mean ---- //
- // ------------------------------ //
-
- float mean = 0.0f;
- for(int i=0; i=w-bRW || gy=h-bRH || gz=z-bRZ){
- return;
- }
-
-
- // -------------------------- //
- // ---- Get patch pixels ---- //
- // -------------------------- //
-
- float patch[patch_size];
- int index = 0;
- for(int n=gz-bRZ; n<=gz+bRZ; n++){
- for(int j=gy-bRH; j<=gy+bRH; j++){
- for(int i=gx-bRW; i<=gx+bRW; i++){
- // Extract only pixels within the inbound circle/ellipse
- float dx = (float)(i-gx);
- float dy = (float)(j-gy);
- float dz = (float)(n-gz);
- if(((dx*dx)/(float)(bRW*bRW))+((dy*dy)/(float)(bRH*bRH))+((dz*dz)/(float)(bRZ*bRZ)) <= 1.0f){
- patch[index] = ref_pixels[w*h*n+j*w+i];
- index++;
- }
- }
- }
- }
-
-
- // ------------------------------ //
- // ---- Calculate patch mean ---- //
- // ------------------------------ //
-
- float mean = 0.0f;
- for(int i=0; i=w-bRW || gy=h-bRH || gz=z-bRZ){
- return;
- }
-
-
- // ------------------------------------------------------------------------ //
- // ---- Get mean-subtracted and normalized reference patch from buffer ---- //
- // ------------------------------------------------------------------------ //
-
- __local float ref_patch[patch_size]; // Make a local copy to avoid slower reads from global memory
-
- for(int i=0; i=w-bRW || gy=h-bRH){
- return;
- }
-
-
- // --------------------------------------------- //
- // ---- Get mean-subtracted reference block ---- //
- // --------------------------------------------- //
-
- __local float ref_patch[patch_size]; // Make a local copy to avoid slower reads from global memory
-
- for(int i=0; i=w-bRW || y0=h-bRH){
- return;
- }
-
- // Get reference patch
- float ref_patch[patch_size] = {0.0f};
- float ref_mean = local_means[y0*w+x0];
- float ref_std = local_stds[y0*w+x0];
- float ref_var = ref_std * ref_std;
-
- int ref_counter = 0;
- for(int j0=y0-bRH; j0<=y0+bRH; j0++){
- for(int i0=x0-bRW; i0<=x0+bRW; i0++){
- ref_patch[ref_counter] = ref_pixels[j0*w+i0] - ref_mean;
- ref_counter++;
- }
- }
-
- // For each comparison pixel...
- float weight = 0.0f;
-
- for(int y1=bRH; y1SReD>Block repetition, "Find block repetition (2D)", BlockRepetition2D_
+Plugins>SReD>Block repetition, "Find block repetition (3D)", BlockRepetition3D_
+
+Plugins>SReD>Global Repetition, "Global repetition (2D)", GlobalRepetition2D_
+Plugins>SReD>Global Repetition, "Global repetition (3D)", GlobalRepetition3D_
+
Plugins>SReD>Noise variance stabilisation, "Stabilise noise variance (2D)", VarianceStabilisingTransform2D_
Plugins>SReD>Noise variance stabilisation, "Stabilise noise variance (3D)", VarianceStabilisingTransform3D_
-Plugins>SReD>Block repetition, "Find block repetition (2D)", BlockRedundancy2D_
-Plugins>SReD>Block repetition, "Find block repetition (3D)", BlockRedundancy3D_
-Plugins>SReD>Block repetition, "Find block repetition (2D+T)", BlockRedundancy2DT_
-Plugins>SReD>Global Repetition, "Global repetition (2D)", RedundancyMap_
-Plugins>SReD>Global Repetition, "Global repetition (3D)", GlobalRepetition3D_
-Plugins>SReD, "Cluster repetition scores", ClassifyRedundancy_
-Plugins>SReD, "OpenCL Preferences...", OpenCLPreferences_
+
Plugins>SReD>Relevance Mask, "Calculate Relevance Mask (2D)", RelevanceMask2D_
Plugins>SReD>Relevance Mask, "Calculate Relevance Mask stack (2D)", RelevanceMaskStack2D_
Plugins>SReD>Relevance Mask, "Optimise Relevance Constant (2D)", OptimiseRelevanceMask_
+
Plugins>SReD>Tools, "Calculate image statistics (2D)", CalculateLocalMeans_
+Plugins>SReD>Tools, "Cluster repetition scores", ClassifyRedundancy_
+Plugins>SReD>Tools, "Go to SReD Wiki page", OpenGithubWiki_
+
+Plugins>SReD, "OpenCL Preferences...", OpenCLPreferences_
+
diff --git a/src/test/java/CLUtilsTest.java b/src/test/java/CLUtilsTest.java
new file mode 100644
index 0000000..7966f10
--- /dev/null
+++ b/src/test/java/CLUtilsTest.java
@@ -0,0 +1,1069 @@
+import static java.lang.Math.*;
+import static org.junit.jupiter.api.Assertions.*;
+import com.jogamp.opencl.*;
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.nio.FloatBuffer;
+import org.junit.jupiter.api.Nested;
+import org.junit.jupiter.api.Test;
+
+public class CLUtilsTest {
+
+ @Nested
+ class OpenCLResourcesTest {
+
+ @Test
+ public void testOpenCLResourcesConstructor() {
+ // Initialize OpenCL context, device, and queue
+ CLContext context = null;
+ CLDevice device = null;
+ CLCommandQueue queue = null;
+
+ try {
+ // Create a default OpenCL context
+ CLPlatform platform = CLPlatform.listCLPlatforms()[0]; // Get the first platform
+ context = CLContext.create(platform);
+ device = context.getMaxFlopsDevice(); // Choose the best device
+ queue = device.createCommandQueue(); // Create a command queue
+
+ // Create an instance of OpenCLResources
+ CLUtils.OpenCLResources resources = new CLUtils.OpenCLResources(context, device, queue);
+
+ // Assertions to verify the properties of the OpenCLResources instance
+ assertNotNull(resources, "OpenCLResources instance should not be null.");
+ assertNotNull(resources.getContext(), "OpenCL context should not be null.");
+ assertNotNull(resources.getDevice(), "OpenCL device should not be null.");
+ assertNotNull(resources.getQueue(), "OpenCL command queue should not be null.");
+ assertEquals(context, resources.getContext(), "The context should match.");
+ assertEquals(device, resources.getDevice(), "The device should match.");
+ assertEquals(queue, resources.getQueue(), "The command queue should match.");
+
+ } catch (CLException e) {
+ fail("OpenCL initialization failed: " + e.getMessage());
+ } finally {
+ // Release resources if they were created
+ if (queue != null) queue.release();
+ if (context != null) context.release();
+ }
+ }
+ }
+
+ @Nested
+ class CLLocalStatisticsTest {
+
+ @Test
+ public void testCLLocalStatisticsConstructor() {
+ // Sample data for local means and standard deviations
+ float[] localMeans = {1.0f, 2.0f, 3.0f};
+ float[] localStds = {0.1f, 0.2f, 0.3f};
+
+ // Initialize OpenCL context, device, and queue
+ CLContext context = null;
+ CLDevice device = null;
+ CLCommandQueue queue = null;
+
+ CLPlatform platform = CLPlatform.listCLPlatforms()[0]; // Get the first platform
+ context = CLContext.create(platform);
+ device = context.getMaxFlopsDevice(); // Choose the best device
+ queue = device.createCommandQueue(); // Create a command queue
+
+ // Create mock OpenCL buffers (assuming these are valid)
+ CLBuffer clImageArray = context.createFloatBuffer(localMeans.length);
+ CLBuffer clLocalMeans = context.createFloatBuffer(localMeans.length);
+ CLBuffer clLocalStds = context.createFloatBuffer(localMeans.length);
+
+ // Create an instance of CLLocalStatistics
+ CLUtils.CLLocalStatistics localStatistics = new CLUtils.CLLocalStatistics(localMeans, localStds, clImageArray, clLocalMeans, clLocalStds);
+
+ // Assertions to verify the properties of the CLLocalStatistics instance
+ assertNotNull(localStatistics, "CLLocalStatistics instance should not be null.");
+ assertArrayEquals(localMeans, localStatistics.getLocalMeans(), "Local means should match.");
+ assertArrayEquals(localStds, localStatistics.getLocalStds(), "Local standard deviations should match.");
+ assertEquals(clImageArray, localStatistics.getCLImageArray(), "CL image array should match.");
+ assertEquals(clLocalMeans, localStatistics.getCLLocalMeans(), "CL local means buffer should match.");
+ assertEquals(clLocalStds, localStatistics.getCLLocalStds(), "CL local standard deviations buffer should match.");
+ }
+ }
+
+
+ // ------------------------------------------------------------------ //
+ // ---- METHODS FOR OPENCL INITIALISATION AND RESOURCE MANAGEMENT---- //
+ // ------------------------------------------------------------------ //
+
+ @Nested
+ class GetOpenCLResourcesTest {
+
+ @Test
+ public void testGetOpenCLResources() {
+
+ System.out.println("Library path: " + System.getProperty("java.library.path"));
+
+
+ // Call the method to get OpenCL resources
+ CLUtils.OpenCLResources resources = null;
+ boolean useDevice = false; // You can change this to true if you want to test user-defined device preferences
+
+ try {
+ resources = CLUtils.getOpenCLResources(useDevice);
+ } catch (RuntimeException e) {
+ fail("OpenCL initialization failed: " + e.getMessage());
+ }
+
+ // Assert that the resources are not null
+ assertNotNull(resources, "OpenCLResources should not be null.");
+
+ // Assert that the context, device, and queue are initialized
+ assertNotNull(resources.getContext(), "OpenCL context should not be null.");
+ assertNotNull(resources.getDevice(), "OpenCL device should not be null.");
+ assertNotNull(resources.getQueue(), "OpenCL command queue should not be null.");
+
+ // Assert properties of the device or context
+ CLDevice device = resources.getDevice();
+ assertTrue(device.getMaxComputeUnits() > 0, "Device should have compute units.");
+ assertTrue(device.getMaxClockFrequency() > 0, "Device should have a clock frequency.");
+ }
+ }
+
+
+ @Test
+ public void testCreateAndFillCLBuffer() {
+ // Create a CLContext
+ CLUtils.OpenCLResources resources = null;
+ try {
+ resources = CLUtils.getOpenCLResources(false);
+ } catch (RuntimeException e) {
+ fail("OpenCL initialization failed: " + e.getMessage());
+ }
+
+ // Sample data
+ int size = 5;
+ float[] inputArray = {1.0f, 2.0f, 3.0f, 4.0f, 5.0f};
+
+ // Create and fill CLBuffer
+ CLBuffer clBuffer = CLUtils.createAndFillCLBuffer(resources.getContext(), size, CLMemory.Mem.READ_WRITE, inputArray);
+
+ // Validate the CLBuffer is not null
+ assertNotNull(clBuffer, "CLBuffer should not be null.");
+
+ // Validate that the buffer has the expected size
+ assertEquals(size, clBuffer.getBuffer().capacity(), "CLBuffer size should match the input size.");
+
+ // Validate the contents of the buffer
+ FloatBuffer bufferContent = clBuffer.getBuffer();
+ for (int i = 0; i < size; i++) {
+ assertEquals(inputArray[i], bufferContent.get(i), "Buffer content at index " + i + " should match the input array.");
+ }
+
+ // Cleanup
+ clBuffer.release(); // Release the CLBuffer
+ resources.getContext().release();
+ }
+
+
+ @Test
+ public void testFillBufferWithFloatArray() {
+ // Create a CLContext
+ CLUtils.OpenCLResources resources = null;
+ try {
+ resources = CLUtils.getOpenCLResources(false);
+ } catch (RuntimeException e) {
+ fail("OpenCL initialization failed: " + e.getMessage());
+ }
+
+ // Sample data
+ int size = 5;
+ float[] inputArray = {1.0f, 2.0f, 3.0f, 4.0f, 5.0f};
+
+ // Create an empty CLBuffer
+ CLBuffer clBuffer = resources.getContext().createFloatBuffer(size, CLMemory.Mem.READ_WRITE);
+
+ // Fill the CLBuffer with the input array
+ CLUtils.fillBufferWithFloatArray(clBuffer, inputArray);
+
+ // Validate the CLBuffer is not null
+ assertNotNull(clBuffer, "CLBuffer should not be null.");
+
+ // Validate that the buffer has the expected size
+ assertEquals(size, clBuffer.getBuffer().capacity(), "CLBuffer size should match the input size.");
+
+ // Validate the contents of the buffer
+ FloatBuffer bufferContent = clBuffer.getBuffer();
+ for (int i = 0; i < size; i++) {
+ assertEquals(inputArray[i], bufferContent.get(i), "Buffer content at index " + i + " should match the input array.");
+ }
+
+ // Cleanup
+ clBuffer.release();
+ resources.getContext().release();
+ }
+
+
+ @Test
+ public void testGetResourceAsString() {
+ // Get the resource as a string
+ String resourceContent = CLUtils.getResourceAsString(CLUtils.class, "kernelGetBlockPearson2D.cl");
+
+ // Validate that the content is not null
+ assertNotNull(resourceContent, "Resource content should not be null.");
+
+ // Validate the content is as expected (update with your expected content)
+ String expectedContent = "//#pragma OPENCL EXTENSION cl_khr_fp64 : enable\n" +
+ "#define w $WIDTH$\n" +
+ "#define h $HEIGHT$\n" +
+ "#define block_size $BLOCK_SIZE$\n" +
+ "#define bW $BW$\n" +
+ "#define bH $BH$\n" +
+ "#define bRW $BRW$\n" +
+ "#define bRH $BRH$\n" +
+ "#define ref_std $BLOCK_STD$\n" +
+ "#define EPSILON $EPSILON$\n" +
+ "\n" +
+ "kernel void kernelGetBlockPearson2D(\n" +
+ " global float* block_pixels,\n" +
+ " global float* ref_pixels,\n" +
+ " global float* local_means,\n" +
+ " global float* local_stds,\n" +
+ " global float* pearson_map\n" +
+ "){\n" +
+ "\n" +
+ " int gx = get_global_id(0);\n" +
+ " int gy = get_global_id(1);\n" +
+ "\n" +
+ " // Bound check (avoids borders dynamically based on block dimensions)\n" +
+ " if(gx=w-bRW || gy=h-bRH){\n" +
+ " pearson_map[gy*w+gx] = 0.0f;\n" +
+ " return;\n" +
+ " }\n" +
+ "\n" +
+ "\n" +
+ " // --------------------------------------------- //\n" +
+ " // ---- Get mean-subtracted reference block ---- //\n" +
+ " // --------------------------------------------- //\n" +
+ "\n" +
+ " __local float ref_block[block_size]; // Make a local copy to avoid slower reads from global memory\n" +
+ "\n" +
+ " for(int i=0; i= 0.0f && value <= 1.0f); // Assuming result is normalized
+ }
+
+ // Check if result is as expected
+ for(int i=0; i= 0.0f && value <= 1.0f); // Assuming result is normalized
+ }
+
+ // Check if result is as expected
+ for(int i=0; i= 0.0f && value <= 1.0f); // Assuming result is normalized
+ }
+
+ // Check if result is as expected
+ for(int i=0; i= 0.0f && value <= 1.0f); // Assuming result is normalized
+ }
+
+ // Check if result is as expected
+ for(int i=0; i= 0.0f && value <= 1.0f); // Assuming result is normalized
+ }
+
+ // Check if result is as expected
+ for(int i=0; i 0);
+
+ // Check mean and std dev (expected values would depend on the generated block pixels)
+ assertEquals(0.5f, block.getMean(), 1e-6);
+ assertEquals(0.64549720287323f, block.getStd(), 1e-6);
+ }
+
+ @Test
+ public void testGetReferenceBlock2DEvenDimensions() {
+ // Create a new image with even dimensions (invalid case)
+ float[] evenPixels = {
+ 1, 2, 3, 4,
+ 2, 3, 4, 5,
+ 3, 4, 5, 6,
+ 4, 5, 6, 7
+ };
+ FloatProcessor evenFp = new FloatProcessor(4, 4, evenPixels);
+ ImagePlus evenImage = new ImagePlus("Even Dimension Image", evenFp);
+
+ // Set the new image as the current image
+ WindowManager.setTempCurrentImage(evenImage);
+
+ // Retrieve the block
+ Utils.ReferenceBlock2D block = Utils.getReferenceBlock2D(evenImage.getID());
+
+ assertNull(block, "Block should be null for even dimensions");
+ }
+
+ @Test
+ public void testGetReferenceBlock2DNonExistentBlock() {
+ // Simulate a case where block image doesn't exist
+ WindowManager.setTempCurrentImage(null);
+
+ Utils.ReferenceBlock2D block = Utils.getReferenceBlock2D(999); // Non-existent blockID
+
+ assertNull(block, "Block should be null if image not found");
+ }
+ }
+
+
+ @Nested
+ class GetInputImage2DTest {
+
+ @BeforeEach
+ public void setUp() {
+ // Clean WindowManager before each test
+ WindowManager.closeAllWindows();
+ }
+
+ @Test
+ public void testGetInputImage2D_NoStabilization_NoNormalization() {
+ // Set up image dimensions and data
+ int width = 3;
+ int height = 3;
+ float[] pixels = {
+ 0.0f, 0.1f, 0.2f,
+ 0.3f, 0.4f, 0.5f,
+ 0.6f, 0.7f, 0.8f
+ };
+
+ // Create FloatProcessor and ImagePlus
+ FloatProcessor fp = new FloatProcessor(width, height, pixels);
+ ImagePlus imp = new ImagePlus("Test Image", fp);
+
+ // Add the ImagePlus to the WindowManager
+ WindowManager.setTempCurrentImage(imp);
+ int imageID = imp.getID();
+
+ // Call the method
+ Utils.InputImage2D result = Utils.getInputImage2D(imageID, false, false);
+
+ // Verify the result
+ assertNotNull(result);
+ assertArrayEquals(pixels, result.getImageArray());
+ assertEquals(width, result.getWidth());
+ assertEquals(height, result.getHeight());
+ assertEquals(width * height, result.getSize());
+ }
+
+ @Test
+ public void testGetInputImage2D_WithNormalization() {
+ // Set up image dimensions and data
+ int width = 2;
+ int height = 2;
+ float[] pixels = {
+ 10.0f, 20.0f,
+ 30.0f, 40.0f
+ };
+
+ // Expected normalized output
+ float[] normalizedPixels = {
+ 0.0f, 0.3333f,
+ 0.6667f, 1.0f
+ };
+
+ // Create FloatProcessor and ImagePlus
+ FloatProcessor fp = new FloatProcessor(width, height, pixels);
+ ImagePlus imp = new ImagePlus("Test Image", fp);
+
+ // Add the ImagePlus to the WindowManager
+ WindowManager.setTempCurrentImage(imp);
+ int imageID = imp.getID();
+
+ // Call the method with normalization
+ Utils.InputImage2D result = Utils.getInputImage2D(imageID, false, true);
+
+ // Verify the result
+ assertNotNull(result);
+ assertEquals(width, result.getWidth());
+ assertEquals(height, result.getHeight());
+ assertEquals(width * height, result.getSize());
+
+ // Check normalized values with a small delta for floating-point comparison
+ for (int i = 0; i < normalizedPixels.length; i++) {
+ assertEquals(normalizedPixels[i], result.getImageArray()[i], 0.0001);
+ }
+ }
+
+ @Test
+ public void testGetInputImage2D_ImageNotFound() {
+ // Call the method with an invalid imageID
+ Utils.InputImage2D result = Utils.getInputImage2D(999, false, false);
+
+ // Verify that the method returns null when image is not found
+ assertNull(result);
+ }
+ }
+
+
+ @Nested
+ class GetInputImage2DTestOverloaded {
+
+ @Test
+ public void testGetInputImage2D_NoStabilization_NoNormalization() {
+ // Image setup
+ int width = 3;
+ int height = 3;
+ float[] pixels = {
+ 0.0f, 0.1f, 0.2f,
+ 0.3f, 0.4f, 0.5f,
+ 0.6f, 0.7f, 0.8f
+ };
+
+ // Call the method with no stabilization and no normalization
+ Utils.InputImage2D result = Utils.getInputImage2D(pixels, width, height, false, 0.0f, 0.0f, 0.0f, false);
+
+ // Verify the result
+ assertNotNull(result);
+ assertArrayEquals(pixels, result.getImageArray());
+ assertEquals(width, result.getWidth());
+ assertEquals(height, result.getHeight());
+ assertEquals(width * height, result.getSize());
+ }
+
+ @Test
+ public void testGetInputImage2D_WithNormalization() {
+ // Image setup
+ int width = 2;
+ int height = 2;
+ float[] pixels = {
+ 10.0f, 20.0f,
+ 30.0f, 40.0f
+ };
+
+ // Expected normalized output
+ float[] normalizedPixels = {
+ 0.0f, 0.3333f,
+ 0.6667f, 1.0f
+ };
+
+ // Call the method with normalization enabled
+ Utils.InputImage2D result = Utils.getInputImage2D(pixels, width, height, false, 0.0f, 0.0f, 0.0f, true);
+
+ // Verify the result
+ assertNotNull(result);
+ assertEquals(width, result.getWidth());
+ assertEquals(height, result.getHeight());
+ assertEquals(width * height, result.getSize());
+
+ // Check normalized values with a small delta for floating-point comparison
+ for (int i = 0; i < normalizedPixels.length; i++) {
+ assertEquals(normalizedPixels[i], result.getImageArray()[i], 0.0001);
+ }
+ }
+
+
+ }
+
+
+ @Nested
+ class GetMeanNoiseVarTest {
+
+ @Test
+ public void testGetMeanNoiseVar2D() {
+ // Setup a 100x100 image
+ int width = 100; // Image width
+ int height = 100; // Image height
+ float[] refPixels = new float[width * height]; // Initialize array for 100x100 image
+
+ // Fill the array with a simple pattern, e.g., a gradient
+ for (int y = 0; y < height; y++) {
+ for (int x = 0; x < width; x++) {
+ refPixels[y * width + x] = (float) (x + y * width) / (width * height);
+ }
+ }
+
+ // Calculate total number of pixels
+ int wh = width * height; // Total number of pixels
+
+ // Execute
+ float result = Utils.getMeanNoiseVar2D(refPixels, width, height, wh);
+
+ // Assertions
+ // Check that the result is greater than or equal to zero
+ assertTrue(result >= 0.0f, "The noise variance should be non-negative.");
+
+ // You can also add checks for specific expected values based on known input
+ // For instance, if you know the expected variance for this input
+ // assertEquals(expectedVariance, result, 0.01f); // Adjust tolerance as necessary
+ }
+ }
+
+
+ @Nested
+ class GetMeanAndVarBlock2DTest {
+
+ @Test
+ public void testGetMeanAndVarBlock2D() {
+ // Setup a 100x100 image with known pixel values
+ int width = 100;
+ int height = 100;
+ float[] imageArray = new float[width * height];
+
+ // Fill the array with a simple pattern (e.g., x + y)
+ for (int y = 0; y < height; y++) {
+ for (int x = 0; x < width; x++) {
+ imageArray[y * width + x] = (float) (x + y);
+ }
+ }
+
+ // Define the block to test (10x10 block starting at (10, 10))
+ int xStart = 10;
+ int yStart = 10;
+ int xEnd = 20; // Exclusive
+ int yEnd = 20; // Exclusive
+
+ // Execute the method
+ float[] meanVar = Utils.getMeanAndVarBlock2D(imageArray, width, xStart, yStart, xEnd, yEnd);
+
+ // Calculate expected mean and variance manually for the block (10x10 block from (10, 10) to (19, 19))
+ float sum = 0.0f;
+ int totalPixels = (xEnd - xStart) * (yEnd - yStart); // 10x10 = 100 pixels
+ for (int y = yStart; y < yEnd; y++) {
+ for (int x = xStart; x < xEnd; x++) {
+ sum += (x + y);
+ }
+ }
+ float expectedMean = sum / totalPixels;
+
+ // For variance, you can compute it similarly by iterating over the block and calculating the sum of squared differences
+ float varianceSum = 0.0f;
+ for (int y = yStart; y < yEnd; y++) {
+ for (int x = xStart; x < xEnd; x++) {
+ float value = (x + y);
+ varianceSum += Math.pow(value - expectedMean, 2);
+ }
+ }
+ float expectedVar = varianceSum / totalPixels;
+
+ // Assertions
+ assertEquals(expectedMean, meanVar[0], 0.01f, "Mean value should match the expected value.");
+ assertEquals(expectedVar, meanVar[1], 0.01f, "Variance value should match the expected value.");
+ }
+ }
+
+
+ @Nested
+ class ApplyMaskTest {
+
+ @Test
+ public void testApplyMask2D() {
+ // Setup a 3x3 image with known pixel values
+ int width = 3;
+ int height = 3;
+ float[] imageArray = new float[] {
+ 1.0f, 2.0f, 3.0f,
+ 4.0f, 5.0f, 6.0f,
+ 7.0f, 8.0f, 9.0f
+ };
+
+ // Create a mask with known values (e.g., a mask that zeroes out some values)
+ float[] mask = new float[] {
+ 1.0f, 0.0f, 1.0f,
+ 0.0f, 1.0f, 0.0f,
+ 1.0f, 1.0f, 0.0f
+ };
+
+ // Expected result after applying the mask
+ float[] expectedResult = new float[] {
+ 1.0f, 0.0f, 3.0f,
+ 0.0f, 5.0f, 0.0f,
+ 7.0f, 8.0f, 0.0f
+ };
+
+ // Execute the method
+ float[] result = Utils.applyMask2D(imageArray, width, height, mask);
+
+ // Assertions to check if the result matches the expected result
+ assertArrayEquals(expectedResult, result);
+ }
+ }
+
+
+ @Test
+ public void GetRelevanceMaskTest() {
+ // Setup a larger 50x50 image with known pixel values
+ int width = 50;
+ int height = 50;
+
+ // Initialize the imageArray and localStds with some sample data (mock values for illustration)
+ float[] imageArray = new float[width * height];
+ float[] localStds = new float[width * height];
+
+ // Set a seed for reproducibility
+ Random random = new Random(42); // Fixed seed
+
+ // Fill the arrays with reproducible values
+ for (int i = 0; i < width * height; i++) {
+ imageArray[i] = (i % 50 == 0) ? 1.0f : 1.0f + random.nextFloat() * 2.0f; // Values between 1.0f and 3.0f
+ localStds[i] = (i % 50 == 0) ? 0.0f : random.nextFloat(); // Local standard deviations
+ }
+
+ // Parameters for the relevance mask
+ int blockRadiusWidth = 2; // Adjusted radius
+ int blockRadiusHeight = 2; // Adjusted radius
+ float relevanceConstant = 2.0f;
+
+ // Calculate expected noise mean variance based on mock data
+ float noiseMeanVariance = 0.25f; // Assuming pre-calculated based on input
+
+ // Create expected relevance mask (for a large test case, exact expected values may vary)
+ float relevanceThreshold = noiseMeanVariance * relevanceConstant;
+
+ // Execute the method
+ Utils.RelevanceMask relevanceMask = Utils.getRelevanceMask(imageArray, width, height, blockRadiusWidth, blockRadiusHeight, localStds, relevanceConstant);
+
+ // Assertions to check if the relevance mask is correctly generated
+ assertEquals(relevanceConstant, relevanceMask.getRelevanceConstant());
+ assertEquals(0.4694742262363434f, relevanceMask.getRelevanceThreshold());
+ assertEquals(0.2347371131181717, relevanceMask.getNoiseMeanVariance());
+
+ // Optionally check some values in the mask for correctness (e.g., expected ranges or patterns)
+ // Example: Check some central part of the mask
+ for (int y = 20; y < 30; y++) {
+ for (int x = 20; x < 30; x++) {
+ assertTrue(relevanceMask.getRelevanceMask()[y * width + x] == 1.0f || relevanceMask.getRelevanceMask()[y * width + x] == 0.0f);
+ }
+ }
+ }
+
+
+ @Nested
+ class NormalizeImage2DTest {
+
+ @Test
+ public void testNormalizeImageWithBinaryMask() {
+ // Setup: a small 4x4 image with known pixel values and a valid binary mask
+ int width = 4;
+ int height = 4;
+ int borderWidth = 1;
+ int borderHeight = 1;
+
+ float[] imageArray = new float[] {
+ 1.0f, 2.0f, 3.0f, 4.0f,
+ 5.0f, 6.0f, 7.0f, 8.0f,
+ 9.0f, 10.0f, 11.0f, 12.0f,
+ 13.0f, 14.0f, 15.0f, 16.0f
+ };
+
+ // Binary mask (0.0f or 1.0f only)
+ float[] mask = new float[] {
+ 1.0f, 0.0f, 1.0f, 0.0f,
+ 1.0f, 1.0f, 0.0f, 0.0f,
+ 1.0f, 0.0f, 1.0f, 1.0f,
+ 0.0f, 0.0f, 0.0f, 1.0f
+ };
+
+ // Expected normalized array with the mask applied (min = 1.0, max = 16.0)
+ float[] expectedArray = new float[] {
+ 1.0f, 2.0f, 3.0f, 4.0f,
+ 5.0f, 0.0f, 7.0f, 8.0f,
+ 9.0f, 10.0f, 1.0f, 12.0f,
+ 13.0f, 14.0f, 15.0f, 16.0f
+ };
+
+ // Run the normalization with a binary mask
+ float[] normalizedArray = Utils.normalizeImage2D(imageArray, width, height, borderWidth, borderHeight, mask);
+
+ // Assertions: Check that the normalized array is as expected
+ assertArrayEquals(expectedArray, normalizedArray, 0.0001f);
+ }
+
+ @Test
+ public void testNormalizeImageWithInvalidMask() {
+ // Setup: a small 4x4 image with known pixel values and an invalid mask (non-binary values)
+ int width = 4;
+ int height = 4;
+ int borderWidth = 0;
+ int borderHeight = 0;
+
+ float[] imageArray = new float[] {
+ 1.0f, 2.0f, 3.0f, 4.0f,
+ 5.0f, 6.0f, 7.0f, 8.0f,
+ 9.0f, 10.0f, 11.0f, 12.0f,
+ 13.0f, 14.0f, 15.0f, 16.0f
+ };
+
+ // Invalid mask (contains values other than 0.0f or 1.0f)
+ float[] invalidMask = new float[] {
+ 1.0f, 0.5f, 1.0f, 0.0f,
+ 1.0f, 0.8f, 0.0f, 0.0f,
+ 1.0f, 0.0f, 1.0f, 1.0f,
+ 0.0f, 0.0f, 0.0f, 1.0f
+ };
+
+ // Expect an IllegalArgumentException due to non-binary mask values
+ assertThrows(IllegalArgumentException.class, () -> {
+ Utils.normalizeImage2D(imageArray, width, height, borderWidth, borderHeight, invalidMask);
+ });
+ }
+ }
+
+
+ // ----------------------------------------- //
+ // ---- METHODS FOR BLOCK REPETITION 3D ---- //
+ // ----------------------------------------- //
+
+ @Nested
+ class GetReferenceBlock3DTest {
+
+ @Test
+ public void testBlockRetrieval() {
+ // Create a 3D ImagePlus object (3x3x3 block) and assign it a window ID
+ int width = 3;
+ int height = 3;
+ int depth = 3;
+ ImageStack stack = new ImageStack(width, height);
+
+ // Fill each slice with some values
+ for (int z = 0; z < depth; z++) {
+ FloatProcessor fp = new FloatProcessor(width, height);
+ for (int y = 0; y < height; y++) {
+ for (int x = 0; x < width; x++) {
+ fp.setf(x, y, (z + 1) * (x + 1) + y); // Some arbitrary pattern
+ }
+ }
+ stack.addSlice(fp);
+ }
+ ImagePlus imagePlus = new ImagePlus("Test Image", stack);
+ imagePlus.show(); // This assigns it an ID in the WindowManager
+
+ // Get the blockID (WindowManager uses 1-based indexing)
+ int blockID = imagePlus.getID();
+
+ // Run the method
+ Utils.ReferenceBlock3D refBlock = Utils.getReferenceBlock3D(blockID);
+
+ // Assert that the block is not null
+ assertNotNull(refBlock);
+
+ // Assert block dimensions
+ assertEquals(width, refBlock.getWidth());
+ assertEquals(height, refBlock.getHeight());
+ assertEquals(depth, refBlock.getDepth());
+
+ // Assert blockSize matches the count of pixels within the ellipsoid
+ assertTrue(refBlock.getSize() > 0);
+
+ // Clean up ImageJ windows after the test
+ imagePlus.changes = false; // Prevents "Save changes?" prompt
+ imagePlus.close();
+ }
+
+ @Test
+ public void testOddDimensionValidation() {
+ // Create a 3D ImagePlus object with even dimensions (should fail)
+ int width = 4; // Even
+ int height = 3; // Odd
+ int depth = 3; // Odd
+ ImageStack stack = new ImageStack(width, height);
+
+ // Fill each slice
+ for (int z = 0; z < depth; z++) {
+ FloatProcessor fp = new FloatProcessor(width, height);
+ stack.addSlice(fp);
+ }
+ ImagePlus imagePlus = new ImagePlus("Test Even Width Image", stack);
+ imagePlus.show();
+
+ // Run the method and expect an exception due to even dimensions
+ int blockID = imagePlus.getID();
+ Exception exception = assertThrows(IllegalArgumentException.class, () -> {
+ Utils.getReferenceBlock3D(blockID);
+ });
+ assertEquals("Block dimensions must be odd.", exception.getMessage());
+
+ // Clean up ImageJ windows after the test
+ imagePlus.changes = false;
+ imagePlus.close();
+ }
+
+ @Test
+ public void testMinimumSlicesValidation() {
+ // Create a 3D ImagePlus object with fewer than 3 slices (should fail)
+ int width = 3;
+ int height = 3;
+ int depth = 1; // Less than 3 slices
+ ImageStack stack = new ImageStack(width, height);
+
+ // Fill each slice
+ for (int z = 0; z < depth; z++) {
+ FloatProcessor fp = new FloatProcessor(width, height);
+ stack.addSlice(fp);
+ }
+ ImagePlus imagePlus = new ImagePlus("Test Few Slices Image", stack);
+ imagePlus.show();
+
+ // Run the method and expect an exception due to fewer than 3 slices
+ int blockID = imagePlus.getID();
+ Exception exception = assertThrows(IllegalArgumentException.class, () -> {
+ Utils.getReferenceBlock3D(blockID);
+ });
+ assertEquals("Reference block must have at least 3 slices.", exception.getMessage());
+
+ // Clean up ImageJ windows after the test
+ imagePlus.changes = false;
+ imagePlus.close();
+ }
+
+ @Test
+ public void testNormalizationAndStatistics() {
+ // Create a 3x3x3 ImagePlus object for testing normalization and statistics
+ int width = 3;
+ int height = 3;
+ int depth = 3;
+ ImageStack stack = new ImageStack(width, height);
+
+ // Fill each slice with a known pattern
+ float[][] slices = {
+ { 1.0f, 2.0f, 3.0f, 4.0f, 5.0f, 6.0f, 7.0f, 8.0f, 9.0f },
+ { 9.0f, 8.0f, 7.0f, 6.0f, 5.0f, 4.0f, 3.0f, 2.0f, 1.0f },
+ { 1.0f, 3.0f, 5.0f, 7.0f, 9.0f, 2.0f, 4.0f, 6.0f, 8.0f }
+ };
+ for (int z = 0; z < depth; z++) {
+ FloatProcessor fp = new FloatProcessor(width, height);
+ fp.setPixels(slices[z]);
+ stack.addSlice(fp);
+ }
+ ImagePlus imagePlus = new ImagePlus("Test Image for Stats", stack);
+ imagePlus.show();
+
+ // Get the blockID (WindowManager uses 1-based indexing)
+ int blockID = imagePlus.getID();
+
+ // Run the method
+ Utils.ReferenceBlock3D refBlock = Utils.getReferenceBlock3D(blockID);
+
+ // Validate the statistics of the block
+ assertNotNull(refBlock);
+
+ // Ensure that the mean and standard deviation have been computed
+ assertTrue(refBlock.getMean() != 0.0f);
+ assertTrue(refBlock.getStd() != 0.0f);
+
+ // Clean up ImageJ windows after the test
+ imagePlus.changes = false;
+ imagePlus.close();
+ }
+ }
+
+
+ @Nested
+ class GetInputImage3DTest {
+
+ @Test
+ public void testImageRetrieval() {
+ // Create a 3D ImagePlus object (3x3x3 block) and assign it a window ID
+ int width = 3;
+ int height = 3;
+ int depth = 3;
+ ImageStack stack = new ImageStack(width, height);
+
+ // Fill each slice with some values
+ for (int z = 0; z < depth; z++) {
+ FloatProcessor fp = new FloatProcessor(width, height);
+ for (int y = 0; y < height; y++) {
+ for (int x = 0; x < width; x++) {
+ fp.setf(x, y, (z + 1) * (x + 1) + y); // Arbitrary values
+ }
+ }
+ stack.addSlice(fp);
+ }
+ ImagePlus imagePlus = new ImagePlus("Test Image", stack);
+ imagePlus.show(); // Assigns an ID in WindowManager
+
+ // Get the imageID (WindowManager uses 1-based indexing)
+ int imageID = imagePlus.getID();
+
+ // Call the method (without stabilizing noise variance, but normalizing output)
+ Utils.InputImage3D inputImage = Utils.getInputImage3D(imageID, false, true);
+
+ // Assertions
+ assertNotNull(inputImage, "Image should be retrieved successfully.");
+ assertEquals(width, inputImage.getWidth(), "Width should match.");
+ assertEquals(height, inputImage.getHeight(), "Height should match.");
+ assertEquals(depth, inputImage.getDepth(), "Depth should match.");
+ assertEquals(width * height * depth, inputImage.getSize(), "Size should match.");
+
+ // Clean up ImageJ windows after the test
+ imagePlus.changes = false; // Prevents "Save changes?" prompt
+ imagePlus.close();
+ }
+
+ @Test
+ public void testImageNotFound() {
+ // Call method with an invalid image ID
+ int invalidImageID = 9999; // Assuming this ID does not exist
+ Utils.InputImage3D inputImage = Utils.getInputImage3D(invalidImageID, false, false);
+
+ // Assert that the method returns null when the image is not found
+ assertNull(inputImage, "Image should not be found and the method should return null.");
+ }
+
+ @Test
+ public void testInsufficientSlices() {
+ // Create an ImagePlus object with only 2 slices (should fail)
+ int width = 3;
+ int height = 3;
+ int depth = 2; // Less than 3 slices
+ ImageStack stack = new ImageStack(width, height);
+
+ // Fill each slice
+ for (int z = 0; z < depth; z++) {
+ FloatProcessor fp = new FloatProcessor(width, height);
+ stack.addSlice(fp);
+ }
+ ImagePlus imagePlus = new ImagePlus("Test Few Slices Image", stack);
+ imagePlus.show(); // Assigns an ID in WindowManager
+
+ // Get the imageID
+ int imageID = imagePlus.getID();
+
+ // Call the method (should return null due to insufficient slices)
+ Utils.InputImage3D inputImage = Utils.getInputImage3D(imageID, false, false);
+
+ // Assert that the method returns null when there are fewer than 3 slices
+ assertNull(inputImage, "Image must have at least 3 slices, method should return null.");
+
+ // Clean up ImageJ windows after the test
+ imagePlus.changes = false;
+ imagePlus.close();
+ }
+
+ @Test
+ public void testNormalization() {
+ // Create a 3x3x3 ImagePlus object for testing normalization
+ int width = 3;
+ int height = 3;
+ int depth = 3;
+ ImageStack stack = new ImageStack(width, height);
+
+ // Fill each slice with a known pattern
+ float[][] slices = {
+ { 1.0f, 2.0f, 3.0f, 4.0f, 5.0f, 6.0f, 7.0f, 8.0f, 9.0f }, // Slice 1
+ { 9.0f, 8.0f, 7.0f, 6.0f, 5.0f, 4.0f, 3.0f, 2.0f, 1.0f }, // Slice 2
+ { 1.0f, 3.0f, 5.0f, 7.0f, 9.0f, 2.0f, 4.0f, 6.0f, 8.0f } // Slice 3
+ };
+ for (int z = 0; z < depth; z++) {
+ FloatProcessor fp = new FloatProcessor(width, height);
+ fp.setPixels(slices[z]);
+ stack.addSlice(fp);
+ }
+ ImagePlus imagePlus = new ImagePlus("Test Image for Normalization", stack);
+ imagePlus.show(); // Assigns an ID in WindowManager
+
+ // Get the imageID
+ int imageID = imagePlus.getID();
+
+ // Call the method with normalization enabled
+ Utils.InputImage3D inputImage = Utils.getInputImage3D(imageID, false, true);
+
+ // Assertions
+ assertNotNull(inputImage, "Image should be retrieved successfully.");
+ float[] normalizedArray = inputImage.getImageArray();
+ assertNotNull(normalizedArray, "Array should not be null after normalization.");
+ assertEquals(width * height * depth, normalizedArray.length, "Array length should match image size.");
+
+ // Ensure array values are within the normalized range (0.0f - 1.0f)
+ for (float value : normalizedArray) {
+ assertTrue(value >= 0.0f && value <= 1.0f, "All values should be in the range [0.0, 1.0].");
+ }
+
+ // Clean up ImageJ windows after the test
+ imagePlus.changes = false;
+ imagePlus.close();
+ }
+
+ @Test
+ public void testNoiseVarianceStabilization() {
+ // Create a 3x3x3 ImagePlus object for testing noise variance stabilization
+ int width = 3;
+ int height = 3;
+ int depth = 3;
+ ImageStack stack = new ImageStack(width, height);
+
+ // Fill each slice with arbitrary values
+ for (int z = 0; z < depth; z++) {
+ FloatProcessor fp = new FloatProcessor(width, height);
+ for (int y = 0; y < height; y++) {
+ for (int x = 0; x < width; x++) {
+ fp.setf(x, y, (float) (Math.random() * 100)); // Random values
+ }
+ }
+ stack.addSlice(fp);
+ }
+ ImagePlus imagePlus = new ImagePlus("Test Image for Noise Variance", stack);
+ imagePlus.show(); // Assigns an ID in WindowManager
+
+ // Get the imageID
+ int imageID = imagePlus.getID();
+
+ // Call the method with noise variance stabilization enabled
+ Utils.InputImage3D inputImage = Utils.getInputImage3D(imageID, true, false);
+
+ // Assertions
+ assertNotNull(inputImage, "Image should be retrieved successfully.");
+ assertEquals(width, inputImage.getWidth(), "Width should match the input image.");
+ assertEquals(height, inputImage.getHeight(), "Height should match the input image.");
+ assertEquals(depth, inputImage.getDepth(), "Depth should match the input image.");
+ assertDoesNotThrow(() -> {
+ float gain = inputImage.getGain();
+ float sigma = inputImage.getSigma();
+ float offset = inputImage.getOffset();
+ }, "Gain, sigma, and offset calculations should not throw exceptions.");
+
+ // Clean up ImageJ windows after the test
+ imagePlus.changes = false;
+ imagePlus.close();
+ }
+ }
+
+
+ @Nested
+ class GetInputImage3DOverloadTest {
+
+ @Test
+ void testGetInputImage3DWithoutNoiseStabilizationOrNormalization() {
+ // Setup: 3D image data (2x2x3)
+ int width = 2;
+ int height = 2;
+ int depth = 3;
+ float[] imageArray = new float[] {
+ 1.0f, 2.0f, 3.0f, 4.0f, // Slice 1
+ 5.0f, 6.0f, 7.0f, 8.0f, // Slice 2
+ 9.0f, 10.0f, 11.0f, 12.0f // Slice 3
+ };
+
+ // Test without stabilization or normalization
+ boolean stabiliseNoiseVariance = false;
+ boolean normalizeOutput = false;
+
+ // Run the method
+ Utils.InputImage3D inputImage = Utils.getInputImage3D(imageArray, width, height, depth, stabiliseNoiseVariance, normalizeOutput);
+
+ // Assert that the image was returned correctly
+ assertNotNull(inputImage, "Image should be retrieved successfully.");
+ assertEquals(width, inputImage.getWidth());
+ assertEquals(height, inputImage.getHeight());
+ assertEquals(depth, inputImage.getDepth());
+
+ // Check that gain, sigma, and offset are still 0.0f
+ assertEquals(0.0f, inputImage.getGain(), 0.0001f);
+ assertEquals(0.0f, inputImage.getSigma(), 0.0001f);
+ assertEquals(0.0f, inputImage.getOffset(), 0.0001f);
+ }
+
+ @Test
+ void testGetInputImage3DWithNormalization() {
+ // Setup: 3D image data (2x2x3)
+ int width = 2;
+ int height = 2;
+ int depth = 3;
+ float[] imageArray = new float[] {
+ 1.0f, 2.0f, 3.0f, 4.0f, // Slice 1
+ 5.0f, 6.0f, 7.0f, 8.0f, // Slice 2
+ 9.0f, 10.0f, 11.0f, 12.0f // Slice 3
+ };
+
+ // Test with normalization but no noise stabilization
+ boolean stabiliseNoiseVariance = false;
+ boolean normalizeOutput = true;
+
+ // Expected normalized array (min = 1.0, max = 12.0)
+ float[] expectedArray = new float[] {
+ 0.0f, 0.090f, 0.181f, 0.272f,
+ 0.363f, 0.454f, 0.545f, 0.636f,
+ 0.727f, 0.818f, 0.909f, 1.0f
+ };
+
+ // Run the method
+ Utils.InputImage3D inputImage = Utils.getInputImage3D(imageArray, width, height, depth, stabiliseNoiseVariance, normalizeOutput);
+
+ // Assert that the image was returned correctly
+ assertNotNull(inputImage, "Image should be retrieved successfully.");
+ assertArrayEquals(expectedArray, inputImage.getImageArray(), 0.001f);
+ }
+ }
+
+
+ @Nested
+ class GetMeanAndVarBlock3DTest {
+
+ @Test
+ void testGetMeanAndVarBlock3D() {
+ // Setup: create a 3D image with known values (4x4x4)
+ int imageWidth = 4;
+ int imageHeight = 4;
+ int depth = 4;
+ float[] pixels = new float[] {
+ // Slice 1 (z=0)
+ 1.0f, 2.0f, 3.0f, 4.0f,
+ 5.0f, 6.0f, 7.0f, 8.0f,
+ 9.0f, 10.0f, 11.0f, 12.0f,
+ 13.0f, 14.0f, 15.0f, 16.0f,
+ // Slice 2 (z=1)
+ 17.0f, 18.0f, 19.0f, 20.0f,
+ 21.0f, 22.0f, 23.0f, 24.0f,
+ 25.0f, 26.0f, 27.0f, 28.0f,
+ 29.0f, 30.0f, 31.0f, 32.0f,
+ // Slice 3 (z=2)
+ 33.0f, 34.0f, 35.0f, 36.0f,
+ 37.0f, 38.0f, 39.0f, 40.0f,
+ 41.0f, 42.0f, 43.0f, 44.0f,
+ 45.0f, 46.0f, 47.0f, 48.0f,
+ // Slice 4 (z=3)
+ 49.0f, 50.0f, 51.0f, 52.0f,
+ 53.0f, 54.0f, 55.0f, 56.0f,
+ 57.0f, 58.0f, 59.0f, 60.0f,
+ 61.0f, 62.0f, 63.0f, 64.0f
+ };
+
+ // Define block coordinates (xStart, yStart, zStart, xEnd, yEnd, zEnd)
+ int xStart = 1, yStart = 1, zStart = 1;
+ int xEnd = 3, yEnd = 3, zEnd = 3; // This block is a 2x2x2 block from slice 2-3
+
+ // Expected mean and variance for the 2x2x2 block
+ float expectedMean = (22.0f + 23.0f + 26.0f + 27.0f + 38.0f + 39.0f + 42.0f + 43.0f) / 8.0f;
+ float expectedVariance = (float) (Math.pow(22.0 - expectedMean, 2) + Math.pow(23.0 - expectedMean, 2)
+ + Math.pow(26.0 - expectedMean, 2) + Math.pow(27.0 - expectedMean, 2)
+ + Math.pow(38.0 - expectedMean, 2) + Math.pow(39.0 - expectedMean, 2)
+ + Math.pow(42.0 - expectedMean, 2) + Math.pow(43.0 - expectedMean, 2)) / 8.0f;
+
+ // Run the method
+ float[] result = Utils.getMeanAndVarBlock3D(pixels, imageWidth, imageHeight, xStart, yStart, zStart, xEnd, yEnd, zEnd);
+
+ // Assert the mean and variance are correct
+ assertEquals(expectedMean, result[0], 0.0001f, "Mean should match expected value.");
+ assertEquals(expectedVariance, result[1], 0.0001f, "Variance should match expected value.");
+ }
+ }
+
+
+ @Nested
+ class GetNoiseMeanVar3DTest {
+
+ @Test
+ void testGetMeanNoiseVar3D() {
+ // Setup: define dimensions and localStds array
+ int imageWidth = 5;
+ int imageHeight = 5;
+ int imageDepth = 5;
+ int blockRadiusWidth = 1;
+ int blockRadiusHeight = 1;
+ int blockRadiusDepth = 1;
+
+ // Create a known localStds array (5x5x5), representing standard deviations at each pixel
+ float[] localStds = new float[] {
+ // Slice 1
+ 1.0f, 1.2f, 1.1f, 1.3f, 1.0f,
+ 1.1f, 1.3f, 1.2f, 1.3f, 1.0f,
+ 1.2f, 1.1f, 1.0f, 1.2f, 1.1f,
+ 1.3f, 1.1f, 1.3f, 1.2f, 1.0f,
+ 1.0f, 1.2f, 1.1f, 1.0f, 1.3f,
+ // Slice 2
+ 1.0f, 1.1f, 1.2f, 1.3f, 1.1f,
+ 1.2f, 1.3f, 1.0f, 1.3f, 1.1f,
+ 1.3f, 1.2f, 1.1f, 1.2f, 1.3f,
+ 1.1f, 1.3f, 1.2f, 1.1f, 1.0f,
+ 1.2f, 1.1f, 1.3f, 1.1f, 1.2f,
+ // Slice 3
+ 1.1f, 1.2f, 1.3f, 1.0f, 1.2f,
+ 1.3f, 1.2f, 1.1f, 1.0f, 1.3f,
+ 1.1f, 1.3f, 1.2f, 1.0f, 1.1f,
+ 1.2f, 1.0f, 1.1f, 1.2f, 1.3f,
+ 1.3f, 1.2f, 1.1f, 1.3f, 1.2f,
+ // Slice 4
+ 1.1f, 1.3f, 1.2f, 1.0f, 1.2f,
+ 1.0f, 1.3f, 1.1f, 1.3f, 1.2f,
+ 1.3f, 1.1f, 1.2f, 1.1f, 1.0f,
+ 1.2f, 1.0f, 1.3f, 1.1f, 1.2f,
+ 1.1f, 1.2f, 1.0f, 1.2f, 1.3f,
+ // Slice 5
+ 1.3f, 1.0f, 1.1f, 1.3f, 1.2f,
+ 1.0f, 1.1f, 1.2f, 1.1f, 1.3f,
+ 1.3f, 1.2f, 1.0f, 1.1f, 1.2f,
+ 1.0f, 1.3f, 1.1f, 1.2f, 1.1f,
+ 1.2f, 1.3f, 1.0f, 1.1f, 1.0f
+ };
+
+ float expectedMeanNoiseVar = 1.355f; // Example expected result
+
+ // Run the method
+ float result = Utils.getMeanNoiseVar3D(imageWidth, imageHeight, imageDepth, blockRadiusWidth, blockRadiusHeight, blockRadiusDepth, localStds);
+
+ // Assert the result is correct
+ assertEquals(expectedMeanNoiseVar, result, 0.001f, "Mean noise variance should match expected value.");
+ }
+ }
+
+
+ @Nested
+ class GetRelevanceMask3DTest {
+
+ @Test
+ void testGetRelevanceMask3D() {
+ // Setup: define dimensions and input parameters
+ int imageWidth = 5;
+ int imageHeight = 5;
+ int imageDepth = 5;
+ int blockRadiusWidth = 1;
+ int blockRadiusHeight = 1;
+ int blockRadiusDepth = 1;
+ float relevanceConstant = 1.0f;
+
+ // Create a known localStds array (5x5x5), representing standard deviations at each pixel
+ float[] localStds = new float[] {
+ // Slice 1
+ 1.0f, 1.2f, 1.1f, 1.3f, 1.0f,
+ 1.1f, 1.3f, 1.2f, 1.3f, 1.0f,
+ 1.2f, 1.1f, 1.0f, 1.2f, 1.1f,
+ 1.3f, 1.1f, 1.3f, 1.2f, 1.0f,
+ 1.0f, 1.2f, 1.1f, 1.0f, 1.3f,
+ // Slice 2
+ 1.0f, 1.1f, 1.2f, 1.3f, 1.1f,
+ 1.2f, 1.3f, 1.0f, 1.3f, 1.1f,
+ 1.3f, 1.2f, 1.1f, 1.2f, 1.3f,
+ 1.1f, 1.3f, 1.2f, 1.1f, 1.0f,
+ 1.2f, 1.1f, 1.3f, 1.1f, 1.2f,
+ // Slice 3
+ 1.1f, 1.2f, 1.3f, 1.0f, 1.2f,
+ 1.3f, 1.2f, 1.1f, 1.0f, 1.3f,
+ 1.1f, 1.3f, 1.2f, 1.0f, 1.1f,
+ 1.2f, 1.0f, 1.1f, 1.2f, 1.3f,
+ 1.3f, 1.2f, 1.1f, 1.3f, 1.2f,
+ // Slice 4
+ 1.1f, 1.3f, 1.2f, 1.0f, 1.2f,
+ 1.0f, 1.3f, 1.1f, 1.3f, 1.2f,
+ 1.3f, 1.1f, 1.2f, 1.1f, 1.0f,
+ 1.2f, 1.0f, 1.3f, 1.1f, 1.2f,
+ 1.1f, 1.2f, 1.0f, 1.2f, 1.3f,
+ // Slice 5
+ 1.3f, 1.0f, 1.1f, 1.3f, 1.2f,
+ 1.0f, 1.1f, 1.2f, 1.1f, 1.3f,
+ 1.3f, 1.2f, 1.0f, 1.1f, 1.2f,
+ 1.0f, 1.3f, 1.1f, 1.2f, 1.1f,
+ 1.2f, 1.3f, 1.0f, 1.1f, 1.0f
+ };
+
+ // Calculate expected mean noise variance
+ float expectedMeanNoiseVariance = Utils.getMeanNoiseVar3D(imageWidth, imageHeight, imageDepth, blockRadiusWidth,
+ blockRadiusHeight, blockRadiusDepth, localStds);
+
+ // Calculate the relevance mask
+ float[] relevanceMask = Utils.getRelevanceMask3D(imageWidth, imageHeight, imageDepth, blockRadiusWidth,
+ blockRadiusHeight, blockRadiusDepth, localStds, relevanceConstant);
+
+ // Verify the mask values
+ for (int z = blockRadiusDepth; z < imageDepth - blockRadiusDepth; z++) {
+ for (int y = blockRadiusHeight; y < imageHeight - blockRadiusHeight; y++) {
+ for (int x = blockRadiusWidth; x < imageWidth - blockRadiusWidth; x++) {
+ int index = imageWidth * imageHeight * z + y * imageWidth + x;
+ float localVariance = localStds[index] * localStds[index];
+ if (localVariance <= expectedMeanNoiseVariance * relevanceConstant) {
+ assertEquals(0.0f, relevanceMask[index], "Relevance mask value at (" + x + ", " + y + ", " + z + ") should be 0.0");
+ } else {
+ assertEquals(1.0f, relevanceMask[index], "Relevance mask value at (" + x + ", " + y + ", " + z + ") should be 1.0");
+ }
+ }
+ }
+ }
+ }
+ }
+
+
+ @Nested
+ class ApplyMask3DTest {
+
+ @Test
+ void testApplyMask3D() {
+ // Setup: Define dimensions and initialize a test image and mask
+ int imageWidth = 3;
+ int imageHeight = 3;
+ int imageDepth = 3;
+
+ // Create a sample 3D image (3x3x3)
+ float[] imageArray = new float[] {
+ // Slice 1
+ 1.0f, 2.0f, 3.0f,
+ 4.0f, 5.0f, 6.0f,
+ 7.0f, 8.0f, 9.0f,
+ // Slice 2
+ 10.0f, 11.0f, 12.0f,
+ 13.0f, 14.0f, 15.0f,
+ 16.0f, 17.0f, 18.0f,
+ // Slice 3
+ 19.0f, 20.0f, 21.0f,
+ 22.0f, 23.0f, 24.0f,
+ 25.0f, 26.0f, 27.0f
+ };
+
+ // Create a mask (1.0f for keep, 0.0f for remove)
+ float[] mask = new float[] {
+ // Slice 1
+ 1.0f, 0.0f, 1.0f,
+ 1.0f, 1.0f, 0.0f,
+ 0.0f, 1.0f, 1.0f,
+ // Slice 2
+ 0.0f, 1.0f, 0.0f,
+ 1.0f, 0.0f, 1.0f,
+ 1.0f, 0.0f, 0.0f,
+ // Slice 3
+ 1.0f, 0.0f, 1.0f,
+ 0.0f, 1.0f, 0.0f,
+ 1.0f, 1.0f, 1.0f
+ };
+
+ // Expected result after applying mask
+ float[] expectedImageArray = new float[] {
+ // Slice 1
+ 1.0f, 0.0f, 3.0f,
+ 4.0f, 5.0f, 0.0f,
+ 0.0f, 8.0f, 9.0f,
+ // Slice 2
+ 0.0f, 11.0f, 0.0f,
+ 13.0f, 0.0f, 15.0f,
+ 16.0f, 0.0f, 0.0f,
+ // Slice 3
+ 19.0f, 0.0f, 21.0f,
+ 0.0f, 23.0f, 0.0f,
+ 25.0f, 26.0f, 27.0f
+ };
+
+ // Apply the mask
+ float[] resultImageArray = Utils.applyMask3D(imageArray, imageWidth, imageHeight, imageDepth, mask);
+
+ // Check that the result matches the expected result
+ assertArrayEquals(expectedImageArray, resultImageArray, "The masked image array does not match the expected output.");
+ }
+ }
+
+
+ @Nested
+ class NormalizeImage3DTest {
+
+
+ @Test
+ void testNormalizeImage3DWithoutMask() {
+ // Setup: Define dimensions and initialize a test image
+ int imageWidth = 3;
+ int imageHeight = 3;
+ int imageDepth = 3;
+
+ // Create a sample 3D image (3x3x3)
+ float[] imageArray = new float[] {
+ // Slice 1
+ 1.0f, 2.0f, 3.0f,
+ 4.0f, 5.0f, 6.0f,
+ 7.0f, 8.0f, 9.0f,
+ // Slice 2
+ 10.0f, 11.0f, 12.0f,
+ 13.0f, 14.0f, 15.0f,
+ 16.0f, 17.0f, 18.0f,
+ // Slice 3
+ 19.0f, 20.0f, 21.0f,
+ 22.0f, 23.0f, 24.0f,
+ 25.0f, 26.0f, 27.0f
+ };
+
+ // Define border dimensions
+ int borderWidth = 0;
+ int borderHeight = 0;
+ int borderDepth = 0;
+
+ // Expected result after normalization (values remapped to [0, 1])
+ float[] expectedNormalizedArray = new float[] {
+ // Slice 1
+ 0.0f, 0.03846154f, 0.07692308f,
+ 0.115384616f, 0.15384616f, 0.1923077f,
+ 0.23076923f, 0.26923078f, 0.30769232f,
+ // Slice 2
+ 0.34615386f, 0.3846154f, 0.42307693f,
+ 0.46153846f, 0.5f, 0.53846157f,
+ 0.5769231f, 0.61538464f, 0.65384614f,
+ // Slice 3
+ 0.6923077f, 0.7307692f, 0.7692308f,
+ 0.8076923f, 0.84615386f, 0.88461536f,
+ 0.9230769f, 0.96153843f, 1.0f
+ };
+
+ // Normalize the image without mask
+ float[] normalizedImage = Utils.normalizeImage3D(imageArray, imageWidth, imageHeight, imageDepth, borderWidth, borderHeight, borderDepth, null);
+
+ // Check that the result matches the expected result
+ assertArrayEquals(expectedNormalizedArray, normalizedImage, Utils.EPSILON, "The normalized image array does not match the expected output.");
+ }
+
+ @Test
+ void testNormalizeImage3DWithMask() {
+ // Setup: Define dimensions and initialize a test image with a mask
+ int imageWidth = 3;
+ int imageHeight = 3;
+ int imageDepth = 3;
+
+ // Create a sample 3D image (3x3x3)
+ float[] imageArray = new float[] {
+ // Slice 1
+ 1.0f, 2.0f, 3.0f,
+ 4.0f, 5.0f, 6.0f,
+ 7.0f, 8.0f, 9.0f,
+ // Slice 2
+ 10.0f, 11.0f, 12.0f,
+ 13.0f, 14.0f, 15.0f,
+ 16.0f, 17.0f, 18.0f,
+ // Slice 3
+ 19.0f, 20.0f, 21.0f,
+ 22.0f, 23.0f, 24.0f,
+ 25.0f, 26.0f, 27.0f
+ };
+
+ // Create a mask (1.0f for keep, 0.0f for remove)
+ float[] mask = new float[] {
+ // Slice 1
+ 1.0f, 1.0f, 1.0f,
+ 1.0f, 1.0f, 1.0f,
+ 1.0f, 1.0f, 1.0f,
+ // Slice 2
+ 1.0f, 1.0f, 1.0f,
+ 1.0f, 1.0f, 1.0f,
+ 1.0f, 1.0f, 1.0f,
+ // Slice 3
+ 1.0f, 1.0f, 1.0f,
+ 1.0f, 1.0f, 1.0f,
+ 1.0f, 1.0f, 1.0f
+ };
+
+ // Define border dimensions
+ int borderWidth = 0;
+ int borderHeight = 0;
+ int borderDepth = 0;
+
+ // Expected result after normalization (same as without mask, since all values are included)
+ float[] expectedNormalizedArray = new float[] {
+ // Slice 1
+ 0.0f, 0.03846154f, 0.07692308f,
+ 0.115384616f, 0.15384616f, 0.1923077f,
+ 0.23076923f, 0.26923078f, 0.30769232f,
+ // Slice 2
+ 0.34615386f, 0.3846154f, 0.42307693f,
+ 0.46153846f, 0.5f, 0.53846157f,
+ 0.5769231f, 0.61538464f, 0.65384614f,
+ // Slice 3
+ 0.6923077f, 0.7307692f, 0.7692308f,
+ 0.8076923f, 0.84615386f, 0.88461536f,
+ 0.9230769f, 0.96153843f, 1.0f
+ };
+
+ // Normalize the image with mask
+ float[] normalizedImage = Utils.normalizeImage3D(imageArray, imageWidth, imageHeight, imageDepth, borderWidth, borderHeight, borderDepth, mask);
+
+ // Check that the result matches the expected result
+ assertArrayEquals(expectedNormalizedArray, normalizedImage, Utils.EPSILON, "The normalized image array with mask does not match the expected output.");
+ }
+
+ @Test
+ void testNormalizeImage3DWithMaskAndBorders() {
+ // Setup: Define dimensions and initialize a test image with a mask
+ int imageWidth = 3;
+ int imageHeight = 3;
+ int imageDepth = 3;
+
+ // Create a sample 3D image (3x3x3)
+ float[] imageArray = new float[] {
+ // Slice 1
+ 1.0f, 2.0f, 3.0f,
+ 4.0f, 5.0f, 6.0f,
+ 7.0f, 8.0f, 9.0f,
+ // Slice 2
+ 10.0f, 11.0f, 12.0f,
+ 13.0f, 14.0f, 15.0f,
+ 16.0f, 17.0f, 18.0f,
+ // Slice 3
+ 19.0f, 20.0f, 21.0f,
+ 22.0f, 23.0f, 24.0f,
+ 25.0f, 26.0f, 27.0f
+ };
+
+ // Create a mask (1.0f for keep, 0.0f for remove)
+ float[] mask = new float[] {
+ // Slice 1
+ 1.0f, 1.0f, 1.0f,
+ 1.0f, 1.0f, 1.0f,
+ 1.0f, 1.0f, 1.0f,
+ // Slice 2
+ 1.0f, 1.0f, 1.0f,
+ 1.0f, 1.0f, 1.0f,
+ 1.0f, 1.0f, 1.0f,
+ // Slice 3
+ 1.0f, 1.0f, 1.0f,
+ 1.0f, 1.0f, 1.0f,
+ 1.0f, 1.0f, 1.0f
+ };
+
+ // Define border dimensions
+ int borderWidth = 1;
+ int borderHeight = 1;
+ int borderDepth = 1;
+
+ // Expected result after normalization (with borders ignored)
+ float[] expectedNormalizedArray = new float[] {
+ // Slice 1
+ 1.0f, 2.0f, 3.0f,
+ 4.0f, 5.0f, 6.0f,
+ 7.0f, 8.0f, 9.0f,
+ // Slice 2
+ 10.0f, 11.0f, 12.0f,
+ 13.0f, 0.0f, 15.0f,
+ 16.0f, 17.0f, 18.0f,
+ // Slice 3
+ 19.0f, 20.0f, 21.0f,
+ 22.0f, 23.0f, 24.0f,
+ 25.0f, 26.0f, 27.0f
+ };
+
+ // Normalize the image with mask and borders
+ float[] normalizedImage = Utils.normalizeImage3D(imageArray, imageWidth, imageHeight, imageDepth, borderWidth, borderHeight, borderDepth, mask);
+
+ // Check that the result matches the expected result
+ assertArrayEquals(expectedNormalizedArray, normalizedImage, Utils.EPSILON, "The normalized image array with mask and borders does not match the expected output.");
+ }
+ }
+
+
+ // -------------------------- //
+ // ---- UNSORTED METHODS ---- //
+ // -------------------------- //
+
+ @Nested
+ class NormalizeArrayTest {
+
+ @Test
+ void testNormalizeArray() {
+ // Setup: Create a sample array
+ float[] inputArray = new float[] {3.0f, 5.0f, 1.0f, 8.0f, 2.0f};
+
+ // Expected normalized output based on the min (1.0f) and max (8.0f)
+ float[] expectedNormalizedArray = new float[] {
+ (3.0f - 1.0f) / (8.0f - 1.0f + Utils.EPSILON), // ≈ 0.25
+ (5.0f - 1.0f) / (8.0f - 1.0f + Utils.EPSILON), // ≈ 0.50
+ (1.0f - 1.0f) / (8.0f - 1.0f + Utils.EPSILON), // 0.0
+ (8.0f - 1.0f) / (8.0f - 1.0f + Utils.EPSILON), // 1.0
+ (2.0f - 1.0f) / (8.0f - 1.0f + Utils.EPSILON) // ≈ 0.125
+ };
+
+ // Normalize the array
+ float[] normalizedArray = Utils.normalizeArray(inputArray);
+
+ // Check that the result matches the expected result
+ assertArrayEquals(expectedNormalizedArray, normalizedArray, Utils.EPSILON, "The normalized array does not match the expected output.");
+ }
+
+ @Test
+ void testNormalizeArraySingleValue() {
+ // Test with an array of a single value
+ float[] inputArray = new float[] {5.0f};
+
+ // Normalizing a single value should return an array with the same value
+ float[] expectedNormalizedArray = new float[] {0.0f}; // Edge case
+
+ // Normalize the array
+ float[] normalizedArray = Utils.normalizeArray(inputArray);
+
+ // Check that the result matches the expected result
+ assertArrayEquals(expectedNormalizedArray, normalizedArray, Utils.EPSILON, "The normalized array with a single value does not match the expected output.");
+ }
+
+ @Test
+ void testNormalizeArrayWithNegativeValues() {
+ // Setup: Create a sample array with negative values
+ float[] inputArray = new float[] {-3.0f, -5.0f, -1.0f, -8.0f, -2.0f};
+
+ // Find min and max
+ float arrMin = Float.MAX_VALUE;
+ float arrMax = -Float.MAX_VALUE;
+ for(int i=0; i< inputArray.length; i++){
+ arrMin = min(arrMin, inputArray[i]);
+ arrMax = max(arrMax, inputArray[i]);
+ }
+
+ // Calculate expected normalized array
+ float[] expectedNormalizedArray = {
+ (inputArray[0]-arrMin)/(arrMax-arrMin+Utils.EPSILON),
+ (inputArray[1]-arrMin)/(arrMax-arrMin+Utils.EPSILON),
+ (inputArray[2]-arrMin)/(arrMax-arrMin+Utils.EPSILON),
+ (inputArray[3]-arrMin)/(arrMax-arrMin+Utils.EPSILON),
+ (inputArray[4]-arrMin)/(arrMax-arrMin+Utils.EPSILON),
+ };
+
+ // Normalize the array
+ float[] normalizedArray = Utils.normalizeArray(inputArray);
+
+ // Check that the result matches the expected result
+ assertArrayEquals(expectedNormalizedArray, normalizedArray, Utils.EPSILON, "The normalized array with negative values does not match the expected output.");
+ }
+ }
+
+
+ @Nested
+ class GetImageIDByTitleTest {
+
+ @Test
+ void testGetImageIDByTitle() {
+ String[] titles = {"Image1", "Image2", "Image3"};
+ int[] ids = {101, 102, 103};
+
+ // Test valid title
+ assertEquals(101, Utils.getImageIDByTitle(titles, ids, "Image1"));
+ assertEquals(102, Utils.getImageIDByTitle(titles, ids, "Image2"));
+ assertEquals(103, Utils.getImageIDByTitle(titles, ids, "Image3"));
+ }
+
+ @Test
+ void testGetImageIDByTitleNotFound() {
+ String[] titles = {"Image1", "Image2", "Image3"};
+ int[] ids = {101, 102, 103};
+
+ // Test title not found
+ Exception exception = assertThrows(IllegalArgumentException.class, () -> {
+ Utils.getImageIDByTitle(titles, ids, "NonExistentImage");
+ });
+
+ String expectedMessage = "Title not found: NonExistentImage";
+ String actualMessage = exception.getMessage();
+ assertTrue(actualMessage.contains(expectedMessage));
+ }
+ }
+
+
+ @Nested
+ class DisplayResults2DTest {
+
+ @Test
+ void testDisplayResults2D() {
+ // Prepare a mock InputImage2D
+ int width = 100;
+ int height = 100;
+ int size = width*height;
+ float[] repetitionMap = new float[size];
+
+ // Fill repetition map with sample values
+ for (int i = 0; i < repetitionMap.length; i++) {
+ repetitionMap[i] = (float) i; // Sample values for the test
+ }
+ Utils.InputImage2D inputImage = new Utils.InputImage2D(repetitionMap, width, height, size);
+
+ // Call the method to display results
+ Utils.displayResults2D(inputImage, repetitionMap);
+
+ // Check if the image is displayed
+ ImagePlus displayedImage = WindowManager.getCurrentImage();
+ assertNotNull(displayedImage, "Image should be displayed.");
+
+ // Verify the image properties
+ assertEquals(width, displayedImage.getWidth(), "Width should match the input image width.");
+ assertEquals(height, displayedImage.getHeight(), "Height should match the input image height.");
+
+ // Check that the FloatProcessor is set up correctly
+ FloatProcessor fp = (FloatProcessor) displayedImage.getProcessor();
+ assertEquals(repetitionMap.length, fp.getPixelCount(), "Pixel count should match the repetition map size.");
+ }
+ }
+
+
+ @Nested
+ class DisplayResults3DTest {
+
+ @Test
+ public void testDisplayResults3D() {
+ // Prepare test data
+ int width = 5;
+ int height = 5;
+ int depth = 3;
+ float[] repetitionMap = new float[width * height * depth];
+ for (int z = 0; z < depth; z++) {
+ for (int y = 0; y < height; y++) {
+ for (int x = 0; x < width; x++) {
+ repetitionMap[width * height * z + y * width + x] = (float) (Math.random() * 100);
+ }
+ }
+ }
+
+ // Create calibration (as needed by InputImage3D)
+ Calibration calibration = new Calibration();
+ // Set any necessary calibration properties here if needed
+
+ // Create InputImage3D with all required parameters
+ Utils.InputImage3D inputImage = new Utils.InputImage3D(repetitionMap, width, height, depth, repetitionMap.length, calibration, 0.0f, 0.0f, 0.0f);
+
+ // Call the method under test
+ Utils.displayResults3D(inputImage, repetitionMap);
+
+ // Check if the ImagePlus was created and is not null
+ ImagePlus imp = WindowManager.getCurrentImage();
+ assertNotNull(imp, "The ImagePlus should be created and not null.");
+
+ // Check that the image stack has the correct dimensions
+ assertEquals(depth, imp.getStackSize(), "The image stack should have the correct depth.");
+ assertEquals(width, imp.getWidth(), "The image width should match the input width.");
+ assertEquals(height, imp.getHeight(), "The image height should match the input height.");
+
+ // Check if the LUT was applied correctly (this is more complex to test, you might need to validate visually or check properties)
+ // Additional assertions regarding the LUT can be added based on your requirements
+
+ // Clean up - close the image if you want to avoid clutter in your test suite
+ imp.close();
+ }
+ }
+}