From e74abe7cbec2dfc7f45ec1e02a1d36bcde63fcfc Mon Sep 17 00:00:00 2001 From: oburri Date: Tue, 26 Sep 2023 09:13:47 +0200 Subject: [PATCH 1/9] fixes typo --- src/main/java/qupath/ext/biop/cellpose/CellposeExtension.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/qupath/ext/biop/cellpose/CellposeExtension.java b/src/main/java/qupath/ext/biop/cellpose/CellposeExtension.java index 548a3da..dada0f1 100644 --- a/src/main/java/qupath/ext/biop/cellpose/CellposeExtension.java +++ b/src/main/java/qupath/ext/biop/cellpose/CellposeExtension.java @@ -103,6 +103,6 @@ private static void openScript(QuPathGUI qupath, String script) { logger.error("No script editor is available!"); return; } - qupath.getScriptEditor().showScript("StarDist detection", script); + qupath.getScriptEditor().showScript("Cellpose detection", script); } } \ No newline at end of file From 687a5beff486d1a6be8bbe5e6117ac31001962f7 Mon Sep 17 00:00:00 2001 From: oburri Date: Tue, 26 Sep 2023 09:14:18 +0200 Subject: [PATCH 2/9] adds troubleshooting in case of image pair writer error --- src/main/java/qupath/ext/biop/cellpose/Cellpose2D.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/java/qupath/ext/biop/cellpose/Cellpose2D.java b/src/main/java/qupath/ext/biop/cellpose/Cellpose2D.java index cdde6ba..2e918d6 100644 --- a/src/main/java/qupath/ext/biop/cellpose/Cellpose2D.java +++ b/src/main/java/qupath/ext/biop/cellpose/Cellpose2D.java @@ -1198,6 +1198,7 @@ private void saveImagePairs(List annotations, String imageName, Imag } catch (IOException ex) { logger.error(ex.getMessage()); + logger.error("Troubleshooting:\n - Check that the channel names are correct in the builder."); } }); } From 6dfd3dc8c1e3395130444776cd01a0384374c467 Mon Sep 17 00:00:00 2001 From: oburri Date: Fri, 12 Apr 2024 12:25:47 +0200 Subject: [PATCH 3/9] bumps version --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 8ec5ea3..5910529 100644 --- a/build.gradle +++ b/build.gradle @@ -25,7 +25,7 @@ ext.qupathVersion = gradle.ext.qupathVersion description = 'QuPath extension to use Cellpose' -version = "0.9.2" +version = "0.9.3-SNAPSHOT" dependencies { implementation "io.github.qupath:qupath-gui-fx:${qupathVersion}" From d6f8561fa2983cbeccef250c9332bceabda8d61f Mon Sep 17 00:00:00 2001 From: oburri Date: Fri, 12 Apr 2024 12:32:08 +0200 Subject: [PATCH 4/9] adds option to deactivate GPU in builder --- .../java/qupath/ext/biop/cellpose/Cellpose2D.java | 3 ++- .../qupath/ext/biop/cellpose/CellposeBuilder.java | 14 ++++++++++++++ 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/src/main/java/qupath/ext/biop/cellpose/Cellpose2D.java b/src/main/java/qupath/ext/biop/cellpose/Cellpose2D.java index 641143e..120f261 100644 --- a/src/main/java/qupath/ext/biop/cellpose/Cellpose2D.java +++ b/src/main/java/qupath/ext/biop/cellpose/Cellpose2D.java @@ -109,6 +109,7 @@ public class Cellpose2D { private final static Logger logger = LoggerFactory.getLogger(Cellpose2D.class); public ImageOp extendChannelOp; + public boolean useGPU; protected double simplifyDistance = 1.4; @@ -746,7 +747,7 @@ private LinkedHashMap runCellpose(LinkedHashMap cellposeArguments.add("--no_npy"); - cellposeArguments.add("--use_gpu"); + if( this.useGPU ) cellposeArguments.add("--use_gpu"); cellposeArguments.add("--verbose"); diff --git a/src/main/java/qupath/ext/biop/cellpose/CellposeBuilder.java b/src/main/java/qupath/ext/biop/cellpose/CellposeBuilder.java index c2d5533..0b3ab3f 100644 --- a/src/main/java/qupath/ext/biop/cellpose/CellposeBuilder.java +++ b/src/main/java/qupath/ext/biop/cellpose/CellposeBuilder.java @@ -95,6 +95,7 @@ public class CellposeBuilder { private ImageOp extendChannelOp = null; private boolean doReadResultsAsynchronously = false; + private boolean useGPU = true; /** * can create a cellpose builder from a serialized JSON version of this builder. @@ -134,9 +135,20 @@ protected CellposeBuilder(String modelPath) { } + /** + * overwrite use GPU + * @param useGPU add or remove the option + * @return this builder + */ + public CellposeBuilder useGPU( boolean useGPU ) { + this.useGPU = useGPU; + + return this; + } /** * Specify the training directory + * */ public CellposeBuilder groundTruthDirectory(File groundTruthDirectory) { this.groundTruthDirectory = groundTruthDirectory; @@ -771,6 +783,8 @@ public Cellpose2D build() { // Give it the number of threads to use cellpose.nThreads = nThreads; + cellpose.useGPU = useGPU; + // Check the model. If it is a file, then it is a custom model File file = new File(this.modelNameOrPath); if (file.exists()) { From f23a2b9360429b4d5a91b98623434047ae3c9da1 Mon Sep 17 00:00:00 2001 From: oburri Date: Fri, 12 Apr 2024 12:35:01 +0200 Subject: [PATCH 5/9] adds option to explicitly wait for runner to finish --- .../java/qupath/ext/biop/cellpose/Cellpose2D.java | 10 ++++------ .../ext/biop/cmd/VirtualEnvironmentRunner.java | 13 +++++++++++-- 2 files changed, 15 insertions(+), 8 deletions(-) diff --git a/src/main/java/qupath/ext/biop/cellpose/Cellpose2D.java b/src/main/java/qupath/ext/biop/cellpose/Cellpose2D.java index 120f261..720ce73 100644 --- a/src/main/java/qupath/ext/biop/cellpose/Cellpose2D.java +++ b/src/main/java/qupath/ext/biop/cellpose/Cellpose2D.java @@ -754,7 +754,7 @@ private LinkedHashMap runCellpose(LinkedHashMap veRunner.setArguments(cellposeArguments); // Finally, we can run Cellpose - veRunner.runCommand(); + veRunner.runCommand(false); return processCellposeFiles(veRunner, allTiles); @@ -926,10 +926,7 @@ private void runTraining() throws IOException, InterruptedException { veRunner.setArguments(cellposeArguments); // Finally, we can run Cellpose - veRunner.runCommand(); - - // Wait for the process to finish - veRunner.getProcess().waitFor(); + veRunner.runCommand(true); // Get the log this.theLog = veRunner.getProcessLog(); @@ -990,7 +987,8 @@ private ResultsTable runCellposeQC() throws IOException, InterruptedException { qcRunner.setArguments(qcArguments); - qcRunner.runCommand(); + qcRunner.runCommand(true); + // The results are stored in the validation directory, open them as a results table File qcResults = new File( getValidationDirectory(), "QC-Results" + File.separator + "Quality_Control for " + this.modelFile.getName() + ".csv"); diff --git a/src/main/java/qupath/ext/biop/cmd/VirtualEnvironmentRunner.java b/src/main/java/qupath/ext/biop/cmd/VirtualEnvironmentRunner.java index 6c56836..ece4ae1 100644 --- a/src/main/java/qupath/ext/biop/cmd/VirtualEnvironmentRunner.java +++ b/src/main/java/qupath/ext/biop/cmd/VirtualEnvironmentRunner.java @@ -123,10 +123,10 @@ public void setArguments(List arguments) { /** * This builds, runs the command and outputs it to the logger as it is being run - * + * @param wait whether to wait for the process to end or not before exiting this command * @throws IOException // In case there is an issue starting the process */ - public void runCommand() throws IOException { + public void runCommand(boolean waitUntilDone) throws IOException { // Get how to start the command, based on the VENV Type List command = getActivationCommand(); @@ -207,6 +207,15 @@ public void run() { logger.info("Virtual Environment Runner Started"); + + // If we ask to wait, let's wait directly here rather than handle it outside + if(waitUntilDone) { + try { + this.process.waitFor(); + } catch (InterruptedException e) { + logger.error(e.getMessage()); + } + } } public Process getProcess() { From f1ffa118d160b185aefee64e343a60a86a692125 Mon Sep 17 00:00:00 2001 From: oburri Date: Fri, 12 Apr 2024 12:38:11 +0200 Subject: [PATCH 6/9] tries to do better logging in case we have no objects to recover --- .../java/qupath/ext/biop/cellpose/Cellpose2D.java | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/src/main/java/qupath/ext/biop/cellpose/Cellpose2D.java b/src/main/java/qupath/ext/biop/cellpose/Cellpose2D.java index 720ce73..cc13639 100644 --- a/src/main/java/qupath/ext/biop/cellpose/Cellpose2D.java +++ b/src/main/java/qupath/ext/biop/cellpose/Cellpose2D.java @@ -413,6 +413,11 @@ public void detectObjectsImpl(ImageData imageData, Collection> candidatesPerParent = allTiles.values().stream() .flatMap(t -> t.getCandidates().stream()) @@ -762,10 +767,17 @@ private LinkedHashMap runCellpose(LinkedHashMap private LinkedHashMap processCellposeFiles(VirtualEnvironmentRunner veRunner, LinkedHashMap allTiles) throws CancellationException, InterruptedException, IOException { + // Make sure that allTiles is not null, if it is, just return null + // as we are likely just running validation and thus do not need to give any results back + if (allTiles == null ) { + veRunner.getProcess().waitFor(); + return null; + } + // Build a thread pool to process reading the images in parallel ExecutorService executor = Executors.newFixedThreadPool(5); - if (!this.doReadResultsAsynchronously || allTiles == null) { + if (!this.doReadResultsAsynchronously) { // We need to wait for the process to finish veRunner.getProcess().waitFor(); allTiles.entrySet().forEach(entry -> { From 29fc323d769253a5ffd8cf2a40039263ed42e1f2 Mon Sep 17 00:00:00 2001 From: oburri Date: Fri, 12 Apr 2024 12:40:28 +0200 Subject: [PATCH 7/9] fixes log parsing during training adapts regex string to match new cellpose version and also omnipose's log --- .../qupath/ext/biop/cellpose/Cellpose2D.java | 54 ++++++++++++++----- 1 file changed, 40 insertions(+), 14 deletions(-) diff --git a/src/main/java/qupath/ext/biop/cellpose/Cellpose2D.java b/src/main/java/qupath/ext/biop/cellpose/Cellpose2D.java index cc13639..01c1941 100644 --- a/src/main/java/qupath/ext/biop/cellpose/Cellpose2D.java +++ b/src/main/java/qupath/ext/biop/cellpose/Cellpose2D.java @@ -1058,20 +1058,25 @@ private ResultsTable parseTrainingResults() { if (this.theLog != null) { // Try to parse the output of Cellpose to give meaningful information to the user. This is very old school - // Look for "Epoch 0, Time 2.3s, Loss 1.0758, Loss Test 0.6007, LR 0.2000" - String epochPattern = ".*Epoch\\s*(\\d+),\\s*Time\\s*(\\d+\\.\\d)s,\\s*Loss\\s*(\\d+\\.\\d+),\\s*Loss Test\\s*(\\d+\\.\\d+),\\s*LR\\s*(\\d+\\.\\d+).*"; - // Build Matcher - Pattern pattern = Pattern.compile(epochPattern); - Matcher m; for (String line : this.theLog) { - m = pattern.matcher(line); - if (m.find()) { - trainingResults.incrementCounter(); - trainingResults.addValue("Epoch", Double.parseDouble(m.group(1))); - trainingResults.addValue("Time[s]", Double.parseDouble(m.group(2))); - trainingResults.addValue("Loss", Double.parseDouble(m.group(3))); - trainingResults.addValue("Loss Test", Double.parseDouble(m.group(4))); - trainingResults.addValue("LR", Double.parseDouble(m.group(5))); + Matcher m; + for (LogParser parser : LogParser.values()) { + m = parser.getPattern().matcher(line); + if (m.find()) { + trainingResults.incrementCounter(); + trainingResults.addValue("Epoch", Double.parseDouble(m.group("epoch"))); + trainingResults.addValue("Time", Double.parseDouble(m.group("time"))); + trainingResults.addValue("Loss", Double.parseDouble(m.group("loss"))); + if (parser != LogParser.OMNI) { // Omnipose does not provide validation loss + trainingResults.addValue("Validation Loss", Double.parseDouble(m.group("val"))); + trainingResults.addValue("LR", Double.parseDouble(m.group("lr"))); + + } else { + trainingResults.addValue("Validation Loss", Double.NaN); + trainingResults.addValue("LR", Double.NaN); + + } + } } } } @@ -1115,7 +1120,7 @@ public void showTrainingGraph(boolean show, boolean save) { //populating the series with data for (int i = 0; i < output.getCounter(); i++) { loss.getData().add(new XYChart.Data<>(output.getValue("Epoch", i), output.getValue("Loss", i))); - lossTest.getData().add(new XYChart.Data<>(output.getValue("Epoch", i), output.getValue("Loss Test", i))); + lossTest.getData().add(new XYChart.Data<>(output.getValue("Epoch", i), output.getValue("Validation Loss", i))); } lineChart.getData().add(loss); @@ -1435,4 +1440,25 @@ private static class CandidateObject { geometry = geometry.getGeometryN(index); } } + public enum LogParser { + + // Cellpose 2 pattern when training : "Look for "Epoch 0, Time 2.3s, Loss 1.0758, Loss Test 0.6007, LR 0.2000" + // Cellpose 3 pattern when training : "5, train_loss=2.6546, test_loss=2.0054, LR=0.1111, time 2.56s" + // Omnipose pattern when training : "Train epoch: 10 | Time: 0.22min | last epoch: 0.74s | : 0.73s | : 0.33s | : 5.076259 | : 4.429341" + // WARNING: Currently Omnipose does not provide any output to the validation loss (Test loss in Cellpose) + CP2("Cellpose v2", ".*Epoch\\s*(?\\d+),\\s*Time\\s*(?