From 65a978dfa4eeb3bdba647da30f213102f93c3eb4 Mon Sep 17 00:00:00 2001 From: Alan O'Callaghan Date: Fri, 8 Sep 2023 13:49:52 +0100 Subject: [PATCH 01/14] Draft changes for 0.5 --- build.gradle | 5 +++-- settings.gradle | 4 ++-- src/main/java/qupath/ext/wsinfer/WSInfer.java | 4 ++-- src/main/java/qupath/ext/wsinfer/ui/WSInferCommand.java | 5 +++-- src/main/java/qupath/ext/wsinfer/ui/WSInferController.java | 4 ++-- src/main/java/qupath/ext/wsinfer/ui/WSInferPrefs.java | 2 +- 6 files changed, 13 insertions(+), 11 deletions(-) diff --git a/build.gradle b/build.gradle index fea71ff..2796ee4 100644 --- a/build.gradle +++ b/build.gradle @@ -9,14 +9,14 @@ plugins { ext.moduleName = 'io.github.qupath.extension.wsinfer' -version = "0.1.0" +version = "0.2.0" description = 'An extension to run WSInfer in QuPath' // The default 'gradle.ext.qupathVersion' reads this from settings.gradle. ext.qupathVersion = gradle.ext.qupathVersion // Generally 11 for QuPath v0.4.3, but will be 17 for QuPath v0.5.0 -ext.qupathJavaVersion = 11 +ext.qupathJavaVersion = 17 def djlVersion = libs.versions.deepJavaLibrary.get() @@ -36,6 +36,7 @@ dependencies { // Main QuPath user interface jar. // Automatically includes other QuPath jars as subdependencies. implementation "io.github.qupath:qupath-gui-fx:${qupathVersion}" + implementation 'io.github.qupath:qupath-fxtras:0.1.0-SNAPSHOT' // For logging - the version comes from QuPath's version catalog at // https://github.com/qupath/qupath/blob/main/gradle/libs.versions.toml diff --git a/settings.gradle b/settings.gradle index 0da8b79..5b7f148 100644 --- a/settings.gradle +++ b/settings.gradle @@ -6,7 +6,7 @@ pluginManagement { rootProject.name = 'qupath-extension-wsinfer' -gradle.ext.qupathVersion = "0.4.3" +gradle.ext.qupathVersion = "0.5.0-SNAPSHOT" dependencyResolutionManagement { @@ -31,4 +31,4 @@ dependencyResolutionManagement { } } -} \ No newline at end of file +} diff --git a/src/main/java/qupath/ext/wsinfer/WSInfer.java b/src/main/java/qupath/ext/wsinfer/WSInfer.java index 8184e6e..89ef853 100644 --- a/src/main/java/qupath/ext/wsinfer/WSInfer.java +++ b/src/main/java/qupath/ext/wsinfer/WSInfer.java @@ -38,7 +38,7 @@ import qupath.ext.wsinfer.models.WSInferTransform; import qupath.ext.wsinfer.models.WSInferUtils; import qupath.ext.wsinfer.ui.WSInferPrefs; -import qupath.lib.gui.dialogs.Dialogs; +import qupath.lib.gui.tools.GuiTools; import qupath.lib.images.ImageData; import qupath.lib.images.servers.ImageServer; import qupath.lib.images.servers.PixelCalibration; @@ -151,7 +151,7 @@ public static void runInference(ImageData imageData, WSInferModel public static void runInference(ImageData imageData, WSInferModel wsiModel, ProgressListener progressListener) throws InterruptedException, ModelNotFoundException, MalformedModelException, IOException, TranslateException { Objects.requireNonNull(wsiModel, "Model cannot be null"); if (imageData == null) { - Dialogs.showNoImageError(resources.getString("title")); + GuiTools.showNoImageError(resources.getString("title")); } // Try to get some tiles we can use diff --git a/src/main/java/qupath/ext/wsinfer/ui/WSInferCommand.java b/src/main/java/qupath/ext/wsinfer/ui/WSInferCommand.java index 707390d..6c14ec5 100644 --- a/src/main/java/qupath/ext/wsinfer/ui/WSInferCommand.java +++ b/src/main/java/qupath/ext/wsinfer/ui/WSInferCommand.java @@ -25,7 +25,8 @@ import org.slf4j.LoggerFactory; import qupath.lib.gui.ExtensionClassLoader; import qupath.lib.gui.QuPathGUI; -import qupath.lib.gui.dialogs.Dialogs; +import qupath.fx.dialogs.Dialogs; + import java.io.IOException; import java.net.URL; @@ -72,7 +73,7 @@ private Stage createStage() throws IOException { // We need to use the ExtensionClassLoader to load the FXML, since it's in a different module var loader = new FXMLLoader(url, resources); - loader.setClassLoader(QuPathGUI.getExtensionClassLoader()); + loader.setClassLoader(this.getClass().getClassLoader()); VBox root = loader.load(); // There's probably a better approach... but wrapping in a border pane diff --git a/src/main/java/qupath/ext/wsinfer/ui/WSInferController.java b/src/main/java/qupath/ext/wsinfer/ui/WSInferController.java index 011caf1..89ec079 100644 --- a/src/main/java/qupath/ext/wsinfer/ui/WSInferController.java +++ b/src/main/java/qupath/ext/wsinfer/ui/WSInferController.java @@ -53,7 +53,7 @@ import qupath.lib.common.ThreadTools; import qupath.lib.gui.QuPathGUI; import qupath.lib.gui.commands.Commands; -import qupath.lib.gui.dialogs.Dialogs; +import qupath.fx.dialogs.Dialogs; import qupath.lib.images.ImageData; import qupath.lib.objects.PathAnnotationObject; import qupath.lib.objects.PathObject; @@ -204,7 +204,7 @@ private void configurePendingTaskProperty() { } private void configureDisplayToggleButtons() { - var actions = qupath.getDefaultActions(); + var actions = qupath.getOverlayActions(); configureActionToggleButton(actions.FILL_DETECTIONS, toggleDetectionFill); configureActionToggleButton(actions.SHOW_DETECTIONS, toggleDetections); configureActionToggleButton(actions.SHOW_ANNOTATIONS, toggleAnnotations); diff --git a/src/main/java/qupath/ext/wsinfer/ui/WSInferPrefs.java b/src/main/java/qupath/ext/wsinfer/ui/WSInferPrefs.java index 2fb6952..783271a 100644 --- a/src/main/java/qupath/ext/wsinfer/ui/WSInferPrefs.java +++ b/src/main/java/qupath/ext/wsinfer/ui/WSInferPrefs.java @@ -64,7 +64,7 @@ public static Property numWorkersProperty() { } private static String getUserDir() { - String userPath = PathPrefs.getUserPath(); + String userPath = PathPrefs.userPathProperty().get(); String cachePath = System.getProperty("user.dir") + File.separator + ".cache" + File.separator + "QuPath"; return userPath == null || userPath.isEmpty() ? cachePath : userPath; } From 4106c3c4924c0b4e69ce965d3b0534c00ca12de7 Mon Sep 17 00:00:00 2001 From: Alan O'Callaghan Date: Wed, 20 Sep 2023 18:37:31 +0100 Subject: [PATCH 02/14] Implement GitHubProject --- src/main/java/qupath/ext/wsinfer/WSInferExtension.java | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/main/java/qupath/ext/wsinfer/WSInferExtension.java b/src/main/java/qupath/ext/wsinfer/WSInferExtension.java index be0df76..da80687 100644 --- a/src/main/java/qupath/ext/wsinfer/WSInferExtension.java +++ b/src/main/java/qupath/ext/wsinfer/WSInferExtension.java @@ -23,6 +23,7 @@ import qupath.ext.wsinfer.ui.WSInferCommand; import qupath.lib.common.Version; import qupath.lib.gui.QuPathGUI; +import qupath.lib.gui.extensions.GitHubProject; import qupath.lib.gui.extensions.QuPathExtension; import qupath.lib.gui.prefs.PathPrefs; @@ -32,7 +33,7 @@ * QuPath extension to run patch-based deep learning inference with WSInfer. * See https://wsinfer.readthedocs.io for more info. */ -public class WSInferExtension implements QuPathExtension { +public class WSInferExtension implements QuPathExtension, GitHubProject { private final static ResourceBundle resources = ResourceBundle.getBundle("qupath.ext.wsinfer.ui.strings"); private final static Logger logger = LoggerFactory.getLogger(WSInferExtension.class); @@ -84,4 +85,8 @@ public Version getQuPathVersion() { return EXTENSION_QUPATH_VERSION; } + @Override + public GitHubRepo getRepository() { + return GitHubRepo.create(getName(), "qupath", "qupath-extension-wsinfer"); + } } From 5669b43d40518a5c89d22847cff973ce6ae4fcfa Mon Sep 17 00:00:00 2001 From: Alan O'Callaghan Date: Mon, 16 Oct 2023 18:16:09 +0200 Subject: [PATCH 03/14] JavaFX plugin --- build.gradle | 1 + 1 file changed, 1 insertion(+) diff --git a/build.gradle b/build.gradle index e1d9ab4..5541729 100644 --- a/build.gradle +++ b/build.gradle @@ -5,6 +5,7 @@ plugins { 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' + id 'org.openjfx.javafxplugin' version '0.1.0' } ext.moduleName = 'io.github.qupath.extension.wsinfer' From 6f45da11da94ca551f4d5069b985131dae5e3e3e Mon Sep 17 00:00:00 2001 From: Alan O'Callaghan Date: Mon, 16 Oct 2023 18:19:14 +0200 Subject: [PATCH 04/14] Use correct types --- src/main/java/qupath/ext/wsinfer/ui/WSInferPrefs.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/qupath/ext/wsinfer/ui/WSInferPrefs.java b/src/main/java/qupath/ext/wsinfer/ui/WSInferPrefs.java index b1a7e0e..9358a69 100644 --- a/src/main/java/qupath/ext/wsinfer/ui/WSInferPrefs.java +++ b/src/main/java/qupath/ext/wsinfer/ui/WSInferPrefs.java @@ -66,7 +66,7 @@ public static Property numWorkersProperty() { private static String getUserDir() { String userPath = UserDirectoryManager.getInstance().getUserPath(); - String cachePath = Paths.get(System.getProperty("user.dir"), ".cache", "QuPath"); - return userPath == null || userPath.isEmpty() ? cachePath : userPath; + Path cachePath = Paths.get(System.getProperty("user.dir"), ".cache", "QuPath"); + return userPath == null || userPath.isEmpty() ? cachePath : userPath.toString(); } } From f84c6886d216c6b0ac46d9f5ecbef5640fb678df Mon Sep 17 00:00:00 2001 From: Alan O'Callaghan Date: Mon, 16 Oct 2023 18:22:32 +0200 Subject: [PATCH 05/14] Actually test changes --- src/main/java/qupath/ext/wsinfer/ui/WSInferPrefs.java | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/main/java/qupath/ext/wsinfer/ui/WSInferPrefs.java b/src/main/java/qupath/ext/wsinfer/ui/WSInferPrefs.java index 9358a69..d8a4648 100644 --- a/src/main/java/qupath/ext/wsinfer/ui/WSInferPrefs.java +++ b/src/main/java/qupath/ext/wsinfer/ui/WSInferPrefs.java @@ -19,9 +19,11 @@ import javafx.beans.property.Property; import javafx.beans.property.StringProperty; import qupath.lib.gui.prefs.PathPrefs; +import qupath.lib.gui.UserDirectoryManager; import java.io.File; import java.nio.file.Paths; +import java.nio.file.Path; /** * Class to store preferences associated with WSInfer. @@ -30,7 +32,7 @@ public class WSInferPrefs { private static final StringProperty modelDirectoryProperty = PathPrefs.createPersistentPreference( "wsinfer.model.dir", - Paths.get(getUserDir(),"wsinfer").toString() + Paths.get(getUserDir().toString(),"wsinfer").toString() ); private static final StringProperty deviceProperty = PathPrefs.createPersistentPreference( @@ -64,9 +66,9 @@ public static Property numWorkersProperty() { return numWorkersProperty; } - private static String getUserDir() { - String userPath = UserDirectoryManager.getInstance().getUserPath(); + private static Path getUserDir() { + Path userPath = UserDirectoryManager.getInstance().getUserPath(); Path cachePath = Paths.get(System.getProperty("user.dir"), ".cache", "QuPath"); - return userPath == null || userPath.isEmpty() ? cachePath : userPath.toString(); + return userPath == null || userPath.toString().isEmpty() ? cachePath : userPath; } } From fb829cb632664bf2576f47335bd7301e56208530 Mon Sep 17 00:00:00 2001 From: Alan O'Callaghan Date: Mon, 30 Oct 2023 15:03:03 +0000 Subject: [PATCH 06/14] Implement local model support, see #32 --- .../qupath/ext/wsinfer/WSInferExtension.java | 2 - .../ext/wsinfer/models/WSInferModel.java | 8 ++-- .../models/WSInferModelCollection.java | 2 +- .../ext/wsinfer/models/WSInferModelLocal.java | 35 ++++++++++++++++++ .../ext/wsinfer/models/WSInferUtils.java | 19 ++++++++++ .../ext/wsinfer/ui/WSInferController.java | 37 ++++++++++++------- .../qupath/ext/wsinfer/ui/WSInferPrefs.java | 14 +++++++ .../qupath/ext/wsinfer/ui/strings.properties | 4 +- .../ext/wsinfer/ui/wsinfer_control.fxml | 24 ++++++++---- 9 files changed, 117 insertions(+), 28 deletions(-) create mode 100644 src/main/java/qupath/ext/wsinfer/models/WSInferModelLocal.java diff --git a/src/main/java/qupath/ext/wsinfer/WSInferExtension.java b/src/main/java/qupath/ext/wsinfer/WSInferExtension.java index da80687..02a4f16 100644 --- a/src/main/java/qupath/ext/wsinfer/WSInferExtension.java +++ b/src/main/java/qupath/ext/wsinfer/WSInferExtension.java @@ -49,8 +49,6 @@ public class WSInferExtension implements QuPathExtension, GitHubProject { private final BooleanProperty enableExtensionProperty = PathPrefs.createPersistentPreference( "enableExtension", true); - - @Override public void installExtension(QuPathGUI qupath) { if (isInstalled) { diff --git a/src/main/java/qupath/ext/wsinfer/models/WSInferModel.java b/src/main/java/qupath/ext/wsinfer/models/WSInferModel.java index cddaaec..9ca3154 100644 --- a/src/main/java/qupath/ext/wsinfer/models/WSInferModel.java +++ b/src/main/java/qupath/ext/wsinfer/models/WSInferModel.java @@ -1,4 +1,4 @@ -/** + /** * Copyright 2023 University of Edinburgh * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -42,10 +42,10 @@ public class WSInferModel { private WSInferModelConfiguration configuration; @SerializedName("hf_repo_id") - private String hfRepoId; + String hfRepoId; @SerializedName("hf_revision") - private String hfRevision; + String hfRevision; public String getName() { return hfRepoId; @@ -118,7 +118,7 @@ private File getFile(String f) { return Paths.get(getModelDirectory().toString(), f).toFile(); } - private File getModelDirectory() { + File getModelDirectory() { return Paths.get(WSInferPrefs.modelDirectoryProperty().get(), hfRepoId, hfRevision).toFile(); } diff --git a/src/main/java/qupath/ext/wsinfer/models/WSInferModelCollection.java b/src/main/java/qupath/ext/wsinfer/models/WSInferModelCollection.java index 4a991c9..626cd76 100644 --- a/src/main/java/qupath/ext/wsinfer/models/WSInferModelCollection.java +++ b/src/main/java/qupath/ext/wsinfer/models/WSInferModelCollection.java @@ -33,6 +33,6 @@ public class WSInferModelCollection { * @return */ public Map getModels() { - return Collections.unmodifiableMap(models); + return Collections.synchronizedMap(models); } } diff --git a/src/main/java/qupath/ext/wsinfer/models/WSInferModelLocal.java b/src/main/java/qupath/ext/wsinfer/models/WSInferModelLocal.java new file mode 100644 index 0000000..e469af5 --- /dev/null +++ b/src/main/java/qupath/ext/wsinfer/models/WSInferModelLocal.java @@ -0,0 +1,35 @@ +package qupath.ext.wsinfer.models; + +import qupath.ext.wsinfer.ui.WSInferPrefs; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Paths; + +public class WSInferModelLocal extends WSInferModel { + + private final File modelDirectory; + + public WSInferModelLocal(File modelDirectory) { + this.modelDirectory = modelDirectory; + this.hfRepoId = modelDirectory.getName(); + // todo: load any files, populate fields from them. + } + + @Override + File getModelDirectory() { + return this.modelDirectory; + } + + @Override + public boolean isValid() { + return getTSFile().exists() && getConfiguration() != null; + } + + @Override + public synchronized void downloadModel() {} + + @Override + public synchronized void removeCache() {} +} diff --git a/src/main/java/qupath/ext/wsinfer/models/WSInferUtils.java b/src/main/java/qupath/ext/wsinfer/models/WSInferUtils.java index 05f72c0..c24b721 100644 --- a/src/main/java/qupath/ext/wsinfer/models/WSInferUtils.java +++ b/src/main/java/qupath/ext/wsinfer/models/WSInferUtils.java @@ -33,6 +33,7 @@ import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; +import java.util.Objects; /** * Utility class to help with working with WSInfer models. @@ -64,9 +65,27 @@ public static WSInferModelCollection getModelCollection() { cachedModelCollection = downloadModelCollection(); } } + String localModelDirectory = WSInferPrefs.localDirectoryProperty().get(); + if (localModelDirectory != null) { + addLocalModels(cachedModelCollection, localModelDirectory); + } return cachedModelCollection; } + private static void addLocalModels(WSInferModelCollection cachedModelCollection, String localModelDirectory) { + File modelDir = new File(localModelDirectory); + if (!modelDir.exists() || !modelDir.isDirectory()) { + return; + } + for (var model: Objects.requireNonNull(modelDir.listFiles())) { + var localModel = new WSInferModelLocal(model); + System.out.println(model); + System.out.println(localModel.getName()); + System.out.println(cachedModelCollection.getModels()); + cachedModelCollection.getModels().put(localModel.getName(), localModel); + } + } + /** * Download the model collection from the hugging face repo. * This replaces any previously cached version. diff --git a/src/main/java/qupath/ext/wsinfer/ui/WSInferController.java b/src/main/java/qupath/ext/wsinfer/ui/WSInferController.java index 70866b1..0dce1bc 100644 --- a/src/main/java/qupath/ext/wsinfer/ui/WSInferController.java +++ b/src/main/java/qupath/ext/wsinfer/ui/WSInferController.java @@ -25,7 +25,6 @@ import javafx.beans.property.ObjectProperty; import javafx.beans.property.SimpleIntegerProperty; import javafx.beans.property.SimpleObjectProperty; -import javafx.beans.value.ObservableBooleanValue; import javafx.beans.value.ObservableValue; import javafx.concurrent.Task; import javafx.concurrent.Worker; @@ -51,10 +50,10 @@ import qupath.ext.wsinfer.models.WSInferModel; import qupath.ext.wsinfer.models.WSInferModelCollection; import qupath.ext.wsinfer.models.WSInferUtils; +import qupath.fx.dialogs.Dialogs; import qupath.lib.common.ThreadTools; import qupath.lib.gui.QuPathGUI; import qupath.lib.gui.commands.Commands; -import qupath.fx.dialogs.Dialogs; import qupath.lib.images.ImageData; import qupath.lib.objects.PathAnnotationObject; import qupath.lib.objects.PathObject; @@ -83,7 +82,7 @@ public class WSInferController { private static final Logger logger = LoggerFactory.getLogger(WSInferController.class); public QuPathGUI qupath; - private ObjectProperty> imageDataProperty = new SimpleObjectProperty<>(); + private final ObjectProperty> imageDataProperty = new SimpleObjectProperty<>(); private MessageTextHelper messageTextHelper; @FXML @@ -112,6 +111,8 @@ public class WSInferController { private Spinner spinnerNumWorkers; @FXML private TextField tfModelDirectory; + @FXML + private TextField localModelDirectory; private final static ResourceBundle resources = ResourceBundle.getBundle("qupath.ext.wsinfer.ui.strings"); @@ -242,6 +243,8 @@ private void configureActionToggleButton(Action action, ToggleButton button) { private void configureModelDirectory() { tfModelDirectory.textProperty().bindBidirectional(WSInferPrefs.modelDirectoryProperty()); + localModelDirectory.textProperty().bindBidirectional(WSInferPrefs.localDirectoryProperty()); + localModelDirectory.textProperty().addListener((v, o, n) -> configureModelChoices()); } private void configureNumWorkers() { @@ -391,7 +394,7 @@ private WSInferTask(ImageData imageData, WSInferModel model) { } @Override - protected Void call() throws Exception { + protected Void call() { try { // Ensure PyTorch engine is available if (!PytorchManager.hasPyTorchEngine()) { @@ -428,7 +431,7 @@ protected Void call() throws Exception { */ private class MessageTextHelper { - private SelectedObjectCounter selectedObjectCounter; + private final SelectedObjectCounter selectedObjectCounter; /** * Text to display a warning (because inference can't be run) @@ -516,14 +519,14 @@ private String getWarningText() { */ private static class SelectedObjectCounter { - private ObjectProperty> imageDataProperty = new SimpleObjectProperty<>(); + private final ObjectProperty> imageDataProperty = new SimpleObjectProperty<>(); - private PathObjectSelectionListener selectionListener = this::selectedPathObjectChanged; + private final PathObjectSelectionListener selectionListener = this::selectedPathObjectChanged; - private ObservableValue hierarchyProperty; + private final ObservableValue hierarchyProperty; - private IntegerProperty numSelectedAnnotations = new SimpleIntegerProperty(); - private IntegerProperty numSelectedDetections = new SimpleIntegerProperty(); + private final IntegerProperty numSelectedAnnotations = new SimpleIntegerProperty(); + private final IntegerProperty numSelectedDetections = new SimpleIntegerProperty(); SelectedObjectCounter(ObservableValue> imageDataProperty) { this.imageDataProperty.bind(imageDataProperty); @@ -561,8 +564,16 @@ private void updateSelectedObjectCounts() { numSelectedDetections.set(0); } else { var selected = hierarchy.getSelectionModel().getSelectedObjects(); - numSelectedAnnotations.set((int)selected.stream().filter(p -> p.isAnnotation()).count()); - numSelectedDetections.set((int)selected.stream().filter(p -> p.isDetection()).count()); + numSelectedAnnotations.set( + (int)selected + .stream().filter(PathObject::isAnnotation) + .count() + ); + numSelectedDetections.set( + (int)selected + .stream().filter(PathObject::isDetection) + .count() + ); } } @@ -570,7 +581,7 @@ private void updateSelectedObjectCounts() { private static class ModelStringConverter extends StringConverter { - private WSInferModelCollection models; + private final WSInferModelCollection models; private ModelStringConverter(WSInferModelCollection models) { Objects.requireNonNull(models, "Models cannot be null"); diff --git a/src/main/java/qupath/ext/wsinfer/ui/WSInferPrefs.java b/src/main/java/qupath/ext/wsinfer/ui/WSInferPrefs.java index d8a4648..b3c27d8 100644 --- a/src/main/java/qupath/ext/wsinfer/ui/WSInferPrefs.java +++ b/src/main/java/qupath/ext/wsinfer/ui/WSInferPrefs.java @@ -16,7 +16,9 @@ package qupath.ext.wsinfer.ui; +import javafx.beans.property.BooleanProperty; import javafx.beans.property.Property; +import javafx.beans.property.SimpleStringProperty; import javafx.beans.property.StringProperty; import qupath.lib.gui.prefs.PathPrefs; import qupath.lib.gui.UserDirectoryManager; @@ -44,6 +46,9 @@ public class WSInferPrefs { "wsinfer.numWorkers", 1 ).asObject(); + private static StringProperty localDirectoryProperty = PathPrefs.createPersistentPreference( + "wsinfer.localDirectory", + null); /** * String storing the preferred directory to cache models. @@ -66,9 +71,18 @@ public static Property numWorkersProperty() { return numWorkersProperty; } + /** + * String storing the preferred directory for user-supplied model folders. + */ + public static StringProperty localDirectoryProperty() { + return localDirectoryProperty; + } + private static Path getUserDir() { Path userPath = UserDirectoryManager.getInstance().getUserPath(); Path cachePath = Paths.get(System.getProperty("user.dir"), ".cache", "QuPath"); return userPath == null || userPath.toString().isEmpty() ? cachePath : userPath; } + + } diff --git a/src/main/resources/qupath/ext/wsinfer/ui/strings.properties b/src/main/resources/qupath/ext/wsinfer/ui/strings.properties index 29963d8..42ce315 100644 --- a/src/main/resources/qupath/ext/wsinfer/ui/strings.properties +++ b/src/main/resources/qupath/ext/wsinfer/ui/strings.properties @@ -52,8 +52,10 @@ ui.options.device = Preferred device: ui.options.device.tooltip = Select the preferred device for model running (choose CPU if other options are not available) ui.options.directory = Model directory: ui.options.directory.tooltip = Choose the directory where models should be stored +ui.options.localModelDirectory = User model directory: +ui.options.localModelDirectory.tooltip = Choose the directory where user-created models can be loaded from ui.options.pworkers = Number of parallel workers: -ui.options.pworkers.label = Choose the desired number of threads used to request tiles for inference +ui.options.pworkers.tooltip = Choose the desired number of threads used to request tiles for inference ##Other Windows #Processing Window and progress pop-ups diff --git a/src/main/resources/qupath/ext/wsinfer/ui/wsinfer_control.fxml b/src/main/resources/qupath/ext/wsinfer/ui/wsinfer_control.fxml index 770a055..ef1890c 100644 --- a/src/main/resources/qupath/ext/wsinfer/ui/wsinfer_control.fxml +++ b/src/main/resources/qupath/ext/wsinfer/ui/wsinfer_control.fxml @@ -137,18 +137,28 @@ - - + + + + + + + + From 8a8dc208d268c01bfec428b9fb608d8d46a00aaf Mon Sep 17 00:00:00 2001 From: Alan O'Callaghan Date: Wed, 1 Nov 2023 14:06:06 +0000 Subject: [PATCH 12/14] Spacing --- build.gradle | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/build.gradle b/build.gradle index d772461..e644da4 100644 --- a/build.gradle +++ b/build.gradle @@ -1,11 +1,11 @@ plugins { - // Main gradle plugin for building a Java library - id 'java-library' - // 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' - id 'org.openjfx.javafxplugin' version '0.1.0' + // Main gradle plugin for building a Java library + id 'java-library' + // 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' + id 'org.openjfx.javafxplugin' version '0.1.0' } ext.moduleName = 'io.github.qupath.extension.wsinfer' From dd4a569a987d1725d643b6768cb53b5b60a84dc8 Mon Sep 17 00:00:00 2001 From: Pete Date: Tue, 7 Nov 2023 17:19:11 +0000 Subject: [PATCH 13/14] Minor fixes, simplifications - Create WebView using `WebViews` class to standardize styling - Fix empty dialog titles generated in the task created by `WSInferController` - Standardize download & info icon approach in fxml - Fix use of multiple style classes in fxml - Reorder extra pane at bottom (to separate 'processing' options from 'model path' options) --- .../ext/wsinfer/ui/WSInferController.java | 22 ++++-- .../qupath/ext/wsinfer/ui/WSInferPrefs.java | 2 +- .../qupath/ext/wsinfer/ui/strings.properties | 4 +- .../ext/wsinfer/ui/wsinfer_control.fxml | 76 +++++++++++++------ .../qupath/ext/wsinfer/ui/wsinferstyles.css | 6 +- 5 files changed, 73 insertions(+), 37 deletions(-) diff --git a/src/main/java/qupath/ext/wsinfer/ui/WSInferController.java b/src/main/java/qupath/ext/wsinfer/ui/WSInferController.java index e0bd60e..17bfe10 100644 --- a/src/main/java/qupath/ext/wsinfer/ui/WSInferController.java +++ b/src/main/java/qupath/ext/wsinfer/ui/WSInferController.java @@ -60,6 +60,7 @@ import qupath.lib.gui.QuPathGUI; import qupath.lib.gui.commands.Commands; import qupath.lib.gui.tools.IconFactory; +import qupath.lib.gui.tools.WebViews; import qupath.lib.images.ImageData; import qupath.lib.objects.PathAnnotationObject; import qupath.lib.objects.PathObject; @@ -140,7 +141,6 @@ private void initialize() { this.qupath = QuPathGUI.getInstance(); this.imageDataProperty.bind(qupath.imageDataProperty()); - infoButton.setGraphic(IconFactory.createNode(20, 20, IconFactory.PathIcons.INFO)); configureModelChoices(); @@ -366,7 +366,7 @@ public void showInfo() throws IOException { WSInferModel model = modelChoiceBox.getSelectionModel().getSelectedItem(); Path mdFile = model.getREADMEFile().toPath(); var doc = Parser.builder().build().parse(Files.readString(mdFile)); - WebView webView = new WebView(); + WebView webView = WebViews.create(true); webView.getEngine().loadContent(HtmlRenderer.builder().build().render(doc)); infoPopover.setContentNode(webView); infoPopover.show(infoButton); @@ -418,19 +418,29 @@ private WSInferTask(ImageData imageData, WSInferModel model) { this.imageData = imageData; this.model = model; this.progressListener = new WSInferProgressDialog(QuPathGUI.getInstance().getStage(), e -> { - if (Dialogs.showYesNoDialog(getTitle(), resources.getString("ui.stop-tasks"))) { + if (Dialogs.showYesNoDialog(getDialogTitle(), resources.getString("ui.stop-tasks"))) { cancel(true); e.consume(); } }); } + private String getDialogTitle() { + try { + return ResourceBundle.getBundle("qupath.ext.wsinfer.ui.strings") + .getString("title"); + } catch (Exception e) { + logger.debug("Exception attempting to request title resource"); + return "WSInfer"; + } + } + @Override protected Void call() { try { // Ensure PyTorch engine is available if (!PytorchManager.hasPyTorchEngine()) { - Platform.runLater(() -> Dialogs.showInfoNotification(getTitle(), resources.getString("ui.pytorch-downloading"))); + Platform.runLater(() -> Dialogs.showInfoNotification(getDialogTitle(), resources.getString("ui.pytorch-downloading"))); PytorchManager.getEngineOnline(); } // Ensure model is available - any prompts allowing the user to cancel @@ -449,9 +459,9 @@ protected Void call() { WSInfer.runInference(imageData, model, progressListener); addToHistoryWorkflow(imageData, model.getName()); } catch (InterruptedException e) { - Platform.runLater(() -> Dialogs.showErrorNotification(getTitle(), e.getLocalizedMessage())); + Platform.runLater(() -> Dialogs.showErrorNotification(getDialogTitle(), e.getLocalizedMessage())); } catch (Exception e) { - Platform.runLater(() -> Dialogs.showErrorMessage(getTitle(), e.getLocalizedMessage())); + Platform.runLater(() -> Dialogs.showErrorMessage(getDialogTitle(), e.getLocalizedMessage())); } return null; } diff --git a/src/main/java/qupath/ext/wsinfer/ui/WSInferPrefs.java b/src/main/java/qupath/ext/wsinfer/ui/WSInferPrefs.java index be6ae21..82b0b40 100644 --- a/src/main/java/qupath/ext/wsinfer/ui/WSInferPrefs.java +++ b/src/main/java/qupath/ext/wsinfer/ui/WSInferPrefs.java @@ -41,7 +41,7 @@ public class WSInferPrefs { private static final Property numWorkersProperty = PathPrefs.createPersistentPreference( "wsinfer.numWorkers", - 1 + 2 ).asObject(); private static StringProperty localDirectoryProperty = PathPrefs.createPersistentPreference( "wsinfer.localDirectory", diff --git a/src/main/resources/qupath/ext/wsinfer/ui/strings.properties b/src/main/resources/qupath/ext/wsinfer/ui/strings.properties index a43656c..f3b70d2 100644 --- a/src/main/resources/qupath/ext/wsinfer/ui/strings.properties +++ b/src/main/resources/qupath/ext/wsinfer/ui/strings.properties @@ -51,11 +51,11 @@ ui.results.slider.tooltip = Adjust the opacity of the QuPath viewer overlay ui.options.pane = Additional Options ui.options.device = Preferred device: ui.options.device.tooltip = Select the preferred device for model running (choose CPU if other options are not available) -ui.options.directory = Model directory: +ui.options.directory = Downloaded model directory: ui.options.directory.tooltip = Choose the directory where models should be stored ui.options.localModelDirectory = User model directory: ui.options.localModelDirectory.tooltip = Choose the directory where user-created models can be loaded from -ui.options.pworkers = Number of parallel workers: +ui.options.pworkers = Number of parallel tile loaders: ui.options.pworkers.tooltip = Choose the desired number of threads used to request tiles for inference ## Other Windows diff --git a/src/main/resources/qupath/ext/wsinfer/ui/wsinfer_control.fxml b/src/main/resources/qupath/ext/wsinfer/ui/wsinfer_control.fxml index 96efc58..8a81b60 100644 --- a/src/main/resources/qupath/ext/wsinfer/ui/wsinfer_control.fxml +++ b/src/main/resources/qupath/ext/wsinfer/ui/wsinfer_control.fxml @@ -1,23 +1,25 @@ + + - - + + @@ -27,25 +29,36 @@ @@ -54,7 +67,7 @@ - + + + + + - + - @@ -109,14 +130,18 @@ - + + + + + - + + + + + + @@ -161,17 +198,6 @@ - - - - diff --git a/src/main/resources/qupath/ext/wsinfer/ui/wsinferstyles.css b/src/main/resources/qupath/ext/wsinfer/ui/wsinferstyles.css index 3f6a563..a9075ad 100644 --- a/src/main/resources/qupath/ext/wsinfer/ui/wsinferstyles.css +++ b/src/main/resources/qupath/ext/wsinfer/ui/wsinferstyles.css @@ -37,15 +37,15 @@ } .standard-padding { - -fx-padding: 5; - } + -fx-padding: 4; +} /*download button*/ /*.download-btn { -fx-background-image: url("icons/download.png"); }*/ -.download-icon { +.fa-icon { -fx-font-family: FontAwesome; -fx-font-size: 14px; -fx-fill: -fx-text-base-color; From 6fb618704ae05fab22c75e8afb70e010c891fed4 Mon Sep 17 00:00:00 2001 From: Alan O'Callaghan Date: Tue, 7 Nov 2023 18:00:18 +0000 Subject: [PATCH 14/14] Document v0.3 changes --- CHANGELOG.md | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7ee6de6..deaf481 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## v0.3.0 + +* Add support for QuPath v0.5 +* Enable auto-updating (see https://github.com/qupath/qupath-extension-wsinfer/issues/40) +* Add support for local models (see https://github.com/qupath/qupath-extension-wsinfer/issues/32) + ## v0.2.1 * Fix CPU/GPU support (https://github.com/qupath/qupath-extension-wsinfer/issues/41) @@ -14,4 +20,4 @@ ## v0.1.0 -* First release! \ No newline at end of file +* First release!