Skip to content

Commit

Permalink
Update to stopping point, need help with QuPath specific code
Browse files Browse the repository at this point in the history
Need to access my own other plugin
  • Loading branch information
MichaelSNelson committed Dec 7, 2023
1 parent dc6d5e9 commit b785116
Show file tree
Hide file tree
Showing 15 changed files with 149 additions and 55 deletions.
Binary file modified .gradle/7.5.1/dependencies-accessors/dependencies-accessors.lock
Binary file not shown.
Binary file modified .gradle/7.5.1/executionHistory/executionHistory.bin
Binary file not shown.
Binary file modified .gradle/7.5.1/executionHistory/executionHistory.lock
Binary file not shown.
Binary file modified .gradle/7.5.1/fileHashes/fileHashes.bin
Binary file not shown.
Binary file modified .gradle/7.5.1/fileHashes/fileHashes.lock
Binary file not shown.
Binary file modified .gradle/buildOutputCleanup/buildOutputCleanup.lock
Binary file not shown.
Binary file modified .gradle/buildOutputCleanup/outputFiles.bin
Binary file not shown.
Binary file modified .gradle/file-system.probe
Binary file not shown.
1 change: 0 additions & 1 deletion .idea/.name

This file was deleted.

2 changes: 0 additions & 2 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -52,8 +52,6 @@ dependencies {
// If you aren't using Groovy, this can be removed
shadow libs.bundles.groovy



testImplementation "io.github.qupath:qupath-gui-fx:${qupathVersion}"
testImplementation libs.junit
}
Expand Down
2 changes: 1 addition & 1 deletion build/tmp/jar/MANIFEST.MF
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
Manifest-Version: 1.0
Implementation-Title: MicroQP
Implementation-Title: qp_scope
Implementation-Version: 0.4.4
Automatic-Module-Name: io.github.qupath.extension.qp_scope

2 changes: 1 addition & 1 deletion settings.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ pluginManagement {
}

// TODO: Name your QuPath extension here!
rootProject.name = 'MicroQP'
rootProject.name = 'qp_scope'

