From 3fdd3cec696cb6cd6a452374d65bc425a75b6c97 Mon Sep 17 00:00:00 2001 From: MichaelSNelson Date: Thu, 11 Jan 2024 12:40:50 -0600 Subject: [PATCH] Formatting improvements - learning to use IntelliJ --- .github/workflows/gradle.yml | 34 ++--- README.md | 1 + build.gradle | 26 ++-- .../qupath/ext/qp_scope/QP_scope.groovy | 130 +++++++++--------- .../qp_scope/functions/QP_scope_GUI.groovy | 65 ++++----- .../utilities/utilityFunctions.groovy | 71 +++++----- .../groovyScripts/save4xMacroTiling.groovy | 61 ++++---- src/main/groovyScripts/saveTilingCSV.groovy | 113 ++++++++------- 8 files changed, 247 insertions(+), 254 deletions(-) diff --git a/.github/workflows/gradle.yml b/.github/workflows/gradle.yml index 13b0e91..7d3ffe2 100644 --- a/.github/workflows/gradle.yml +++ b/.github/workflows/gradle.yml @@ -23,20 +23,20 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 - - name: Set up JDK 11 - uses: actions/setup-java@v3 - with: - java-version: '11' - distribution: 'temurin' - - name: Validate Gradle wrapper - uses: gradle/wrapper-validation-action@e6e38bacfdf1a337459f332974bb2327a31aaf4b - - name: Build with Gradle - uses: gradle/gradle-build-action@67421db6bd0bf253fb4bd25b31ebb98943c375e1 - with: - arguments: build - - uses: actions/upload-artifact@v3 - with: - name: jar - path: build/libs - retention-days: 7 + - uses: actions/checkout@v3 + - name: Set up JDK 11 + uses: actions/setup-java@v3 + with: + java-version: '11' + distribution: 'temurin' + - name: Validate Gradle wrapper + uses: gradle/wrapper-validation-action@e6e38bacfdf1a337459f332974bb2327a31aaf4b + - name: Build with Gradle + uses: gradle/gradle-build-action@67421db6bd0bf253fb4bd25b31ebb98943c375e1 + with: + arguments: build + - uses: actions/upload-artifact@v3 + with: + name: jar + path: build/libs + retention-days: 7 diff --git a/README.md b/README.md index d420717..40c67ef 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,3 @@ # qp_scope + Controlling a microscope through some sort of python control interface (Pycromanager) and MicroManager diff --git a/build.gradle b/build.gradle index b802e83..1a13aba 100644 --- a/build.gradle +++ b/build.gradle @@ -1,12 +1,12 @@ plugins { - // Main gradle plugin for building a Java library - //id 'java-library' - // Support writing the extension in Groovy (remove this if you don't want to) - id 'groovy' - // To create a shadow/fat jar that bundle up all dependencies - id 'com.github.johnrengelman.shadow' version '7.1.2' - // Include this plugin to avoid downloading JavaCPP dependencies for all platforms - id 'org.bytedeco.gradle-javacpp-platform' + // Main gradle plugin for building a Java library + //id 'java-library' + // Support writing the extension in Groovy (remove this if you don't want to) + id 'groovy' + // To create a shadow/fat jar that bundle up all dependencies + id 'com.github.johnrengelman.shadow' version '7.1.2' + // Include this plugin to avoid downloading JavaCPP dependencies for all platforms + id 'org.bytedeco.gradle-javacpp-platform' } //the module name @@ -73,9 +73,9 @@ jar { * Copy the LICENSE file into the jar... if we have one (we should!) */ processResources { - from ("${projectDir}/LICENSE") { - into 'licenses/' - } + from("${projectDir}/LICENSE") { + into 'licenses/' + } } /* @@ -116,7 +116,7 @@ tasks.withType(Javadoc).configureEach { * Specify that the encoding should be UTF-8 for source files */ tasks.named('compileJava') { - options.encoding = 'UTF-8' + options.encoding = 'UTF-8' } /* @@ -141,7 +141,7 @@ repositories { // Add this if you need access to dependencies only installed locally mavenLocal() - flatDir{ + flatDir { dirs 'C:/ImageAnalysis/QPExtensionTest/BasicStitching/build/libs' } diff --git a/src/main/groovy/qupath/ext/qp_scope/QP_scope.groovy b/src/main/groovy/qupath/ext/qp_scope/QP_scope.groovy index 4f4d010..53bcadb 100644 --- a/src/main/groovy/qupath/ext/qp_scope/QP_scope.groovy +++ b/src/main/groovy/qupath/ext/qp_scope/QP_scope.groovy @@ -1,92 +1,92 @@ -package qupath.ext.qp_scope; +package qupath.ext.qp_scope import javafx.scene.control.MenuItem -import qupath.lib.common.Version; -import qupath.lib.gui.QuPathGUI -import qupath.lib.gui.extensions.QuPathExtension; -import qupath.ext.qp_scope.functions.* import org.slf4j.LoggerFactory +import qupath.ext.qp_scope.functions.QP_scope_GUI +import qupath.lib.common.Version +import qupath.lib.gui.QuPathGUI +import qupath.lib.gui.extensions.QuPathExtension /** * Built from the QuPath extension template - an extension to control a microscope through a Python interface */ class QP_scope implements QuPathExtension { - // Setting the variables here is enough for them to be available in the extension - String name = "Microscopy in QuPath" - String description = "Interact with a microscope from QuPath via Python interfaces like PycroManager or PyMMCore" - Version QuPathVersion = Version.parse("v0.4.4") + // Setting the variables here is enough for them to be available in the extension + String name = "Microscopy in QuPath" + String description = "Interact with a microscope from QuPath via Python interfaces like PycroManager or PyMMCore" + Version QuPathVersion = Version.parse("v0.4.4") // @Override // void installExtension(QuPathGUI qupath) { // qupath.installActions(ActionTools.getAnnotatedActions(new BSCommands(qupath))) // addMenuItem(qupath) // } - @Override - void installExtension(QuPathGUI qupath) { - addMenuItem(qupath) + @Override + void installExtension(QuPathGUI qupath) { + addMenuItem(qupath) + + } + /** + * Get the description of the extension. + * + * @return The description of the extension. + */ + @Override + public String getDescription() { + return "Control a microscope!"; + } - } - /** - * Get the description of the extension. - * - * @return The description of the extension. - */ - @Override - public String getDescription() { - return "Control a microscope!"; - } + /** + * Get the name of the extension. + * + * @return The name of the extension. + */ + @Override + public String getName() { + return "qp_scope"; + } - /** - * Get the name of the extension. - * - * @return The name of the extension. - */ - @Override - public String getName() { - return "qp_scope"; - } + private void addMenuItem(QuPathGUI qupath) { + def logger = LoggerFactory.getLogger(QuPathGUI.class) + // Check for dependencies and QuPath version + logger.info("QuPath Version") + logger.info(getQuPathVersion().toString()) + // TODO: how to check if version is supported? - private void addMenuItem(QuPathGUI qupath) { - def logger = LoggerFactory.getLogger(QuPathGUI.class) - // Check for dependencies and QuPath version - logger.info("QuPath Version") - logger.info(getQuPathVersion().toString()) - // TODO: how to check if version is supported? + // Get or create the menu + def menu = qupath.getMenu("Extensions>${name}", true) - // Get or create the menu - def menu = qupath.getMenu("Extensions>${name}", true) + // First menu item + def qpScope1 = new MenuItem("Start qp_scope") + // TODO: tooltip + qpScope1.setOnAction(e -> { + // TODO: check preferences for all necessary entries + QP_scope_GUI.createGUI1() + }) - // First menu item - def qpScope1 = new MenuItem("Start qp_scope") - // TODO: tooltip - qpScope1.setOnAction(e -> { - // TODO: check preferences for all necessary entries - QP_scope_GUI.createGUI1() - }) + // Second menu item + def qpScope2 = new MenuItem("Second scan on existing annotations") + // TODO: tooltip + qpScope2.setOnAction(e -> { + // TODO: check preferences for all necessary entries + QP_scope_GUI.createGUI2() + }) - // Second menu item - def qpScope2 = new MenuItem("Second scan on existing annotations") - // TODO: tooltip - qpScope2.setOnAction(e -> { - // TODO: check preferences for all necessary entries - QP_scope_GUI.createGUI2() - }) + // Third menu item - "Use current image as macro view" + def qpScope3 = new MenuItem("Use current image as macro view") + // TODO: tooltip + qpScope3.setOnAction(e -> { + QP_scope_GUI.createGUI3() + }) - // Third menu item - "Use current image as macro view" - def qpScope3 = new MenuItem("Use current image as macro view") - // TODO: tooltip - qpScope3.setOnAction(e -> { - QP_scope_GUI.createGUI3() - }) + // Add the menu items to the menu + menu.getItems() << qpScope1 + menu.getItems() << qpScope2 + menu.getItems() << qpScope3 + } - // Add the menu items to the menu - menu.getItems() << qpScope1 - menu.getItems() << qpScope2 - menu.getItems() << qpScope3 - } - } //@ActionMenu("Extensions") //public class BSCommands { diff --git a/src/main/groovy/qupath/ext/qp_scope/functions/QP_scope_GUI.groovy b/src/main/groovy/qupath/ext/qp_scope/functions/QP_scope_GUI.groovy index 9ba74e6..f56ec56 100644 --- a/src/main/groovy/qupath/ext/qp_scope/functions/QP_scope_GUI.groovy +++ b/src/main/groovy/qupath/ext/qp_scope/functions/QP_scope_GUI.groovy @@ -1,27 +1,18 @@ package qupath.ext.qp_scope.functions import javafx.scene.Node - -import javafx.scene.control.ButtonType -import javafx.scene.control.CheckBox -import javafx.scene.control.ChoiceDialog -import javafx.scene.control.Dialog -import javafx.scene.control.Label -import javafx.scene.control.TextField +import javafx.scene.control.* import javafx.scene.layout.GridPane import javafx.scene.layout.HBox - import javafx.stage.Modality import org.slf4j.LoggerFactory +import qupath.ext.basicstitching.stitching.stitchingImplementations import qupath.ext.qp_scope.utilities.utilityFunctions +import qupath.lib.gui.QuPathGUI import qupath.lib.gui.dialogs.Dialogs -import groovy.io.FileType -import qupath.lib.images.writers.ome.OMEPyramidWriter +import qupath.lib.gui.scripting.QPEx import qupath.lib.projects.Project -import qupath.lib.gui.QuPathGUI import qupath.lib.scripting.QP -import qupath.lib.gui.scripting.QPEx -import qupath.ext.basicstitching.stitching.stitchingImplementations import java.awt.image.BufferedImage import java.nio.file.Path @@ -53,7 +44,8 @@ class QP_scope_GUI { static TextField sampleLabelField = new TextField("First_Test") // New field for sample label // GUI3 static CheckBox slideFlippedCheckBox = new CheckBox("Slide is flipped") - static TextField groovyScriptField = new TextField("C:\\ImageAnalysis\\QPExtensionTest\\qp_scope\\src\\main\\groovyScripts/DetectTissue.groovy") // Default empty + static TextField groovyScriptField = new TextField("C:\\ImageAnalysis\\QPExtensionTest\\qp_scope\\src\\main\\groovyScripts/DetectTissue.groovy") + // Default empty static TextField pixelSizeField = new TextField("7.2") // Default empty static CheckBox nonIsotropicCheckBox = new CheckBox("Non-isotropic pixels") @@ -119,10 +111,10 @@ class QP_scope_GUI { } // Check if any value is empty - if (dataCheck){ - Project currentQuPathProject= utilityFunctions.createProjectFolder(projectsFolderPath, sampleLabel, preferences.firstScanType) + if (dataCheck) { + Project currentQuPathProject = utilityFunctions.createProjectFolder(projectsFolderPath, sampleLabel, preferences.firstScanType) def scanTypeWithIndex = utilityFunctions.getUniqueFolderName(projectsFolderPath + File.separator + sampleLabel + File.separator + preferences.firstScanType) - def tempTileDirectory = projectsFolderPath + File.separator + sampleLabel+File.separator+scanTypeWithIndex + def tempTileDirectory = projectsFolderPath + File.separator + sampleLabel + File.separator + scanTypeWithIndex def logger = LoggerFactory.getLogger(QuPathGUI.class) logger.info(tempTileDirectory) //Reduce the number of sent args @@ -134,7 +126,7 @@ class QP_scope_GUI { sampleLabel, scanTypeWithIndex, annotationJsonFileLocation, - boundingBox ] + boundingBox] //TODO can we create non-blocking python code utilityFunctions.runPythonCommand(virtualEnvPath, pythonScriptPath, args) @@ -262,15 +254,14 @@ class QP_scope_GUI { } - def scanTypeWithIndex = utilityFunctions.getUniqueFolderName(projectsFolderPath + File.separator + sampleLabel + File.separator + preferences.secondScanType) - def tempTileDirectory = projectsFolderPath + File.separator + sampleLabel+File.separator+scanTypeWithIndex + def tempTileDirectory = projectsFolderPath + File.separator + sampleLabel + File.separator + scanTypeWithIndex logger.info("Scan type with index: " + scanTypeWithIndex) logger.info(tempTileDirectory) logger.info("Creating json") annotationJsonFileLocation = utilityFunctions.createAnnotationJson(projectsFolderPath, sampleLabel, scanTypeWithIndex) - List args = [pythonScriptPath, projectsFolderPath, sampleLabel,scanTypeWithIndex, annotationJsonFileLocation] + List args = [pythonScriptPath, projectsFolderPath, sampleLabel, scanTypeWithIndex, annotationJsonFileLocation] //TODO how can we distinguish between a hung python run and one that is taking a long time? - possibly check for new files in target folder? utilityFunctions.runPythonCommand(virtualEnvPath, pythonScriptPath, args) //utilityFunctions.runPythonCommand(virtualEnvPath, "C:\\ImageAnalysis\\python\\py_dummydoc.py", args) @@ -281,7 +272,7 @@ class QP_scope_GUI { //stitchingImplementations.stitchCore(stitchingType, folderPath, compressionType, pixelSize, downsample, matchingString) logger.info("Begin stitching") - String stitchedImagePathStr =stitchingImplementations.stitchCore("Coordinates in TileConfiguration.txt file", projectsFolderPath + File.separator + sampleLabel, stitchedImageOutputFolder, "J2K_LOSSY", 0, 1, scanTypeWithIndex) + String stitchedImagePathStr = stitchingImplementations.stitchCore("Coordinates in TileConfiguration.txt file", projectsFolderPath + File.separator + sampleLabel, stitchedImageOutputFolder, "J2K_LOSSY", 0, 1, scanTypeWithIndex) logger.info("Get project") Project currentQuPathProject = getProject() @@ -405,9 +396,9 @@ class QP_scope_GUI { String exportScriptPathString = exportScriptPath.toString().replace("\\", "/"); //Create the QuPath project - Project currentQuPathProject= utilityFunctions.createProjectFolder(projectsFolderPath, sampleLabel, preferences.firstScanType) + Project currentQuPathProject = utilityFunctions.createProjectFolder(projectsFolderPath, sampleLabel, preferences.firstScanType) def scanTypeWithIndex = utilityFunctions.getUniqueFolderName(projectsFolderPath + File.separator + sampleLabel + File.separator + preferences.firstScanType) - def tempTileDirectory = projectsFolderPath + File.separator + sampleLabel+File.separator+scanTypeWithIndex + def tempTileDirectory = projectsFolderPath + File.separator + sampleLabel + File.separator + scanTypeWithIndex //Get the current image open in QuPath and add it to the project @@ -425,7 +416,7 @@ class QP_scope_GUI { //https://qupath.github.io/javadoc/docs/qupath/lib/gui/QuPathGUI.html#setProject(qupath.lib.projects.Project) def qupathGUI = QPEx.getQuPath() - utilityFunctions.addImageToProject( new File(macroImagePath), currentQuPathProject) + utilityFunctions.addImageToProject(new File(macroImagePath), currentQuPathProject) qupathGUI.setProject(currentQuPathProject) //Find the existing images - there should only be one since the project was just created def matchingImage = currentQuPathProject.getImageList().find { image -> @@ -446,7 +437,7 @@ class QP_scope_GUI { //Create an export tile locations String tilesCSVdirectory = projectsFolderPath + File.separator + sampleLabel + File.separator + "tiles_csv"; - String exportScript = utilityFunctions.modifyCSVExportScript(exportScriptPathString, pixelSize, tilesCSVdirectory) + String exportScript = utilityFunctions.modifyCSVExportScript(exportScriptPathString, pixelSize, tilesCSVdirectory) logger.info(exportScript) logger.info(tilesCSVdirectory) logger.info(exportScriptPathString) @@ -456,7 +447,7 @@ class QP_scope_GUI { //Dialog chain to validate stage location ////////////////////////////////////// // the transformation consists of an X-shift in stage microns, a Y-shift in stage microns, and a pixelSize - def transformation = [0,0,pixelSize as double] + def transformation = [0, 0, pixelSize as double] boolean gui4Success = createGUI4(); if (!gui4Success) { // User cancelled GUI4, so end GUI3 and do not proceed @@ -467,7 +458,7 @@ class QP_scope_GUI { def topCenterTileXY = utilityFunctions.getTopCenterTile(detections) QP.selectObjects(topCenterTileXY[2]) List args = [topCenterTileXY[0], topCenterTileXY[1]] - QuPathGUI.getInstance().getViewer().setCenterPixelLocation(topCenterTileXY[2].getROI().getCentroidX(),topCenterTileXY[2].getROI().getCentroidY()) + QuPathGUI.getInstance().getViewer().setCenterPixelLocation(topCenterTileXY[2].getROI().getCentroidX(), topCenterTileXY[2].getROI().getCentroidY()) //TODO run python script to move the stage to the middle X value of the lowest Y value utilityFunctions.runPythonCommand(virtualEnvPath, pythonScriptPath, args) //Validate the position that was moved to or update with an adjusted position @@ -482,7 +473,7 @@ class QP_scope_GUI { def leftCenterTileXY = utilityFunctions.getLeftCenterTile(detections) QP.selectObjects(leftCenterTileXY[2]) args = [leftCenterTileXY[0], leftCenterTileXY[1]] - QuPathGUI.getInstance().getViewer().setCenterPixelLocation(leftCenterTileXY[2].getROI().getCentroidX(),leftCenterTileXY[2].getROI().getCentroidY()) + QuPathGUI.getInstance().getViewer().setCenterPixelLocation(leftCenterTileXY[2].getROI().getCentroidX(), leftCenterTileXY[2].getROI().getCentroidY()) //TODO run python script to move the stage to the a tile position with the lowest X value, mid Y value utilityFunctions.runPythonCommand(virtualEnvPath, pythonScriptPath, args) //Once again, validate the position or update @@ -495,7 +486,7 @@ class QP_scope_GUI { } // Additional code for annotations - def annotations = getAnnotationObjects().findAll{it.getPathClass() == QP.getPathClass('Tissue')} + def annotations = getAnnotationObjects().findAll { it.getPathClass() == QP.getPathClass('Tissue') } if (annotations.size() != 1) { Dialogs.showWarningNotification("Error!", "Can only handle 1 annotation at the moment!"); return; @@ -508,7 +499,7 @@ class QP_scope_GUI { // TODO Check if any value is empty //Send the QuPath pixel coordinates for the bounding box along with the pixel size and upper left coordinates of the tissue - def boundingBox = utilityFunctions.transformBoundingBox(x1,y1,x2,y2,pixelSize, xCoordinate, yCoordinate, isSlideFlipped) + def boundingBox = utilityFunctions.transformBoundingBox(x1, y1, x2, y2, pixelSize, xCoordinate, yCoordinate, isSlideFlipped) logger.info(tilesCSVdirectory) @@ -517,11 +508,11 @@ class QP_scope_GUI { // scanTypeWithIndex will be the name of the folder where the tiles will be saved to args = [pythonScriptPath, - projectsFolderPath, - sampleLabel, - scanTypeWithIndex, - tilesCSVdirectory, //no annotation JSON file location - boundingBox ] + projectsFolderPath, + sampleLabel, + scanTypeWithIndex, + tilesCSVdirectory, //no annotation JSON file location + boundingBox] //TODO can we create non-blocking python code utilityFunctions.runPythonCommand(virtualEnvPath, pythonScriptPath, args) @@ -540,7 +531,6 @@ class QP_scope_GUI { utilityFunctions.addImageToProject(stitchedImagePath, currentQuPathProject) - qupathGUI.setProject(currentQuPathProject) //Find the existing images - there should only be one since the project was just created matchingImage = currentQuPathProject.getImageList().find { image -> @@ -600,6 +590,7 @@ class QP_scope_GUI { return pane } + static boolean createGUI4() { Dialog dlg = new Dialog<>(); dlg.initModality(Modality.NONE); diff --git a/src/main/groovy/qupath/ext/qp_scope/utilities/utilityFunctions.groovy b/src/main/groovy/qupath/ext/qp_scope/utilities/utilityFunctions.groovy index 14619e0..7c2eb75 100644 --- a/src/main/groovy/qupath/ext/qp_scope/utilities/utilityFunctions.groovy +++ b/src/main/groovy/qupath/ext/qp_scope/utilities/utilityFunctions.groovy @@ -1,23 +1,19 @@ package qupath.ext.qp_scope.utilities +import javafx.scene.control.Alert +import javafx.stage.Modality import org.slf4j.LoggerFactory -import qupath.lib.gui.QuPathGUI +import qupath.lib.gui.commands.ProjectCommands import qupath.lib.gui.dialogs.Dialogs +import qupath.lib.images.ImageData import qupath.lib.images.servers.ImageServerProvider import qupath.lib.objects.PathObject +import qupath.lib.projects.Project import qupath.lib.projects.ProjectIO +import qupath.lib.projects.Projects import qupath.lib.scripting.QP import java.awt.image.BufferedImage -import qupath.lib.projects.Projects; -import java.io.File -import javax.imageio.ImageIO -import qupath.lib.images.ImageData; -import qupath.lib.gui.commands.ProjectCommands -import javafx.scene.control.Alert -import javafx.stage.Modality -import qupath.lib.projects.Project - import java.nio.charset.StandardCharsets import java.nio.file.Files import java.nio.file.Path @@ -25,29 +21,27 @@ import java.nio.file.Paths import java.util.regex.Matcher import java.util.regex.Pattern import java.util.stream.Collectors -import java.util.stream.Stream -import java.io.*; -import java.nio.file.*; -import java.util.zip.*; +import java.util.zip.ZipEntry +import java.util.zip.ZipOutputStream; class utilityFunctions { static final logger = LoggerFactory.getLogger(utilityFunctions.class) - static void showAlertDialog(String message){ + static void showAlertDialog(String message) { Alert alert = new Alert(Alert.AlertType.WARNING); alert.setTitle("Warning!"); alert.setHeaderText(null); alert.setContentText(message); - // This line makes the alert a modal dialog + // This line makes the alert a modal dialog alert.initModality(Modality.APPLICATION_MODAL); alert.showAndWait(); } - static boolean addImageToProject(File stitchedImagePath, Project project){ + static boolean addImageToProject(File stitchedImagePath, Project project) { def imagePath = stitchedImagePath.toURI().toString() //logger.info(imagePath) @@ -83,7 +77,7 @@ class utilityFunctions { project.syncChanges() return true; -} + } static Project createProjectFolder(String projectsFolderPath, String sampleLabel, String scanType) { //TODO check if a project is open! It probably should not be? @@ -106,10 +100,10 @@ class utilityFunctions { Project project = null if (qpprojFiles == null || qpprojFiles.length == 0) { project = Projects.createProject(sampleLabelFolder, BufferedImage.class) - }else{ + } else { //WARNING: This assumes there will be only one file ending in .qpproj // this should USUALLY be a safe assumption - if (qpprojFiles.length > 1){ + if (qpprojFiles.length > 1) { Dialogs.showWarningNotification("Warning!", "Multiple Project files found, may cause unexpected behavior!") } @@ -120,8 +114,8 @@ class utilityFunctions { Dialogs.showWarningNotification("Warning!", "Project is null!") } // Within projectsFolderPath, check for a folder with the name "SlideImages", if it does not exist, create it - String slideImagesFolderPathStr = projectsFolderPath + File.separator + sampleLabel + File.separator + "SlideImages" ; - File slideImagesFolder = new File(slideImagesFolderPathStr); + String slideImagesFolderPathStr = projectsFolderPath + File.separator + sampleLabel + File.separator + "SlideImages"; + File slideImagesFolder = new File(slideImagesFolderPathStr); if (!slideImagesFolder.exists()) { slideImagesFolder.mkdirs() @@ -138,7 +132,7 @@ class utilityFunctions { * @param pythonScriptPath The path to the Python script to be executed. * @param arguments A list of arguments to pass to the python script. The amount may vary, and different scripts will be run depending on the number of arguments passed */ - static runPythonCommand(String anacondaEnvPath, String pythonScriptPath, List arguments) { + static runPythonCommand(String anacondaEnvPath, String pythonScriptPath, List arguments) { try { String pythonExecutable = new File(anacondaEnvPath, "python.exe").getCanonicalPath(); @@ -227,13 +221,13 @@ class utilityFunctions { //If preferences are null or missing, throw an error and close //Open to discussion whether scan types should be included here or typed every time, or some other option //TODO fix the installation to be a folder with an expected .py file target? Or keep as .py file target? - return [installation: "C:\\ImageAnalysis\\QPExtensionTest\\qp_scope\\src\\main\\pythonScripts/4x_bf_scan_pycromanager.py", - environment: "C:\\Anaconda\\envs\\paquo", - projects: "C:\\ImageAnalysis\\slides", + return [installation : "C:\\ImageAnalysis\\QPExtensionTest\\qp_scope\\src\\main\\pythonScripts/4x_bf_scan_pycromanager.py", + environment : "C:\\Anaconda\\envs\\paquo", + projects : "C:\\ImageAnalysis\\slides", tissueDetection: "C:\\ImageAnalysis\\QPExtensionTest\\qp_scope\\src\\main\\groovyScripts/DetectTissueSize.groovy", - firstScanType: "4x_bf", - secondScanType:"20x_bf", - tileHandling:"Zip"] //Zip Delete or anything else is ignored + firstScanType : "4x_bf", + secondScanType : "20x_bf", + tileHandling : "Zip"] //Zip Delete or anything else is ignored } /** * Exports all annotations to a JSON file in the specified JSON subfolder of the current project. @@ -248,7 +242,8 @@ class utilityFunctions { // Check if the folder exists, and create it if it doesn't if (!folder.exists()) { - folder.mkdirs(); // This will create the directory including any necessary but nonexistent parent directories. + folder.mkdirs(); + // This will create the directory including any necessary but nonexistent parent directories. } // Construct the full path for the annotation JSON file @@ -390,13 +385,12 @@ class utilityFunctions { } - /** * Modifies the specified Groovy script by updating the pixel size and the JSON file path. * - * @param groovyScriptPath The path to the Groovy script file. - * @param pixelSize The new pixel size to set in the script. - * @param jsonFilePathString The new JSON file path to set in the script. + * @param groovyScriptPath The path to the Groovy script file. + * @param pixelSize The new pixel size to set in the script. + * @param jsonFilePathString The new JSON file path to set in the script. * @throws IOException if an I/O error occurs reading from or writing to the file. */ public static String modifyTissueDetectScript(String groovyScriptPath, String pixelSize, String jsonFilePathString) throws IOException { @@ -419,9 +413,9 @@ class utilityFunctions { /** * Modifies the specified export script by updating the pixel size source and the base directory, and returns the modified script as a string. * - * @param exportScriptPathString The path to the export script file. - * @param pixelSize The new pixel size to set in the script. - * @param tilesCSVdirectory The new base directory to set in the script. + * @param exportScriptPathString The path to the export script file. + * @param pixelSize The new pixel size to set in the script. + * @param tilesCSVdirectory The new base directory to set in the script. * @return String representing the modified script. * @throws IOException if an I/O error occurs reading from the file. */ @@ -464,6 +458,7 @@ class utilityFunctions { return null; // No match found } } + static List getTopCenterTile(Collection detections) { // Filter out null detections and sort by Y-coordinate List sortedDetections = detections.findAll { it != null } @@ -508,7 +503,7 @@ class utilityFunctions { //TODO possibly use QuPath's affine transformation tools //Convert the QuPath pixel based coordinates for a location into the MicroManager micron based stage coordinates - static List QPtoMicroscopeCoordinates(List qpCoordinates, Double imagePixelSize, Object transformation){ + static List QPtoMicroscopeCoordinates(List qpCoordinates, Double imagePixelSize, Object transformation) { //TODO figure out conversion def mmCoordinates = qpCoordinates return mmCoordinates diff --git a/src/main/groovyScripts/save4xMacroTiling.groovy b/src/main/groovyScripts/save4xMacroTiling.groovy index 890ca82..c00c6ee 100644 --- a/src/main/groovyScripts/save4xMacroTiling.groovy +++ b/src/main/groovyScripts/save4xMacroTiling.groovy @@ -15,13 +15,13 @@ imagingModality = "4x-tiles" /***********************************************/ //Name each annotation in the image by its XY centroids -getAnnotationObjects().each{ +getAnnotationObjects().each { - it.setName((int)it.getROI().getCentroidX()+"_"+ (int)it.getROI().getCentroidY()) + it.setName((int) it.getROI().getCentroidX() + "_" + (int) it.getROI().getCentroidY()) } /***********************************************/ -getAnnotationObjects().each{it.setLocked(true)} +getAnnotationObjects().each { it.setLocked(true) } imageData = getQuPath().getImageData() hierarchy = imageData.getHierarchy() @@ -40,10 +40,10 @@ tilePath = buildFilePath(baseDirectory, imagingModality) mkdirs(tilePath) //CSV will be only two columns with the following header -String header="x_pos,y_pos"; +String header = "x_pos,y_pos"; + +annotations.eachWithIndex { a, i -> -annotations.eachWithIndex{a,i-> - predictedTileCount = 0; //Numbering tiles based on the tiles that would have been created from the bounding box actualTileCount = 0; //Tile objects created and saved to CSV - tiles not overlapping the annotation are excluded xy = []; @@ -56,57 +56,56 @@ annotations.eachWithIndex{a,i-> bBoxW = a.getROI().getBoundsWidth() y = bBoxY x = bBoxX - while (y< bBoxY+bBoxH){ + while (y < bBoxY + bBoxH) { //In order to serpentine the resutls, there need to be two bounds for X now - while ((x <= bBoxX+bBoxW) && (x >=bBoxX-bBoxW*overlapPercent/100)){ + while ((x <= bBoxX + bBoxW) && (x >= bBoxX - bBoxW * overlapPercent / 100)) { - def roi = new RectangleROI(x,y,frameWidth,frameHeight, ImagePlane.getDefaultPlane()) - if(roiA.getGeometry().intersects(roi.getGeometry())){ + def roi = new RectangleROI(x, y, frameWidth, frameHeight, ImagePlane.getDefaultPlane()) + if (roiA.getGeometry().intersects(roi.getGeometry())) { newAnno = PathObjects.createDetectionObject(roi, getPathClass(imagingModality)) newAnno.setName(predictedTileCount.toString()) newAnno.getMeasurementList().putMeasurement("TileNumber", actualTileCount) newTiles << newAnno - xy << [x,y] + xy << [x, y] //print predictedTileCount + " good "+x actualTileCount++ }//else {print x} - if (yline%2 ==0){ - x = x+frameWidth-overlapPercent/100*frameWidth - } else { x = x-(frameWidth - overlapPercent/100*frameWidth)} + if (yline % 2 == 0) { + x = x + frameWidth - overlapPercent / 100 * frameWidth + } else { + x = x - (frameWidth - overlapPercent / 100 * frameWidth) + } predictedTileCount++ } - y = y+frameHeight-overlapPercent/100*frameHeight - if (yline%2 ==0){ - x = x-(frameWidth - overlapPercent/100*frameWidth) - } else {x = x+frameWidth-overlapPercent/100*frameWidth} - + y = y + frameHeight - overlapPercent / 100 * frameHeight + if (yline % 2 == 0) { + x = x - (frameWidth - overlapPercent / 100 * frameWidth) + } else { + x = x + frameWidth - overlapPercent / 100 * frameWidth + } + yline++ } hierarchy.addObjects(newTiles) //Does not use CLASS of annotation in the name at the moment. annotationName = a.getName() - path = buildFilePath(baseDirectory, imagingModality, imageName+"-"+annotationName+".csv") + path = buildFilePath(baseDirectory, imagingModality, imageName + "-" + annotationName + ".csv") new File(path).withWriter { fw -> fw.writeLine(header) //Make sure everything being sent is a child and part of the current annotation. - - xy.each{ - String line = it[0] as String +","+it[1] as String + + xy.each { + String line = it[0] as String + "," + it[1] as String fw.writeLine(line) } } } - -import qupath.imagej.gui.IJExtension -import qupath.imagej.tools.IJTools -import qupath.lib.objects.PathObjectTools -import qupath.lib.regions.RegionRequest import qupath.lib.regions.ImagePlane -import qupath.lib.roi.RectangleROI; -import qupath.lib.gui.QuPathGUI +import qupath.lib.roi.RectangleROI -import static qupath.lib.gui.scripting.QPEx.* +import static qupath.lib.gui.scripting.QPEx.getQuPath +import static qupath.lib.scripting.QP.* diff --git a/src/main/groovyScripts/saveTilingCSV.groovy b/src/main/groovyScripts/saveTilingCSV.groovy index 95d63ea..4933818 100644 --- a/src/main/groovyScripts/saveTilingCSV.groovy +++ b/src/main/groovyScripts/saveTilingCSV.groovy @@ -15,13 +15,13 @@ baseDirectory = "to be replaced" /***********************************************/ //Name each annotation in the image by its XY centroids -getAnnotationObjects().each{ +getAnnotationObjects().each { - it.setName((int)it.getROI().getCentroidX()+"_"+ (int)it.getROI().getCentroidY()) + it.setName((int) it.getROI().getCentroidX() + "_" + (int) it.getROI().getCentroidY()) } /***********************************************/ -getAnnotationObjects().each{it.setLocked(true)} +getAnnotationObjects().each { it.setLocked(true) } //Logger logger = LoggerFactory.getLogger(QuPathGUI.class); imageData = getQuPath().getImageData() @@ -41,10 +41,10 @@ tilePath = buildFilePath(baseDirectory, "20x-tiles") mkdirs(tilePath) //CSV will be only two columns with the following header -String header="x_pos,y_pos"; +String header = "x_pos,y_pos"; + +annotations.eachWithIndex { a, i -> -annotations.eachWithIndex{a,i-> - predictedTileCount = 0; //Numbering tiles based on the tiles that would have been created from the bounding box actualTileCount = 0; //Tile objects created and saved to CSV - tiles not overlapping the annotation are excluded xy = []; @@ -57,44 +57,50 @@ annotations.eachWithIndex{a,i-> bBoxW = a.getROI().getBoundsWidth() y = bBoxY x = bBoxX - while (y< bBoxY+bBoxH){ + while (y < bBoxY + bBoxH) { //In order to serpentine the resutls, there need to be two bounds for X now - while ((x <= bBoxX+bBoxW) && (x >=bBoxX-bBoxW*overlapPercent/100)){ + while ((x <= bBoxX + bBoxW) && (x >= bBoxX - bBoxW * overlapPercent / 100)) { - def roi = new RectangleROI(x,y,frameWidth,frameHeight, ImagePlane.getDefaultPlane()) - if(roiA.getGeometry().intersects(roi.getGeometry())){ + def roi = new RectangleROI(x, y, frameWidth, frameHeight, ImagePlane.getDefaultPlane()) + if (roiA.getGeometry().intersects(roi.getGeometry())) { newAnno = PathObjects.createDetectionObject(roi, getPathClass("20x Tile")) newAnno.setName(predictedTileCount.toString()) newAnno.getMeasurementList().putMeasurement("TileNumber", actualTileCount) newTiles << newAnno - xy << [x,y] + xy << [x, y] //print predictedTileCount + " good "+x actualTileCount++ - }else {print x} - if (yline%2 ==0){ - x = x+frameWidth-overlapPercent/100*frameWidth - } else { x = x-(frameWidth - overlapPercent/100*frameWidth)} + } else { + print x + } + if (yline % 2 == 0) { + x = x + frameWidth - overlapPercent / 100 * frameWidth + } else { + x = x - (frameWidth - overlapPercent / 100 * frameWidth) + } predictedTileCount++ } - y = y+frameHeight-overlapPercent/100*frameHeight - if (yline%2 ==0){ - x = x-(frameWidth - overlapPercent/100*frameWidth) - } else {x = x+frameWidth-overlapPercent/100*frameWidth} - + y = y + frameHeight - overlapPercent / 100 * frameHeight + if (yline % 2 == 0) { + x = x - (frameWidth - overlapPercent / 100 * frameWidth) + } else { + x = x + frameWidth - overlapPercent / 100 * frameWidth + } + yline++ } hierarchy.addPathObjects(newTiles) //Does not use CLASS of annotation in the name at the moment. annotationName = a.getName() - path = buildFilePath(baseDirectory, "20x-tiles", imageName+"-"+annotationName+".csv") + path = buildFilePath(baseDirectory, "20x-tiles", imageName + "-" + annotationName + ".csv") //logger.info(path.toString()) new File(path).withWriter { fw -> fw.writeLine(header) //logger.info(header) //Make sure everything being sent is a child and part of the current annotation. - - xy.each{ - String line = it[0] as String +","+it[1] as String + + xy.each { + String line = it[0] as String + "," + it[1] as String fw.writeLine(line) } } @@ -119,14 +125,14 @@ newTiles = [] //Store XY coordinates in an array //Check all annotations. Use .findAll{expression} to select a subset -annotations = hierarchy.getAnnotationObjects().findAll{it.getPathClass()!=getPathClass("Background")} +annotations = hierarchy.getAnnotationObjects().findAll { it.getPathClass() != getPathClass("Background") } imageName = GeneralTools.getNameWithoutExtension(getQuPath().getProject().getEntry(imageData).getImageName()) //logger.info(imageName.toString()) //Ensure the folder to store the csv exists tilePath = buildFilePath(baseDirectory, "mp-tiles") mkdirs(tilePath) -annotations.eachWithIndex{a,i-> +annotations.eachWithIndex { a, i -> predictedTileCount = 0; //Numbering tiles based on the tiles that would have been created from the bounding box actualTileCount = 0; //Tile objects created and saved to CSV - tiles not overlapping the annotation are excluded xy = []; @@ -139,57 +145,58 @@ annotations.eachWithIndex{a,i-> bBoxW = a.getROI().getBoundsWidth() y = bBoxY x = bBoxX - while (y< bBoxY+bBoxH){ + while (y < bBoxY + bBoxH) { //In order to serpentine the results, there need to be two bounds for X now - while ((x <= bBoxX+bBoxW) && (x >= bBoxX-bBoxW*overlapPercent/100)){ + while ((x <= bBoxX + bBoxW) && (x >= bBoxX - bBoxW * overlapPercent / 100)) { - def roi = new RectangleROI(x,y,frameWidth,frameHeight, ImagePlane.getDefaultPlane()) - if(roiA.getGeometry().intersects(roi.getGeometry())){ + def roi = new RectangleROI(x, y, frameWidth, frameHeight, ImagePlane.getDefaultPlane()) + if (roiA.getGeometry().intersects(roi.getGeometry())) { newAnno = PathObjects.createDetectionObject(roi, getPathClass("MP Tile")) newAnno.setName(predictedTileCount.toString()) newAnno.getMeasurementList().putMeasurement("TileNumber", actualTileCount) newTiles << newAnno - xy << [x,y] + xy << [x, y] actualTileCount++; //print predictedTileCount + " good "+x - }else {print x} - if (yline%2 ==0){ - x = x+frameWidth-overlapPercent/100*frameWidth - } else { x = x-(frameWidth - overlapPercent/100*frameWidth)} + } else { + print x + } + if (yline % 2 == 0) { + x = x + frameWidth - overlapPercent / 100 * frameWidth + } else { + x = x - (frameWidth - overlapPercent / 100 * frameWidth) + } predictedTileCount++ } - y = y+frameHeight-overlapPercent/100*frameHeight - if (yline%2 ==0){ - x = x-(frameWidth - overlapPercent/100*frameWidth) - } else {x = x+frameWidth-overlapPercent/100*frameWidth} - + y = y + frameHeight - overlapPercent / 100 * frameHeight + if (yline % 2 == 0) { + x = x - (frameWidth - overlapPercent / 100 * frameWidth) + } else { + x = x + frameWidth - overlapPercent / 100 * frameWidth + } + yline++ } hierarchy.addPathObjects(newTiles) //Does not use CLASS of annotation in the name at the moment. annotationName = a.getName() - path = buildFilePath(baseDirectory, "mp-tiles",imageName+"-"+annotationName+".csv") + path = buildFilePath(baseDirectory, "mp-tiles", imageName + "-" + annotationName + ".csv") //logger.info(path.toString()) new File(path).withWriter { fw -> fw.writeLine(header) //logger.info(header) //Make sure everything being sent is a child and part of the current annotation. - - xy.each{ - String line = it[0] as String +","+it[1] as String + + xy.each { + String line = it[0] as String + "," + it[1] as String fw.writeLine(line) } } } -import qupath.imagej.gui.IJExtension -import qupath.imagej.tools.IJTools -import qupath.lib.objects.PathObjectTools -import qupath.lib.regions.RegionRequest + import qupath.lib.regions.ImagePlane -import qupath.lib.roi.RectangleROI; -import qupath.lib.gui.QuPathGUI +import qupath.lib.roi.RectangleROI -import static qupath.lib.gui.scripting.QPEx.* -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; \ No newline at end of file +import static qupath.lib.gui.scripting.QPEx.getQuPath +import static qupath.lib.scripting.QP.*; \ No newline at end of file