diff --git a/src/main/java/qupath/ext/omero/core/apis/ApisHandler.java b/src/main/java/qupath/ext/omero/core/apis/ApisHandler.java index eb7e88f..14fb50b 100644 --- a/src/main/java/qupath/ext/omero/core/apis/ApisHandler.java +++ b/src/main/java/qupath/ext/omero/core/apis/ApisHandler.java @@ -1,8 +1,10 @@ package qupath.ext.omero.core.apis; import com.drew.lang.annotations.Nullable; +import javafx.beans.property.BooleanProperty; import javafx.beans.property.ReadOnlyBooleanProperty; import javafx.beans.property.ReadOnlyIntegerProperty; +import javafx.beans.property.SimpleBooleanProperty; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import qupath.ext.omero.core.entities.annotations.AnnotationGroup; @@ -25,7 +27,11 @@ import java.awt.image.BufferedImage; import java.net.URI; -import java.util.*; +import java.util.Collection; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ExecutionException; @@ -57,6 +63,7 @@ public class ApisHandler implements AutoCloseable { private final WebclientApi webclientApi; private final WebGatewayApi webGatewayApi; private final IViewerApi iViewerApi; + private final BooleanProperty areOrphanedImagesLoading = new SimpleBooleanProperty(false); private final Map thumbnails = new ConcurrentHashMap<>(); private final Map, BufferedImage> omeroIcons = new ConcurrentHashMap<>(); private final boolean canSkipAuthentication; @@ -300,14 +307,19 @@ public CompletableFuture getNumberOfOrphanedImages() { * be possible to add elements to this list */ public void populateOrphanedImagesIntoList(List children) { - getOrphanedImagesIds().thenAccept(orphanedImageIds -> jsonApi.populateOrphanedImagesIntoList(children, orphanedImageIds)); + setOrphanedImagesLoading(true); + + getOrphanedImagesIds() + .thenAcceptAsync(orphanedImageIds -> jsonApi.populateOrphanedImagesIntoList(children, orphanedImageIds)) + .thenRun(() -> setOrphanedImagesLoading(false)); } /** - * See {@link JsonApi#areOrphanedImagesLoading()}. + * @return whether orphaned images are currently being loaded. + * This property may be updated from any thread */ public ReadOnlyBooleanProperty areOrphanedImagesLoading() { - return jsonApi.areOrphanedImagesLoading(); + return areOrphanedImagesLoading; } /** @@ -564,4 +576,8 @@ public CompletableFuture writeROIs(long id, Collection shapes, b public CompletableFuture> getImageSettings(long imageId) { return iViewerApi.getImageSettings(imageId); } + + private synchronized void setOrphanedImagesLoading(boolean orphanedImagesLoading) { + areOrphanedImagesLoading.set(orphanedImagesLoading); + } } diff --git a/src/main/java/qupath/ext/omero/core/apis/JsonApi.java b/src/main/java/qupath/ext/omero/core/apis/JsonApi.java index 8188c10..824086b 100644 --- a/src/main/java/qupath/ext/omero/core/apis/JsonApi.java +++ b/src/main/java/qupath/ext/omero/core/apis/JsonApi.java @@ -8,11 +8,8 @@ import com.google.gson.JsonObject; import com.google.gson.JsonSyntaxException; import com.google.gson.reflect.TypeToken; -import javafx.beans.property.BooleanProperty; import javafx.beans.property.IntegerProperty; -import javafx.beans.property.ReadOnlyBooleanProperty; import javafx.beans.property.ReadOnlyIntegerProperty; -import javafx.beans.property.SimpleBooleanProperty; import javafx.beans.property.SimpleIntegerProperty; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -71,7 +68,6 @@ class JsonApi { private static final String WELLS_URL = "%s/api/v0/m/plateacquisitions/%d/wellsampleindex/%d/wells/"; private static final String ROIS_URL = "%s/api/v0/m/rois/?image=%s"; private final IntegerProperty numberOfEntitiesLoading = new SimpleIntegerProperty(0); - private final BooleanProperty areOrphanedImagesLoading = new SimpleBooleanProperty(false); private final IntegerProperty numberOfOrphanedImagesLoaded = new SimpleIntegerProperty(0); private final URI webURI; private final Map urls; @@ -398,7 +394,7 @@ public int getNumberOfOrphanedImages(List orphanedImagesIds) { *

* Populate all orphaned images of this server to the list specified in parameter. * This function populates and doesn't return a list because the number of images can - * be large, so this operation can take tens of seconds. + * be large, so this operation can take tens of seconds and should be run in a background thread. *

*

The list can be updated from any thread.

* @@ -407,38 +403,25 @@ public int getNumberOfOrphanedImages(List orphanedImagesIds) { * @param orphanedImagesIds the Ids of all orphaned images of the server */ public void populateOrphanedImagesIntoList(List children, List orphanedImagesIds) { - setOrphanedImagesLoading(true); resetNumberOfOrphanedImagesLoaded(); List orphanedImagesURIs = getOrphanedImagesURIs(orphanedImagesIds); - CompletableFuture.runAsync(() -> { - // The number of parallel requests is limited to 16 - // to avoid too many concurrent streams - List> batches = Lists.partition(orphanedImagesURIs, 16); - for (List batch: batches) { - children.addAll(batch.stream() - .map(this::requestImageInfo) - .map(CompletableFuture::join) - .flatMap(Optional::stream) - .map(jsonObject -> ServerEntity.createFromJsonElement(jsonObject, webURI)) - .flatMap(Optional::stream) - .map(serverEntity -> (Image) serverEntity) - .toList() - ); - - addToNumberOfOrphanedImagesLoaded(batch.size()); - } - - setOrphanedImagesLoading(false); - }); - } + // The number of parallel requests is limited to 16 + // to avoid too many concurrent streams + List> batches = Lists.partition(orphanedImagesURIs, 16); + for (List batch: batches) { + children.addAll(batch.stream() + .map(this::requestImageInfo) + .map(CompletableFuture::join) + .flatMap(Optional::stream) + .map(jsonObject -> ServerEntity.createFromJsonElement(jsonObject, webURI)) + .flatMap(Optional::stream) + .map(serverEntity -> (Image) serverEntity) + .toList() + ); - /** - * @return whether orphaned images are currently being loaded. - * This property may be updated from any thread - */ - public ReadOnlyBooleanProperty areOrphanedImagesLoading() { - return areOrphanedImagesLoading; + addToNumberOfOrphanedImagesLoaded(batch.size()); + } } /** @@ -637,10 +620,6 @@ private static CompletableFuture> getToken(Map } } - private synchronized void setOrphanedImagesLoading(boolean orphanedImagesLoading) { - areOrphanedImagesLoading.set(orphanedImagesLoading); - } - private List getOrphanedImagesURIs(List orphanedImagesIds) { return orphanedImagesIds.stream() .map(id -> WebUtilities.createURI(urls.get(IMAGES_URL_KEY) + id))