// TODO: Define the QuPath version compatible with the extension
// Note that the QuPath API isn't stable; something designed for
Expand Down
3 changes: 3 additions & 0 deletions src/main/groovy/qupath/ext/qp_scope/QP_scope.groovy
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ class QP_scope implements QuPathExtension {
@Override
void installExtension(QuPathGUI qupath) {
addMenuItem(qupath)

}
/**
* Get the description of the extension.
Expand All @@ -56,6 +57,8 @@ class QP_scope implements QuPathExtension {
def fileNameStitching = new MenuItem("Start qp_scope")
// TODO tooltip "Coordinates are expected to be in the format ImageName[xCoordinateInMicrons, yCoordinateInMicrons].tif"
fileNameStitching.setOnAction(e -> {
//TODO check preferences for all necessary entries

QP_scope_GUI.createGUI()

})
Expand Down
34 changes: 27 additions & 7 deletions src/main/groovy/qupath/ext/qp_scope/functions/QP_scope_GUI.groovy
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@ import javafx.scene.layout.GridPane
import javafx.stage.Modality
import qupath.ext.qp_scope.utilities.utilityFunctions
import qupath.lib.gui.dialogs.Dialogs
import groovy.io.FileType
import java.awt.image.BufferedImage
import qupath.lib.images.servers.ImageServerProvider


class QP_scope_GUI {

Expand All @@ -17,12 +21,14 @@ class QP_scope_GUI {
static TextField y1Field = new TextField("");
static TextField x2Field = new TextField("");
static TextField y2Field = new TextField("");
static TextField scanBox = new TextField("");
static TextField scanBox = new TextField("20,25,30,35");
static preferences = utilityFunctions.getPreferences()

// New text fields for Python environment, script path, and sample label
static TextField virtualEnvField = new TextField("");
static TextField pythonScriptField = new TextField("");
static TextField sampleLabelField = new TextField(""); // New field for sample label
static TextField virtualEnvField = new TextField(preferences.environment);
static TextField pythonScriptField = new TextField(preferences.installation);
static TextField projectsFolderField = new TextField(preferences.projects);
static TextField sampleLabelField = new TextField("First_Test"); // New field for sample label

static void createGUI() {
// Create the dialog
Expand All @@ -48,8 +54,10 @@ class QP_scope_GUI {
def y1 = y1Field.getText();
def x2 = x2Field.getText();
def y2 = y2Field.getText();
def virtualEnvPath = virtualEnvField.getText();
def pythonScriptPath = pythonScriptField.getText();
def virtualEnvPath = virtualEnvField.getText()
def pythonScriptPath = pythonScriptField.getText()
def projectsFolderPath = projectsFolderField.getText()


// Handle full bounding box input
def boxString = scanBox.getText();
Expand All @@ -67,7 +75,16 @@ class QP_scope_GUI {
if ([sampleLabel, x1, y1, x2, y2, virtualEnvPath, pythonScriptPath].any { it == null || it.isEmpty() }) {
Dialogs.showWarningNotification("Warning!", "Incomplete data entered.");
} else {
utilityFunctions.runPythonCommand(virtualEnvPath, pythonScriptPath, sampleLabel, x1, y1, x2, y2);
Object project = utilityFunctions.createProjectFolder(projectsFolderPath,sampleLabel, preferences.firstScanType)
utilityFunctions.runPythonCommand(virtualEnvPath, pythonScriptPath, projectsFolderPath, sampleLabel, x1, y1, x2, y2);

//TODO figure out how to call stitching function in other plugin
utilityFunctions.showAlertDialog("Wait and complete stitching in other version of QuPath")
String stitchedImagePathStr = projectsFolderPath + File.separator + sampleLabel + File.separator + "SlideImages" + File.separator + preferences.firstScanType + sampleLabel;
File stitchedImagePath = new File(stitchedImagePathStr);
utilityFunctions.addImageToProject(stitchedImagePath, project)

//}
}
}
}
Expand All @@ -91,6 +108,7 @@ class QP_scope_GUI {
// Add components for Python environment and script path
addToGrid(pane, new Label('Python Virtual Env Location:'), virtualEnvField, row++);
addToGrid(pane, new Label('.py file path:'), pythonScriptField, row++);
addToGrid(pane, new Label('Projects path:'), projectsFolderField, row++);

return pane;
}
Expand All @@ -99,4 +117,6 @@ class QP_scope_GUI {
pane.add(label, 0, rowIndex);
pane.add(control, 1, rowIndex);
}


}
160 changes: 117 additions & 43 deletions src/main/groovy/qupath/ext/qp_scope/utilities/utilityFunctions.groovy
Original file line number Diff line number Diff line change
Expand Up @@ -2,73 +2,147 @@ package qupath.ext.qp_scope.utilities

import org.slf4j.LoggerFactory
import qupath.lib.gui.QuPathGUI
import qupath.lib.images.writers.ome.OMEPyramidWriter
import qupath.lib.images.servers.ImageServerProvider

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


class utilityFunctions {
static OMEPyramidWriter.CompressionType getCompressionType(String selectedOption) {
switch (selectedOption) {
case "Lossy compression":
return OMEPyramidWriter.CompressionType.J2K_LOSSY
case "Lossless compression":
return OMEPyramidWriter.CompressionType.J2K
default:
return null // or a default value
}

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
alert.initModality(Modality.APPLICATION_MODAL);

alert.showAndWait();
}

static Map<String, Integer> getTiffDimensions(File filePath) {
def logger = LoggerFactory.getLogger(QuPathGUI.class)
if (!filePath.exists()) {
logger.info("File not found: $filePath")
return null
static boolean addImageToProject(File stitchedImagePath, Project project){

def imagePath = stitchedImagePath.getCanonicalPath()

def support = ImageServerProvider.getPreferredUriImageSupport(BufferedImage.class, imagePath, "")
println(support)
def builder = support.builders.get(0)
// Make sure we don't have null
if (builder == null) {
print "Image not supported: " + imagePath
return false
}

try {
def image = ImageIO.read(filePath)
if (image == null) {
logger.info("ImageIO returned null for file: $filePath")
return null
}
return [width: image.getWidth(), height: image.getHeight()]
} catch (IOException e) {
logger.info("Error reading the image file $filePath: ${e.message}")
return null
// Add the image as entry to the project
print "Adding: " + imagePath
Object entry = project.addImage(builder)

// Set a particular image type
def imageData = entry.readImageData()
imageData.setImageType(ImageData.ImageType.BRIGHTFIELD_H_DAB)
entry.saveImageData(imageData)

// Write a thumbnail if we can
var img = ProjectCommands.getThumbnailRGB(imageData.getServer());
entry.setThumbnail(img)

// Add an entry name (the filename)
entry.setImageName(stitchedImagePath.getName())
project.syncChanges()
return true;

}

static Project createProjectFolder(String projectsFolderPath, String sampleLabel, String scanType) {
// Ensure that the projectsFolderPath exists, if it does not, create it.
File projectsFolder = new File(projectsFolderPath)
if (!projectsFolder.exists()) {
projectsFolder.mkdirs()
}

// Within projectsFolderPath, check for a folder named sampleLabel, if it does not exist, create it.
File sampleLabelFolder = new File(projectsFolder, sampleLabel)
if (!sampleLabelFolder.exists()) {
sampleLabelFolder.mkdirs()
}

// Check for a .qpproj file in the sampleLabel folder
File[] qpprojFiles = sampleLabelFolder.listFiles({ File f -> f.name.endsWith('.qpproj') } as FilenameFilter)
//Create a QuPath project in the sampleLabelFolder, within the projects folder
Project project = null
if (qpprojFiles == null || qpprojFiles.length == 0) {
project = Projects.createProject(sampleLabelFolder, BufferedImage.class)
}

// Within projectsFolderPath, check for a folder with the name "SlideImages", if it does not exist, create it
File slideImagesFolder = new File(projectsFolder, sampleLabelFolder, "SlideImages")
if (!slideImagesFolder.exists()) {
slideImagesFolder.mkdirs()
}

// Within projectFolderPath/sampleLabel, check for a folder with the name that combines the two variables scanType+sampleLabel, if it does not exist, create it
File scanTypeFolder = new File(sampleLabelFolder, scanType + sampleLabel)
if (!scanTypeFolder.exists()) {
scanTypeFolder.mkdirs()
}
return project
}
/**
* Executes a Python script using a specified Python executable within a virtual environment.
* This method is designed to be compatible with Windows, Linux, and macOS.
*
* @param anacondaEnvPath The path to the Python virtual environment.
* @param pythonScriptPath The path to the Python script to be executed.
* @param x1 The first x-coordinate to be passed to the Python script.
* @param y1 The first y-coordinate to be passed to the Python script.
* @param x2 The second x-coordinate to be passed to the Python script.
* @param y2 The second y-coordinate to be passed to the Python script.
*/
static void runPythonCommand(String anacondaEnvPath, String pythonScriptPath, String x1, String y1, String x2, String y2) {

/**
* Executes a Python script using a specified Python executable within a virtual environment.
* This method is designed to be compatible with Windows, Linux, and macOS.
*
* @param anacondaEnvPath The path to the Python virtual environment.
* @param pythonScriptPath The path to the Python script to be executed.
* @param x1 The first x-coordinate to be passed to the Python script.
* @param y1 The first y-coordinate to be passed to the Python script.
* @param x2 The second x-coordinate to be passed to the Python script.
* @param y2 The second y-coordinate to be passed to the Python script.
*/
static void runPythonCommand(String anacondaEnvPath, String pythonScriptPath,String projectsFolderPath, String sampleLabel, String x1, String y1, String x2, String y2) {
try {
def logger = LoggerFactory.getLogger(QuPathGUI.class)
// Path to the Python executable in the Anaconda environment
String pythonExecutable = "${anacondaEnvPath}/python.exe";

// Combine coordinates into a single argument
String coordinatesArg = "${x1},${y1},${x2},${y2}";
logger.info("${x1},${y1},${x2},${y2}")
String args = "$pythonScriptPath, $projectsFolderPath, $sampleLabel, ${x1},${y1},${x2},${y2}";
logger.info(args)
// Construct the command
String command = "\"${pythonExecutable}\" \"${pythonScriptPath}\" ${coordinatesArg}";
String command = "\"${pythonExecutable}\" \"${pythonScriptPath}\" ${args}";
logger.info(command)
// Execute the command
Process process = command.execute();
process.waitFor();

// Read the output
process.inputStream.eachLine { line -> println line };
process.errorStream.eachLine { line -> System.err.println line };
// Read and log standard output
process.inputStream.eachLine { line ->
logger.info(line)
}

// Read and log standard error
process.errorStream.eachLine { line ->
logger.error(line)
}

} catch (Exception e) {
e.printStackTrace();
}
}

static Map<String, String> getPreferences() {
//TODO add code to access Preferences fields
//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
return [installation: "C:\\ImageAnalysis\\python", environment: "C:\\Anaconda\\envs\\paquo", projects: "C:\\ImageAnalysis\\slides", firstScanType: "4x_bf_", secondScanType:"20x_bf"]
}
}

0 comments on commit b785116

Please sign in to comment.