Skip to content

Commit

Permalink
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Added initial support for screen / plate / well
Browse files Browse the repository at this point in the history
Rylern committed Dec 4, 2023
1 parent b6f6296 commit 7e44e29
Showing 77 changed files with 3,295 additions and 1,014 deletions.
2 changes: 2 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
@@ -5,6 +5,8 @@ plugins {
id 'com.github.johnrengelman.shadow' version '7.1.2'
// Add JavaFX dependencies
alias(libs.plugins.javafx)
// Version in settings.gradle
id 'org.bytedeco.gradle-javacpp-platform'
}

ext.moduleName = 'qupath.extension.omero'
4 changes: 4 additions & 0 deletions sample-scripts/open_image_from_command_line.groovy
Original file line number Diff line number Diff line change
@@ -17,6 +17,10 @@ If you omit the credentials ([--username, your_username, --password, your_passwo
The parameters [--pixelAPI, Web] are used to specify QuPath how to retrieve pixel values. "Web" can be replaced by "Ice"
or "Pixel Buffer Microservice" (see the README file of the extension, "Reading images" section). If you omit these parameters,
the most accurate available pixel API will be selected.
If you select the web API ([--pixelAPI, Web]), you can add the [--jpegQuality, 0.6] optional argument to set the quality
level of the JPEG images returned by the web pixel API (number between 0 and 1).
If you select the pixel buffer microservice API ([--pixelAPI, Pixel Buffer Microservice]), you can add the
[--msPixelBufferPort, 8082] optional argument to set the port used by this microservice on the OMERO server.
*/

// Open server
8 changes: 7 additions & 1 deletion settings.gradle
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
pluginManagement {
plugins {
id 'org.bytedeco.gradle-javacpp-platform' version '1.5.9'
}
}

rootProject.name = 'qupath-extension-omero'

gradle.ext.qupathVersion = "0.5.0-rc2"
gradle.ext.qupathVersion = "0.5.0"

dependencyResolutionManagement {
versionCatalogs {
114 changes: 78 additions & 36 deletions src/main/java/qupath/ext/omero/core/ClientsPreferencesManager.java
Original file line number Diff line number Diff line change
@@ -17,6 +17,8 @@
public class ClientsPreferencesManager {

private static final Logger logger = LoggerFactory.getLogger(ClientsPreferencesManager.class);
private static final String PREFERENCE_DELIMITER = ",";
private static final String PROPERTY_DELIMITER = "%";
private static final StringProperty serverListPreference = PathPrefs.createPersistentPreference(
"omero_ext.server_list",
""
@@ -33,15 +35,19 @@ public class ClientsPreferencesManager {
"omero_ext.ms_pixel_buffer_port",
""
);
private static final StringProperty webJpegQualityPreference = PathPrefs.createPersistentPreference(
"omero_ext.web_jpeg_quality",
""
);
private static final ObservableList<String> uris = FXCollections.observableArrayList(
Arrays.stream(serverListPreference.get().split(","))
Arrays.stream(serverListPreference.get().split(PREFERENCE_DELIMITER))
.filter(uri -> !uri.isEmpty())
.toList()
);
private static final ObservableList<String> urisImmutable = FXCollections.unmodifiableObservableList(uris);

static {
uris.addListener((ListChangeListener<? super String>) c -> setServerListPreference(String.join(",", uris)));
uris.addListener((ListChangeListener<? super String>) c -> setServerListPreference(String.join(PREFERENCE_DELIMITER, uris)));
}

private ClientsPreferencesManager() {
@@ -124,24 +130,15 @@ public static void setLastUsername(String username) {
* @param serverURI the URI of the OMERO server to whose port should be retrieved
* @return the port, or an empty optional if not found
*/
public static synchronized Optional<Integer> getMsPixelBufferPort(String serverURI) {
String[] uriPorts = msPixelBufferPortPreference.get().split(",");

for (String uriPort: uriPorts) {
String uri = uriPort.split("%")[0];

if (uri.equals(serverURI)) {
String port = uriPort.split("%")[1];

try {
return Optional.of(Integer.valueOf(port));
} catch (NumberFormatException e) {
logger.error(String.format("Can't convert %s to int", port), e);
}
public static Optional<Integer> getMsPixelBufferPort(String serverURI) {
return getProperty(msPixelBufferPortPreference, serverURI).map(p -> {
try {
return Integer.valueOf(p);
} catch (NumberFormatException e) {
logger.error(String.format("Can't convert %s to int", p), e);
return null;
}
}

return Optional.empty();
});
}

/**
@@ -151,26 +148,35 @@ public static synchronized Optional<Integer> getMsPixelBufferPort(String serverU
* @param serverURI the URI of the OMERO server to whose port should be set
* @param port the pixel buffer microservice port
*/
public static synchronized void setMsPixelBufferPort(String serverURI, int port) {
String[] uriPorts = msPixelBufferPortPreference.get().split(",");

boolean portAdded = false;
for (int i=0; i<uriPorts.length; ++i) {
String uri = uriPorts[i].split("%")[0];
public static void setMsPixelBufferPort(String serverURI, int port) {
setProperty(msPixelBufferPortPreference, serverURI, String.valueOf(port));
}

if (uri.equals(serverURI)) {
uriPorts[i] = uri + "%" + port;
portAdded = true;
/**
* Get the saved JPEG quality used by the pixel web API corresponding to the provided URI.
*
* @param serverURI the URI of the OMERO server to whose JPEG quality should be retrieved
* @return the JPEG quality, or an empty optional if not found
*/
public static Optional<Float> getWebJpegQuality(String serverURI) {
return getProperty(webJpegQualityPreference, serverURI).map(p -> {
try {
return Float.valueOf(p);
} catch (NumberFormatException e) {
logger.error(String.format("Can't convert %s to float", p), e);
return null;
}
}

String newPreference = String.join(",", uriPorts);

if (!portAdded) {
newPreference += "," + serverURI + "%" + port;
}
});
}

msPixelBufferPortPreference.set(newPreference);
/**
* Set the saved JPEG quality used by the pixel web API corresponding to the provided URI.
*
* @param serverURI the URI of the OMERO server to whose port should be set
* @param jpegQuality the JPEG quality
*/
public static synchronized void setWebJpegQuality(String serverURI, float jpegQuality) {
setProperty(webJpegQualityPreference, serverURI, String.valueOf(jpegQuality));
}

private static synchronized void setServerListPreference(String serverListPreference) {
@@ -196,4 +202,40 @@ private static synchronized void updateURIs(String uri, boolean add) {
uris.remove(uri);
}
}

private static synchronized Optional<String> getProperty(StringProperty preference, String serverURI) {
String[] uriProperties = preference.get().split(PREFERENCE_DELIMITER);

for (String uriProperty: uriProperties) {
String[] uriPropertySplit = uriProperty.split(PROPERTY_DELIMITER);

if (uriPropertySplit.length > 1 && uriPropertySplit[0].equals(serverURI)) {
return Optional.of(uriPropertySplit[1]);
}
}

return Optional.empty();
}

public static synchronized void setProperty(StringProperty preference, String serverURI, String property) {
String[] uriProperties = preference.get().split(PREFERENCE_DELIMITER);

boolean propertyAdded = false;
for (int i=0; i<uriProperties.length; ++i) {
String[] uriPropertySplit = uriProperties[i].split(PROPERTY_DELIMITER);

if (uriPropertySplit.length > 0 && uriPropertySplit[0].equals(serverURI)) {
uriProperties[i] = serverURI + PROPERTY_DELIMITER + property;
propertyAdded = true;
}
}

String newPreference = String.join(PREFERENCE_DELIMITER, uriProperties);

if (!propertyAdded) {
newPreference += PREFERENCE_DELIMITER + serverURI + PROPERTY_DELIMITER + property;
}

preference.set(newPreference);
}
}
10 changes: 5 additions & 5 deletions src/main/java/qupath/ext/omero/core/WebClient.java
Original file line number Diff line number Diff line change
@@ -56,7 +56,7 @@ public class WebClient implements AutoCloseable {
private ApisHandler apisHandler;
private List<PixelAPI> allPixelAPIs;
private Timer timeoutTimer;
private char[] password;
private String sessionUuid;
private Status status;
private FailReason failReason;

@@ -198,11 +198,11 @@ public ReadOnlyStringProperty getUsername() {
}

/**
* @return the password of the authenticated user, or an empty Optional if
* @return the session UUID of the authenticated user, or an empty Optional if
* there is no authentication
*/
public Optional<char[]> getPassword() {
return Optional.ofNullable(password);
public Optional<String> getSessionUuid() {
return Optional.ofNullable(sessionUuid);
}

/**
@@ -463,7 +463,7 @@ private synchronized void setAuthenticationInformation(LoginResponse loginRespon
this.authenticated.set(true);

username.set(loginResponse.getUsername());
password = loginResponse.getPassword();
sessionUuid = loginResponse.getSessionUuid();
}

private synchronized void startTimer() {
96 changes: 81 additions & 15 deletions src/main/java/qupath/ext/omero/core/apis/ApisHandler.java
Original file line number Diff line number Diff line change
@@ -6,8 +6,7 @@
import qupath.ext.omero.core.entities.login.LoginResponse;
import qupath.ext.omero.core.entities.repositoryentities.OrphanedFolder;
import qupath.ext.omero.core.entities.repositoryentities.RepositoryEntity;
import qupath.ext.omero.core.entities.repositoryentities.serverentities.Dataset;
import qupath.ext.omero.core.entities.repositoryentities.serverentities.Project;
import qupath.ext.omero.core.entities.repositoryentities.serverentities.*;
import qupath.ext.omero.core.entities.search.SearchQuery;
import qupath.ext.omero.core.entities.search.SearchResult;
import qupath.ext.omero.core.entities.shapes.Shape;
@@ -18,7 +17,6 @@
import qupath.ext.omero.core.entities.permissions.Group;
import qupath.ext.omero.core.entities.permissions.Owner;
import qupath.ext.omero.core.entities.repositoryentities.serverentities.image.Image;
import qupath.ext.omero.core.entities.repositoryentities.serverentities.ServerEntity;

import java.awt.image.BufferedImage;
import java.net.URI;
@@ -37,7 +35,16 @@
public class ApisHandler implements AutoCloseable {

private static final int THUMBNAIL_SIZE = 256;
private final WebClient client;
private static final Map<String, PixelType> PIXEL_TYPE_MAP = Map.of(
"uint8", PixelType.UINT8,
"int8", PixelType.INT8,
"uint16", PixelType.UINT16,
"int16", PixelType.INT16,
"int32", PixelType.INT32,
"uint32", PixelType.UINT32,
"float", PixelType.FLOAT32,
"double", PixelType.FLOAT64
);
private final URI host;
private final WebclientApi webclientApi;
private final WebGatewayApi webGatewayApi;
@@ -46,8 +53,7 @@ public class ApisHandler implements AutoCloseable {
private final Map<Class<? extends RepositoryEntity>, BufferedImage> omeroIcons = new ConcurrentHashMap<>();
private JsonApi jsonApi;

private ApisHandler(WebClient client, URI host) {
this.client = client;
private ApisHandler(URI host) {
this.host = host;

webclientApi = new WebclientApi(host);
@@ -64,9 +70,9 @@ private ApisHandler(WebClient client, URI host) {
* @return a CompletableFuture with the request handler, an empty Optional if an error occurred
*/
public static CompletableFuture<Optional<ApisHandler>> create(WebClient client, URI host) {
ApisHandler apisHandler = new ApisHandler(client, host);
ApisHandler apisHandler = new ApisHandler(host);

return JsonApi.create(apisHandler, host).thenApply(jsonApi -> {
return JsonApi.create(client, host).thenApply(jsonApi -> {
if (jsonApi.isPresent()) {
apisHandler.jsonApi = jsonApi.get();
apisHandler.webclientApi.setToken(jsonApi.get().getToken());
@@ -102,10 +108,13 @@ public int hashCode() {
}

/**
* @return the web client using this APIs handler
* Convert a pixel type returned by OMERO to a QuPath {@link PixelType}
*
* @param pixelType the OMERO pixel type
* @return the QuPath pixel type, or an empty Optional if the OMERO pixel type was not recognized
*/
public WebClient getClient() {
return client;
public static Optional<PixelType> getPixelType(String pixelType) {
return Optional.ofNullable(PIXEL_TYPE_MAP.get(pixelType));
}

/**
@@ -123,10 +132,10 @@ public String getServerURI() {
}

/**
* See {@link JsonApi#getPort()}.
* See {@link JsonApi#getServerPort()}.
*/
public int getPort() {
return jsonApi.getPort();
public int getServerPort() {
return jsonApi.getServerPort();
}

/**
@@ -279,6 +288,48 @@ public ReadOnlyIntegerProperty getNumberOfOrphanedImagesLoaded() {
return jsonApi.getNumberOfOrphanedImagesLoaded();
}

/**
* See {@link JsonApi#getScreens()}.
*/
public CompletableFuture<List<Screen>> getScreens() {
return jsonApi.getScreens();
}

/**
* See {@link JsonApi#getOrphanedPlates()}.
*/
public CompletableFuture<List<Plate>> getOrphanedPlates() {
return jsonApi.getOrphanedPlates();
}

/**
* See {@link JsonApi#getPlates(long)}.
*/
public CompletableFuture<List<Plate>> getPlates(long screenID) {
return jsonApi.getPlates(screenID);
}

/**
* See {@link JsonApi#getPlateAcquisitions(long)}.
*/
public CompletableFuture<List<PlateAcquisition>> getPlateAcquisitions(long plateID) {
return jsonApi.getPlateAcquisitions(plateID);
}

/**
* See {@link JsonApi#getWellsFromPlate(long)}.
*/
public CompletableFuture<List<Well>> getWellsFromPlate(long plateID) {
return jsonApi.getWellsFromPlate(plateID);
}

/**
* See {@link JsonApi#getWellsFromPlateAcquisition(long,int)}.
*/
public CompletableFuture<List<Well>> getWellsFromPlateAcquisition(long plateAcquisitionID, int wellSampleIndex) {
return jsonApi.getWellsFromPlateAcquisition(plateAcquisitionID, wellSampleIndex);
}

/**
* See {@link WebclientApi#getAnnotations(ServerEntity)}.
*/
@@ -295,7 +346,7 @@ public CompletableFuture<List<SearchResult>> getSearchResults(SearchQuery search

/**
* <p>Attempt to retrieve the icon of an OMERO entity.</p>
* <p>Icons for orphaned folders, projects, datasets, and images can be retrieved.</p>
* <p>Icons for orphaned folders, projects, datasets, images, screens, plates, and plate acquisitions can be retrieved.</p>
* <p>This function is asynchronous.</p>
*
* @param type the class of the entity whose icon is to be retrieved
@@ -325,6 +376,21 @@ public CompletableFuture<Optional<BufferedImage>> getOmeroIcon(Class<? extends R
icon.ifPresent(bufferedImage -> omeroIcons.put(type, bufferedImage));
return icon;
});
} else if (type.equals(Screen.class)) {
return webclientApi.getScreenIcon().thenApply(icon -> {
icon.ifPresent(bufferedImage -> omeroIcons.put(type, bufferedImage));
return icon;
});
} else if (type.equals(Plate.class)) {
return webclientApi.getPlateIcon().thenApply(icon -> {
icon.ifPresent(bufferedImage -> omeroIcons.put(type, bufferedImage));
return icon;
});
} else if (type.equals(PlateAcquisition.class)) {
return webclientApi.getPlateAcquisitionIcon().thenApply(icon -> {
icon.ifPresent(bufferedImage -> omeroIcons.put(type, bufferedImage));
return icon;
});
} else {
return CompletableFuture.completedFuture(Optional.empty());
}
Loading

0 comments on commit 7e44e29

Please sign in to comment.