Skip to content

Commit 7e44e29

Browse files
committed
Added initial support for screen / plate / well
1 parent b6f6296 commit 7e44e29

File tree

77 files changed

+3295
-1014
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

77 files changed

+3295
-1014
lines changed

build.gradle

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ plugins {
55
id 'com.github.johnrengelman.shadow' version '7.1.2'
66
// Add JavaFX dependencies
77
alias(libs.plugins.javafx)
8+
// Version in settings.gradle
9+
id 'org.bytedeco.gradle-javacpp-platform'
810
}
911

1012
ext.moduleName = 'qupath.extension.omero'

sample-scripts/open_image_from_command_line.groovy

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,10 @@ If you omit the credentials ([--username, your_username, --password, your_passwo
1717
The parameters [--pixelAPI, Web] are used to specify QuPath how to retrieve pixel values. "Web" can be replaced by "Ice"
1818
or "Pixel Buffer Microservice" (see the README file of the extension, "Reading images" section). If you omit these parameters,
1919
the most accurate available pixel API will be selected.
20+
If you select the web API ([--pixelAPI, Web]), you can add the [--jpegQuality, 0.6] optional argument to set the quality
21+
level of the JPEG images returned by the web pixel API (number between 0 and 1).
22+
If you select the pixel buffer microservice API ([--pixelAPI, Pixel Buffer Microservice]), you can add the
23+
[--msPixelBufferPort, 8082] optional argument to set the port used by this microservice on the OMERO server.
2024
*/
2125

2226
// Open server

settings.gradle

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,12 @@
1+
pluginManagement {
2+
plugins {
3+
id 'org.bytedeco.gradle-javacpp-platform' version '1.5.9'
4+
}
5+
}
6+
17
rootProject.name = 'qupath-extension-omero'
28

3-
gradle.ext.qupathVersion = "0.5.0-rc2"
9+
gradle.ext.qupathVersion = "0.5.0"
410

511
dependencyResolutionManagement {
612
versionCatalogs {

src/main/java/qupath/ext/omero/core/ClientsPreferencesManager.java

Lines changed: 78 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@
1717
public class ClientsPreferencesManager {
1818

1919
private static final Logger logger = LoggerFactory.getLogger(ClientsPreferencesManager.class);
20+
private static final String PREFERENCE_DELIMITER = ",";
21+
private static final String PROPERTY_DELIMITER = "%";
2022
private static final StringProperty serverListPreference = PathPrefs.createPersistentPreference(
2123
"omero_ext.server_list",
2224
""
@@ -33,15 +35,19 @@ public class ClientsPreferencesManager {
3335
"omero_ext.ms_pixel_buffer_port",
3436
""
3537
);
38+
private static final StringProperty webJpegQualityPreference = PathPrefs.createPersistentPreference(
39+
"omero_ext.web_jpeg_quality",
40+
""
41+
);
3642
private static final ObservableList<String> uris = FXCollections.observableArrayList(
37-
Arrays.stream(serverListPreference.get().split(","))
43+
Arrays.stream(serverListPreference.get().split(PREFERENCE_DELIMITER))
3844
.filter(uri -> !uri.isEmpty())
3945
.toList()
4046
);
4147
private static final ObservableList<String> urisImmutable = FXCollections.unmodifiableObservableList(uris);
4248

4349
static {
44-
uris.addListener((ListChangeListener<? super String>) c -> setServerListPreference(String.join(",", uris)));
50+
uris.addListener((ListChangeListener<? super String>) c -> setServerListPreference(String.join(PREFERENCE_DELIMITER, uris)));
4551
}
4652

4753
private ClientsPreferencesManager() {
@@ -124,24 +130,15 @@ public static void setLastUsername(String username) {
124130
* @param serverURI the URI of the OMERO server to whose port should be retrieved
125131
* @return the port, or an empty optional if not found
126132
*/
127-
public static synchronized Optional<Integer> getMsPixelBufferPort(String serverURI) {
128-
String[] uriPorts = msPixelBufferPortPreference.get().split(",");
129-
130-
for (String uriPort: uriPorts) {
131-
String uri = uriPort.split("%")[0];
132-
133-
if (uri.equals(serverURI)) {
134-
String port = uriPort.split("%")[1];
135-
136-
try {
137-
return Optional.of(Integer.valueOf(port));
138-
} catch (NumberFormatException e) {
139-
logger.error(String.format("Can't convert %s to int", port), e);
140-
}
133+
public static Optional<Integer> getMsPixelBufferPort(String serverURI) {
134+
return getProperty(msPixelBufferPortPreference, serverURI).map(p -> {
135+
try {
136+
return Integer.valueOf(p);
137+
} catch (NumberFormatException e) {
138+
logger.error(String.format("Can't convert %s to int", p), e);
139+
return null;
141140
}
142-
}
143-
144-
return Optional.empty();
141+
});
145142
}
146143

147144
/**
@@ -151,26 +148,35 @@ public static synchronized Optional<Integer> getMsPixelBufferPort(String serverU
151148
* @param serverURI the URI of the OMERO server to whose port should be set
152149
* @param port the pixel buffer microservice port
153150
*/
154-
public static synchronized void setMsPixelBufferPort(String serverURI, int port) {
155-
String[] uriPorts = msPixelBufferPortPreference.get().split(",");
156-
157-
boolean portAdded = false;
158-
for (int i=0; i<uriPorts.length; ++i) {
159-
String uri = uriPorts[i].split("%")[0];
151+
public static void setMsPixelBufferPort(String serverURI, int port) {
152+
setProperty(msPixelBufferPortPreference, serverURI, String.valueOf(port));
153+
}
160154

161-
if (uri.equals(serverURI)) {
162-
uriPorts[i] = uri + "%" + port;
163-
portAdded = true;
155+
/**
156+
* Get the saved JPEG quality used by the pixel web API corresponding to the provided URI.
157+
*
158+
* @param serverURI the URI of the OMERO server to whose JPEG quality should be retrieved
159+
* @return the JPEG quality, or an empty optional if not found
160+
*/
161+
public static Optional<Float> getWebJpegQuality(String serverURI) {
162+
return getProperty(webJpegQualityPreference, serverURI).map(p -> {
163+
try {
164+
return Float.valueOf(p);
165+
} catch (NumberFormatException e) {
166+
logger.error(String.format("Can't convert %s to float", p), e);
167+
return null;
164168
}
165-
}
166-
167-
String newPreference = String.join(",", uriPorts);
168-
169-
if (!portAdded) {
170-
newPreference += "," + serverURI + "%" + port;
171-
}
169+
});
170+
}
172171

173-
msPixelBufferPortPreference.set(newPreference);
172+
/**
173+
* Set the saved JPEG quality used by the pixel web API corresponding to the provided URI.
174+
*
175+
* @param serverURI the URI of the OMERO server to whose port should be set
176+
* @param jpegQuality the JPEG quality
177+
*/
178+
public static synchronized void setWebJpegQuality(String serverURI, float jpegQuality) {
179+
setProperty(webJpegQualityPreference, serverURI, String.valueOf(jpegQuality));
174180
}
175181

176182
private static synchronized void setServerListPreference(String serverListPreference) {
@@ -196,4 +202,40 @@ private static synchronized void updateURIs(String uri, boolean add) {
196202
uris.remove(uri);
197203
}
198204
}
205+
206+
private static synchronized Optional<String> getProperty(StringProperty preference, String serverURI) {
207+
String[] uriProperties = preference.get().split(PREFERENCE_DELIMITER);
208+
209+
for (String uriProperty: uriProperties) {
210+
String[] uriPropertySplit = uriProperty.split(PROPERTY_DELIMITER);
211+
212+
if (uriPropertySplit.length > 1 && uriPropertySplit[0].equals(serverURI)) {
213+
return Optional.of(uriPropertySplit[1]);
214+
}
215+
}
216+
217+
return Optional.empty();
218+
}
219+
220+
public static synchronized void setProperty(StringProperty preference, String serverURI, String property) {
221+
String[] uriProperties = preference.get().split(PREFERENCE_DELIMITER);
222+
223+
boolean propertyAdded = false;
224+
for (int i=0; i<uriProperties.length; ++i) {
225+
String[] uriPropertySplit = uriProperties[i].split(PROPERTY_DELIMITER);
226+
227+
if (uriPropertySplit.length > 0 && uriPropertySplit[0].equals(serverURI)) {
228+
uriProperties[i] = serverURI + PROPERTY_DELIMITER + property;
229+
propertyAdded = true;
230+
}
231+
}
232+
233+
String newPreference = String.join(PREFERENCE_DELIMITER, uriProperties);
234+
235+
if (!propertyAdded) {
236+
newPreference += PREFERENCE_DELIMITER + serverURI + PROPERTY_DELIMITER + property;
237+
}
238+
239+
preference.set(newPreference);
240+
}
199241
}

src/main/java/qupath/ext/omero/core/WebClient.java

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ public class WebClient implements AutoCloseable {
5656
private ApisHandler apisHandler;
5757
private List<PixelAPI> allPixelAPIs;
5858
private Timer timeoutTimer;
59-
private char[] password;
59+
private String sessionUuid;
6060
private Status status;
6161
private FailReason failReason;
6262

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

200200
/**
201-
* @return the password of the authenticated user, or an empty Optional if
201+
* @return the session UUID of the authenticated user, or an empty Optional if
202202
* there is no authentication
203203
*/
204-
public Optional<char[]> getPassword() {
205-
return Optional.ofNullable(password);
204+
public Optional<String> getSessionUuid() {
205+
return Optional.ofNullable(sessionUuid);
206206
}
207207

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

465465
username.set(loginResponse.getUsername());
466-
password = loginResponse.getPassword();
466+
sessionUuid = loginResponse.getSessionUuid();
467467
}
468468

469469
private synchronized void startTimer() {

src/main/java/qupath/ext/omero/core/apis/ApisHandler.java

Lines changed: 81 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,7 @@
66
import qupath.ext.omero.core.entities.login.LoginResponse;
77
import qupath.ext.omero.core.entities.repositoryentities.OrphanedFolder;
88
import qupath.ext.omero.core.entities.repositoryentities.RepositoryEntity;
9-
import qupath.ext.omero.core.entities.repositoryentities.serverentities.Dataset;
10-
import qupath.ext.omero.core.entities.repositoryentities.serverentities.Project;
9+
import qupath.ext.omero.core.entities.repositoryentities.serverentities.*;
1110
import qupath.ext.omero.core.entities.search.SearchQuery;
1211
import qupath.ext.omero.core.entities.search.SearchResult;
1312
import qupath.ext.omero.core.entities.shapes.Shape;
@@ -18,7 +17,6 @@
1817
import qupath.ext.omero.core.entities.permissions.Group;
1918
import qupath.ext.omero.core.entities.permissions.Owner;
2019
import qupath.ext.omero.core.entities.repositoryentities.serverentities.image.Image;
21-
import qupath.ext.omero.core.entities.repositoryentities.serverentities.ServerEntity;
2220

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

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

49-
private ApisHandler(WebClient client, URI host) {
50-
this.client = client;
56+
private ApisHandler(URI host) {
5157
this.host = host;
5258

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

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

104110
/**
105-
* @return the web client using this APIs handler
111+
* Convert a pixel type returned by OMERO to a QuPath {@link PixelType}
112+
*
113+
* @param pixelType the OMERO pixel type
114+
* @return the QuPath pixel type, or an empty Optional if the OMERO pixel type was not recognized
106115
*/
107-
public WebClient getClient() {
108-
return client;
116+
public static Optional<PixelType> getPixelType(String pixelType) {
117+
return Optional.ofNullable(PIXEL_TYPE_MAP.get(pixelType));
109118
}
110119

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

125134
/**
126-
* See {@link JsonApi#getPort()}.
135+
* See {@link JsonApi#getServerPort()}.
127136
*/
128-
public int getPort() {
129-
return jsonApi.getPort();
137+
public int getServerPort() {
138+
return jsonApi.getServerPort();
130139
}
131140

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

291+
/**
292+
* See {@link JsonApi#getScreens()}.
293+
*/
294+
public CompletableFuture<List<Screen>> getScreens() {
295+
return jsonApi.getScreens();
296+
}
297+
298+
/**
299+
* See {@link JsonApi#getOrphanedPlates()}.
300+
*/
301+
public CompletableFuture<List<Plate>> getOrphanedPlates() {
302+
return jsonApi.getOrphanedPlates();
303+
}
304+
305+
/**
306+
* See {@link JsonApi#getPlates(long)}.
307+
*/
308+
public CompletableFuture<List<Plate>> getPlates(long screenID) {
309+
return jsonApi.getPlates(screenID);
310+
}
311+
312+
/**
313+
* See {@link JsonApi#getPlateAcquisitions(long)}.
314+
*/
315+
public CompletableFuture<List<PlateAcquisition>> getPlateAcquisitions(long plateID) {
316+
return jsonApi.getPlateAcquisitions(plateID);
317+
}
318+
319+
/**
320+
* See {@link JsonApi#getWellsFromPlate(long)}.
321+
*/
322+
public CompletableFuture<List<Well>> getWellsFromPlate(long plateID) {
323+
return jsonApi.getWellsFromPlate(plateID);
324+
}
325+
326+
/**
327+
* See {@link JsonApi#getWellsFromPlateAcquisition(long,int)}.
328+
*/
329+
public CompletableFuture<List<Well>> getWellsFromPlateAcquisition(long plateAcquisitionID, int wellSampleIndex) {
330+
return jsonApi.getWellsFromPlateAcquisition(plateAcquisitionID, wellSampleIndex);
331+
}
332+
282333
/**
283334
* See {@link WebclientApi#getAnnotations(ServerEntity)}.
284335
*/
@@ -295,7 +346,7 @@ public CompletableFuture<List<SearchResult>> getSearchResults(SearchQuery search
295346

296347
/**
297348
* <p>Attempt to retrieve the icon of an OMERO entity.</p>
298-
* <p>Icons for orphaned folders, projects, datasets, and images can be retrieved.</p>
349+
* <p>Icons for orphaned folders, projects, datasets, images, screens, plates, and plate acquisitions can be retrieved.</p>
299350
* <p>This function is asynchronous.</p>
300351
*
301352
* @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
325376
icon.ifPresent(bufferedImage -> omeroIcons.put(type, bufferedImage));
326377
return icon;
327378
});
379+
} else if (type.equals(Screen.class)) {
380+
return webclientApi.getScreenIcon().thenApply(icon -> {
381+
icon.ifPresent(bufferedImage -> omeroIcons.put(type, bufferedImage));
382+
return icon;
383+
});
384+
} else if (type.equals(Plate.class)) {
385+
return webclientApi.getPlateIcon().thenApply(icon -> {
386+
icon.ifPresent(bufferedImage -> omeroIcons.put(type, bufferedImage));
387+
return icon;
388+
});
389+
} else if (type.equals(PlateAcquisition.class)) {
390+
return webclientApi.getPlateAcquisitionIcon().thenApply(icon -> {
391+
icon.ifPresent(bufferedImage -> omeroIcons.put(type, bufferedImage));
392+
return icon;
393+
});
328394
} else {
329395
return CompletableFuture.completedFuture(Optional.empty());
330396
}

0 commit comments

Comments
 (0)