Skip to content

Commit

Permalink
Changes for non-macro image code
Browse files Browse the repository at this point in the history
Get BoundingBox input code working - multiple changes to the Groovy script for tiling to allow either a bounding box or annotation as input.
  • Loading branch information
MichaelSNelson committed Jan 15, 2024
1 parent 33d8f7e commit 6de7f78
Show file tree
Hide file tree
Showing 5 changed files with 178 additions and 124 deletions.
79 changes: 39 additions & 40 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 @@ -15,8 +15,10 @@ import qupath.lib.projects.Project
import qupath.lib.scripting.QP

import java.awt.image.BufferedImage

import java.nio.file.Path
import java.nio.file.Paths

import java.util.stream.Collectors

import static qupath.lib.scripting.QP.getAnnotationObjects
Expand All @@ -33,9 +35,8 @@ class QP_scope_GUI {
static TextField y1Field = new TextField("")
static TextField x2Field = new TextField("")
static TextField y2Field = new TextField("")
static TextField scanBox = new TextField("20,25,30,35")
static TextField scanBox = new TextField("0,0,5000,5000")
static preferences = utilityFunctions.getPreferences()
static CheckBox useAnnotationsCheckBox = new CheckBox("Use annotations")

// New text fields for Python environment, script path, and sample label
static TextField virtualEnvField = new TextField(preferences.environment)
Expand Down Expand Up @@ -81,36 +82,24 @@ class QP_scope_GUI {
def boxString = scanBox.getText()
//Boolean to check whether to proceed with running the microscope data collection
boolean dataCheck = true
def annotations = QP.getAnnotationObjects()
// Check if using annotations
def pixelSize = preferences.pixelSizeTarget

//TODO REPLACE ALL OF THIS WITH TILING
if (useAnnotationsCheckBox.isSelected()) {
// Continue with previous behavior using coordinates

// Check if annotations are present
if (annotations.isEmpty() || [sampleLabel, virtualEnvPath, pythonScriptPath].any { it == null || it.isEmpty() }) {
Dialogs.showWarningNotification("Warning!", "Insufficient data to send command to microscope!")
dataCheck = false
return
}
annotationJsonFileLocation = utilityFunctions.createAnnotationJson(projectsFolderPath, sampleLabel, preferences.firstScanType)
} else {
// Continue with previous behavior using coordinates

if (boxString != "") {
def values = boxString.replaceAll("[^0-9.,]", "").split(",")
if (values.length == 4) {
x1 = values[0]
y1 = values[1]
x2 = values[2]
y2 = values[3]
}
}
if ([sampleLabel, x1, y1, x2, y2, virtualEnvPath, pythonScriptPath].any { it == null || it.isEmpty() }) {
Dialogs.showWarningNotification("Warning!", "Incomplete data entered.")
dataCheck = false
if (boxString != "") {
def values = boxString.replaceAll("[^0-9.,]", "").split(",")
if (values.length == 4) {
x1 = values[0]
y1 = values[1]
x2 = values[2]
y2 = values[3]
}
}
if ([sampleLabel, x1, y1, x2, y2, virtualEnvPath, pythonScriptPath].any { it == null || it.isEmpty() }) {
Dialogs.showWarningNotification("Warning!", "Incomplete data entered.")
dataCheck = false
}


// Check if any value is empty
if (dataCheck) {
Expand All @@ -119,8 +108,26 @@ class QP_scope_GUI {
def tempTileDirectory = projectsFolderPath + File.separator + sampleLabel + File.separator + scanTypeWithIndex
def logger = LoggerFactory.getLogger(QuPathGUI.class)
logger.info(tempTileDirectory)
//Reduce the number of sent args

Path groovyScriptDirectory = Paths.get(pythonScriptPath).getParent();
groovyScriptDirectory = groovyScriptDirectory.resolveSibling("groovyScripts")

// Combine the directory with the new filename
Path exportScriptPath = groovyScriptDirectory.resolve("save4xMacroTiling.groovy")
String exportScriptPathString = exportScriptPath.toString().replace("\\", "/");
String exportScript = utilityFunctions.modifyTXTExportScript(exportScriptPathString, pixelSize, preferences, sampleLabel)
def boundingBox = "{$x1}, {$y1}, {$x2}, {$y2}"
//Specifically for the case where there is only a bounding box provided
List boundingBoxValues = [x1, y1, x2, y2] // Replace x1, y1, x2, y2 with actual values
exportScript = utilityFunctions.boundingBoxReadyTXT(exportScript, boundingBoxValues)


logger.info(exportScript)
logger.info(boundingBox)
QuPathGUI.getInstance().runScript(null, exportScript);

//Reduce the number of sent args

// scanTypeWithIndex will be the name of the folder where the tiles will be saved to

List args = [pythonScriptPath,
Expand Down Expand Up @@ -180,7 +187,6 @@ class QP_scope_GUI {

// Add new component for Sample Label
addToGrid(pane, new Label('Sample Label:'), sampleLabelField, row++)
addToGrid(pane, new Label('Use annotations in current image?'), useAnnotationsCheckBox, row++)

// Add existing components to the grid
addToGrid(pane, new Label('X1:'), x1Field, row++)
Expand All @@ -193,15 +199,7 @@ class QP_scope_GUI {
addToGrid(pane, new Label('Python Virtual Env Location:'), virtualEnvField, row++)
addToGrid(pane, new Label('PycroManager .py path:'), pythonScriptField, row++)
addToGrid(pane, new Label('Projects parent folder:'), projectsFolderField, row++)
// Listener for the checkbox
useAnnotationsCheckBox.selectedProperty().addListener((observable, oldValue, newValue) -> {
x1Field.setDisable(newValue)
y1Field.setDisable(newValue)
x2Field.setDisable(newValue)
y2Field.setDisable(newValue)
scanBox.setDisable(newValue)
// You can also disable other related fields if necessary
})

return pane
}

Expand Down Expand Up @@ -438,7 +436,7 @@ class QP_scope_GUI {
QuPathGUI.getInstance().runScript(null, tissueDetectScript);
//At this point the tissue should be outlined in an annotation

String exportScript = utilityFunctions.modifyCSVExportScript(exportScriptPathString, pixelSize, preferences)
String exportScript = utilityFunctions.modifyTXTExportScript(exportScriptPathString, pixelSize, preferences, sampleLabel)
logger.info(exportScript)
logger.info(exportScriptPathString)
QuPathGUI.getInstance().runScript(null, exportScript);
Expand Down Expand Up @@ -467,6 +465,7 @@ class QP_scope_GUI {
if (updatePosition) {
//TODO get access to current stage coordinates
List currentStageCoordinates_um = utilityFunctions.runPythonCommand(virtualEnvPath, pythonScriptPath, null)
logger.info(currentStageCoordinates_um.toString())
transformation = utilityFunctions.updateTransformation(transformation, currentStageCoordinates_um, args)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -149,7 +149,7 @@ class utilityFunctions {
logger.info("This should get stage coordinates back")
List<String> result = handleProcessOutput(process)
if (result != null) {
logger.info("Received coordinates: ${result[0]}, ${result[1]}")
logger.info("Received output: ${result.join(', ')}")
return result
} else {
logger.error("Error occurred or no valid output received from the script.")
Expand Down Expand Up @@ -262,32 +262,7 @@ class utilityFunctions {
frameHeight : "1040",
overlapPercent : "0"] //Zip Delete or anything else is ignored
}
/**
* Exports all annotations to a JSON file in the specified JSON subfolder of the current project.
*
* @param projectsFolderPath The base path to the projects folder.
* @param sampleLabel The label of the sample.
* @param firstScanType The type of the first scan.
*/
static String createAnnotationJson(String projectsFolderPath, String sampleLabel, String firstScanType) {
// Construct the folder path for storing the JSON file
File folder = new File(projectsFolderPath + File.separator + sampleLabel + File.separator + "JSON");

// 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.
}

// Construct the full path for the annotation JSON file
File annotationJsonFile = new File(folder, firstScanType + sampleLabel + ".geojson");
String annotationJsonFileLocation = annotationJsonFile.getPath();

// Export all annotations to the GeoJSON file
QP.exportAllObjectsToGeoJson(annotationJsonFileLocation, "EXCLUDE_MEASUREMENTS", "FEATURE_COLLECTION");

return annotationJsonFileLocation
}
/**
* Generates a unique folder name by checking the number of existing folders with a similar name
* in the current directory, and then appending that number to the folder name.
Expand Down Expand Up @@ -452,7 +427,7 @@ class utilityFunctions {
* @return String representing the modified script.
* @throws IOException if an I/O error occurs reading from the file.
*/
public static String modifyCSVExportScript(String exportScriptPathString, String pixelSize, LinkedHashMap<String, String> preferences) throws IOException {
public static String modifyTXTExportScript(String exportScriptPathString, String pixelSize, Map<String, String> preferences, String sampleLabel) throws IOException {
// Read and modify the script
List<String> lines = Files.lines(Paths.get(exportScriptPathString), StandardCharsets.UTF_8)
.map(line -> {
Expand All @@ -469,7 +444,12 @@ class utilityFunctions {
} else if (line.startsWith("double overlapPercent")) {
return "double overlapPercent = " + preferences.overlapPercent + ";";
} else if (line.startsWith("baseDirectory")) {
return "baseDirectory = \"" + preferences.projects.replace("\\", "\\\\") + "\";";
String pathSeparator = System.getProperty("file.separator");
String baseDirectoryPath = preferences.projects + pathSeparator + sampleLabel;
baseDirectoryPath = baseDirectoryPath.replace("\\", "\\\\"); // Handle backslashes for Windows paths
String newLine = "baseDirectory = \"" + baseDirectoryPath + "\";";
logger.info("Replacing baseDirectory line with: " + newLine);
return newLine;
} else if (line.startsWith("imagingModality")) {
return "imagingModality = \"" + preferences.firstScanType + "-tiles\";";
} else {
Expand All @@ -482,6 +462,39 @@ class utilityFunctions {
return String.join(System.lineSeparator(), lines);
}

/**
* Modifies a Groovy script content by setting the 'createTiles' variable to false and updating
* the 'boundingBoxStageCoordinates_um' variable with provided bounding box values.
*
* @param scriptContent The content of the script to be modified as a multi-line string.
* @param boundingBox A list containing the bounding box coordinates (x1, y1, x2, y2).
* @return A string representing the modified script content.
*/
public static String boundingBoxReadyTXT(String scriptContent, List boundingBox) {
// Convert bounding box list to a string
String boundingBoxStr = boundingBox.join(", ")

// Split the script content into lines
List<String> lines = scriptContent.split(System.lineSeparator())

// Modify the script lines
List<String> modifiedLines = lines.stream()
.map(line -> {
if (line.trim().startsWith("createTiles")) {
return "createTiles = false";
} else if (line.trim().startsWith("boundingBoxStageCoordinates_um")) {
return "boundingBoxStageCoordinates_um = [" + boundingBoxStr + "]";
} else {
return line;
}
})
.collect(Collectors.toList());

// Join the modified lines into a single string
return String.join(System.lineSeparator(), modifiedLines);
}



/**
* Extracts the file path from the server path string.
Expand Down
Loading

0 comments on commit 6de7f78

Please sign in to comment.