-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Restructuring and additional features
Split utilityFunctions into three different types of functions. Modified some of the GUI elements to be more accurate in their descriptions. Completed change where TileConfiguration.txt files can be used instead of CSV files for handling tiles. Swapped to affine transformations from handling modifications to objects and coordinates line by line. Still an issue with the resulting tile size.
- Loading branch information
1 parent
6de7f78
commit 7cb98a7
Showing
9 changed files
with
570 additions
and
423 deletions.
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
271 changes: 154 additions & 117 deletions
271
src/main/groovy/qupath/ext/qp_scope/functions/QP_scope_GUI.groovy
Large diffs are not rendered by default.
Oops, something went wrong.
106 changes: 106 additions & 0 deletions
106
src/main/groovy/qupath/ext/qp_scope/utilities/minorFunctions.groovy
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,106 @@ | ||
package qupath.ext.qp_scope.utilities | ||
|
||
import javafx.scene.control.Alert | ||
import javafx.stage.Modality | ||
import org.slf4j.LoggerFactory | ||
|
||
import java.nio.file.Files | ||
import java.nio.file.Path | ||
import java.nio.file.Paths | ||
import java.util.regex.Matcher | ||
import java.util.regex.Pattern | ||
|
||
class minorFunctions { | ||
static final logger = LoggerFactory.getLogger(minorFunctions.class) | ||
|
||
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() | ||
} | ||
/** | ||
* 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. | ||
* The naming starts with _1 and increments for each additional folder with a similar base name. | ||
* | ||
* @param originalFolderPath The original folder path. | ||
* @return A unique folder name. | ||
*/ | ||
static String getUniqueFolderName(String originalFolderPath) { | ||
Path path = Paths.get(originalFolderPath) | ||
Path parentDir = path.getParent() | ||
String baseName = path.getFileName().toString() | ||
|
||
int counter = 1 | ||
Path newPath = parentDir.resolve(baseName + "_" + counter) | ||
|
||
// Check for existing folders with the same base name and increment counter | ||
while (Files.exists(newPath)) { | ||
counter++ | ||
newPath = parentDir.resolve(baseName + "_" + counter) | ||
} | ||
|
||
// Return only the unique folder name, not the full path | ||
return newPath.getFileName().toString() | ||
} | ||
|
||
private static int getNextImagingModalityIndex(String baseDirectoryPath, String firstScanType) { | ||
File directory = new File(baseDirectoryPath) | ||
if (!directory.exists() || !directory.isDirectory()) { | ||
return 1 // If directory doesn't exist or isn't a directory, start with index 1 | ||
} | ||
|
||
// Filter directories that match the pattern and find the highest index | ||
int maxIndex = Arrays.stream(directory.listFiles()) | ||
.filter(File::isDirectory) | ||
.map(File::getName) | ||
.filter(name -> name.startsWith(firstScanType + "_")) | ||
.map(name -> { | ||
try { | ||
return Integer.parseInt(name.substring(name.lastIndexOf('_') + 1)) | ||
} catch (NumberFormatException e) { | ||
return 0 // If the part after '_' is not a number, return 0 | ||
} | ||
}) | ||
.max(Integer::compare) | ||
.orElse(0) // If no matching directories, start with index 1 | ||
|
||
return maxIndex + 1 // Increment the index for the next modality | ||
} | ||
/** | ||
* Extracts the file path from the server path string. | ||
* | ||
* @param serverPath The server path string. | ||
* @return The extracted file path, or null if the path could not be extracted. | ||
*/ | ||
static String extractFilePath(String serverPath) { | ||
// Regular expression to match the file path | ||
String regex = "file:/(.*?\\.TIF)" | ||
|
||
// Create a pattern and matcher for the regular expression | ||
Pattern pattern = Pattern.compile(regex) | ||
Matcher matcher = pattern.matcher(serverPath) | ||
|
||
// Check if the pattern matches and return the file path | ||
if (matcher.find()) { | ||
return matcher.group(1).replaceFirst("^/", "").replaceAll("%20", " ") | ||
} else { | ||
return null // No match found | ||
} | ||
} | ||
static double parseDoubleSafely(String str) { | ||
try { | ||
return str?.trim()?.toDouble() ?: 0.0 | ||
} catch (NumberFormatException e) { | ||
logger.error("NumberFormatException in parsing string to double: ${e.message}") | ||
return 0.0 | ||
} | ||
} | ||
|
||
} |
177 changes: 177 additions & 0 deletions
177
src/main/groovy/qupath/ext/qp_scope/utilities/transformationFunctions.groovy
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,177 @@ | ||
package qupath.ext.qp_scope.utilities | ||
|
||
import org.slf4j.LoggerFactory | ||
import qupath.lib.objects.PathObject | ||
|
||
import java.awt.geom.AffineTransform | ||
import java.awt.geom.Point2D | ||
import java.util.regex.Matcher | ||
import java.util.regex.Pattern | ||
|
||
class transformationFunctions { | ||
static final logger = LoggerFactory.getLogger(transformationFunctions.class) | ||
|
||
//Convert the QuPath pixel based coordinates for a location into the MicroManager micron based stage coordinates | ||
|
||
static List<Double> QPtoMicroscopeCoordinates(List<Double> qpCoordinates, AffineTransform transformation) { | ||
Point2D.Double sourcePoint = new Point2D.Double(qpCoordinates[0], qpCoordinates[1]) | ||
Point2D.Double destPoint = new Point2D.Double() | ||
|
||
transformation.transform(sourcePoint, destPoint) | ||
|
||
return [destPoint.x, destPoint.y] | ||
} | ||
|
||
/** | ||
* Transforms the coordinates in TileConfiguration.txt files located in all child directories | ||
* of a specified parent directory, using an AffineTransform. It reads each file, applies the | ||
* transformation to each tile's coordinates, and writes the transformed coordinates back to a | ||
* new file in each directory. | ||
* | ||
* @param parentDirPath The path to the parent directory containing child directories with TileConfiguration.txt files. | ||
* @param transformation The AffineTransform to be applied to each tile's coordinates. | ||
* @return A list of folder names that contain TileConfiguration.txt files which were modified. | ||
*/ | ||
static List<String> transformTileConfiguration(String parentDirPath, AffineTransform transformation) { | ||
logger.info("entering transform Tileconfiguration modification function") | ||
logger.info(parentDirPath) | ||
logger.info(transformation.toString()) | ||
System.out.println("AffineTransform: " + transformation) | ||
|
||
File parentDir = new File(parentDirPath) | ||
List<String> modifiedFolders = [] | ||
|
||
// Check if the path is a valid directory | ||
if (!parentDir.isDirectory()) { | ||
System.err.println("Provided path is not a directory: $parentDirPath") | ||
return modifiedFolders | ||
} | ||
|
||
// Iterate over all child folders | ||
File[] subdirectories = parentDir.listFiles(new FileFilter() { | ||
@Override | ||
boolean accept(File file) { | ||
return file.isDirectory() | ||
} | ||
}) | ||
|
||
if (subdirectories) { | ||
subdirectories.each { File subdir -> | ||
File tileConfigFile = new File(subdir, "TileConfiguration.txt") | ||
if (tileConfigFile.exists()) { | ||
// Process the TileConfiguration.txt file | ||
processTileConfigurationFile(tileConfigFile, transformation) | ||
modifiedFolders.add(subdir.name) | ||
} | ||
} | ||
} | ||
|
||
return modifiedFolders | ||
} | ||
|
||
private static void processTileConfigurationFile(File tileConfigFile, AffineTransform transformation) { | ||
List<String> transformedLines = [] | ||
Pattern pattern = Pattern.compile("\\d+\\.tif; ; \\((.*),\\s*(.*)\\)") | ||
|
||
tileConfigFile.eachLine { line -> | ||
Matcher m = pattern.matcher(line) | ||
if (m.find()) { // Use 'find()' to search for a match in the line | ||
double x1 = Double.parseDouble(m.group(1)) | ||
double y1 = Double.parseDouble(m.group(2)) | ||
List<Double> qpCoordinates = [x1, y1] | ||
List<Double> transformedCoords = QPtoMicroscopeCoordinates(qpCoordinates, transformation) | ||
transformedLines.add(line.replaceFirst("\\(.*\\)", "(${transformedCoords[0]}, ${transformedCoords[1]})")) | ||
} else { | ||
transformedLines.add(line) // Add line as is if no coordinate match | ||
} | ||
} | ||
|
||
|
||
// Write the transformed lines to a new file | ||
File newTileConfigFile = new File(tileConfigFile.getParent(), "TileConfiguration_transformed.txt") | ||
newTileConfigFile.withWriter { writer -> | ||
transformedLines.each { writer.println(it) } | ||
} | ||
} | ||
|
||
/** | ||
* Updates an AffineTransform based on the difference between coordinates in QPath and microscope stage. | ||
* It applies the existing transformation to the QPath coordinates and then adjusts the transformation | ||
* to align these with the given microscope stage coordinates. | ||
* | ||
* @param transformation The current AffineTransform object. | ||
* @param coordinatesQP List of QPath coordinates (as Strings) to be transformed. | ||
* @param coordinatesMM List of microscope stage coordinates (as Strings) for alignment. | ||
* @return An updated AffineTransform object that reflects the necessary shift to align QPath coordinates | ||
* with microscope stage coordinates after scaling. | ||
*/ | ||
//TODO adjust for situations where the macro image is flipped | ||
static AffineTransform updateTransformation(AffineTransform transformation, List<String> coordinatesQP, List<String> coordinatesMM) { | ||
// Convert coordinatesQP and coordinatesMM elements from String to Double | ||
double xQP = coordinatesQP[0].toDouble() | ||
double yQP = coordinatesQP[1].toDouble() | ||
double xMM = coordinatesMM[0].toDouble() | ||
double yMM = coordinatesMM[1].toDouble() | ||
|
||
// Apply the existing transformation to the QP coordinates | ||
Point2D.Double transformedPoint = new Point2D.Double() | ||
transformation.transform(new Point2D.Double(xQP, yQP), transformedPoint) | ||
|
||
// Calculate the additional translation needed | ||
double additionalXShift = xMM - transformedPoint.x | ||
double additionalYShift = yMM - transformedPoint.y | ||
|
||
logger.info("Additional xShift: $additionalXShift") | ||
logger.info("Additional yShift: $additionalYShift") | ||
|
||
// Create a new AffineTransform that includes this additional translation | ||
AffineTransform updatedTransformation = new AffineTransform(transformation) | ||
updatedTransformation.translate(additionalXShift, additionalYShift) | ||
|
||
return updatedTransformation | ||
} | ||
|
||
|
||
static List<Object> getTopCenterTile(Collection<PathObject> detections) { | ||
// Filter out null detections and sort by Y-coordinate | ||
List<PathObject> sortedDetections = detections.findAll { it != null } | ||
.sort { it.getROI().getCentroidY() } | ||
|
||
// Get the minimum Y-coordinate (top tiles) | ||
double minY = sortedDetections.first().getROI().getCentroidY() | ||
|
||
// Get all tiles that are at the top | ||
List<PathObject> topTiles = sortedDetections.findAll { it.getROI().getCentroidY() == minY } | ||
|
||
// Find the median X-coordinate of the top tiles | ||
List<Double> xCoordinates = topTiles.collect { it.getROI().getCentroidX() } | ||
double medianX = xCoordinates.sort()[xCoordinates.size() / 2] | ||
|
||
// Select the top tile closest to the median X-coordinate | ||
PathObject topCenterTile = topTiles.min { Math.abs(it.getROI().getCentroidX() - medianX) } | ||
|
||
return [topCenterTile.getROI().getCentroidX(), topCenterTile.getROI().getCentroidY(), topCenterTile] | ||
} | ||
|
||
static List<Object> getLeftCenterTile(Collection<PathObject> detections) { | ||
// Filter out null detections and sort by X-coordinate | ||
List<PathObject> sortedDetections = detections.findAll { it != null } | ||
.sort { it.getROI().getCentroidX() } | ||
|
||
// Get the minimum X-coordinate (left tiles) | ||
double minX = sortedDetections.first().getROI().getCentroidX() | ||
|
||
// Get all tiles that are at the left | ||
List<PathObject> leftTiles = sortedDetections.findAll { it.getROI().getCentroidX() == minX } | ||
|
||
// Find the median Y-coordinate of the left tiles | ||
List<Double> yCoordinates = leftTiles.collect { it.getROI().getCentroidY() } | ||
double medianY = yCoordinates.sort()[yCoordinates.size() / 2] | ||
|
||
// Select the left tile closest to the median Y-coordinate | ||
PathObject leftCenterTile = leftTiles.min { Math.abs(it.getROI().getCentroidY() - medianY) } | ||
|
||
return [leftCenterTile.getROI().getCentroidX(), leftCenterTile.getROI().getCentroidY(), leftCenterTile] | ||
} | ||
|
||
} |
Oops, something went wrong.