Closed Rylern closed 9 months ago
Thank you for doing all this, and the fantastic explanation of everything! I've started to check out the code in more detail but it might take me a little while.
Quick answer to the questions:
Wouldn't it better to make QuPath
ImageServers
asynchronous? Opening an OMERO image requires to perform several HTTP requests, which for now make the application unresponsive during a few seconds.
Possibly...
I tentatively think it's better to leave that up to the calling code. One reason is that we want the API simple for people to use server.readRegion(...)
in scripts (which generally run in a background thread anyway). And we definitely can't use any JavaFX features in ImageServer
since it should be UI-independent. I feel like making the ImageServer
asynchronous might add complexity and I'm not sure of the specific benefits.
At least, I think it makes sense to retain the current approach for now. But I'm strongly in favor of considering how to rethink the entire ImageServer
concept (alongside considering ImgLib2, multidimensional image 'blocks' rather than 2D 'regions', and perhaps writing an entirely new viewer...). So please do record all ideas for improvements :)
I saw that OMERO was also providing screens/wells/plates, is that something this extension should support?
Ideally yes! Since I think these usually aren't RGB images, the previous extension couldn't support the images anyway - so the lack of this feature didn't matter. But it would be a welcome addition.
I created a commit with the ICE support and updated the text above with the changes
I updated the pull requests with the changes you requested. This version relies on the Bio-Formats pull request (this is why the GitHub checks failed).
Organization & scripting
I reorganized the package structure. There are now less packages, and every UI elements are in a gui
package.
Do you have ideas of example scripts I should include?
Switching raw and web pixel access
I changed the way of accessing pixel values. I created the PixelAPI
interface, which provides information (e.g. supported images) about a pixel API (Ice or web for now). A PixelAPI
is created by a WebClient
.
The user can choose an available pixel API in the browser before opening an image. Every OMERO server supports the web API, but not all support the Ice API (for example servers that don't require authentication like IDR).
The default pixel API is the one that provides access to raw pixel values (which is only Ice for now), or the web API if none is available. This means that if a user:
then the image will be opened with the Ice API. Is that a problem?
If the user tries to open an image with an unavailable API, the image server won't be created (which will show an error dialog), so the bug you mentioned shouldn't appear anymore.
PixelAPI
is used to retrieve information about a pixel API, but can't actually read pixel values. For that, I created the PixelAPIReader
interface. I separated PixelAPI
and PixelAPIReader
because there is only one instance of PixelAPI
per OMERO server, while there can be several instances of PixelAPIReader
per OMERO server (if multiple images are opened at the same time for example).
The browser gives some info below the thumbnail. It would help to add more dimension info, as seen under the 'Image' tab in QuPath Dimensions (CZT) and also an estimate of the uncompressed size. Particularly for raw pixel access this helps determine how long it's likely to take to open the image.
The number of channels, z-slices, and timepoints are indicated, but I didn't find a way to get the uncompressed size from the web API.
Thanks, I'll read properly when I have more time - for now I'll just try to answer the questions.
I reorganized the package structure. There are now less packages, and every UI elements are in a
gui
package.
Sounds good!
Do you have ideas of example scripts I should include?
I guess the main one would be to open an image, access pixels or metadata, and close the image.
This may require login (ideally not requiring the password to be in the script).
Also, it should be possible to run a script over OMERO images from a command line, e.g. see https://qupath.readthedocs.io/en/0.4/docs/advanced/command_line.html#subcommands
The following is a rough example - even just printing the image data should demonstrate that the image can be opened.
QuPath script -p myproject/project.qpproj -c "println getCurrentImageData()"
One might also want to connect to a server and list the images contained, selecting which to open that way.
In the future, there will likely be a need for other things (e.g. requesting or sending objects). Basically, if it can be done through the UI then there is a strong chance a user will want to script it some day...
then the image will be opened with the Ice API. Is that a problem?
Yes, it's important that we try to ensure the user always receives the same pixels for the same image in their project. If they request the web version, they should always receive that - because it will be JPEG-compressed, and therefore have different pixel values.
If it switches to use ICE, then the results of tasks like cell detection will be different (and also the performance will be different).
The choice of server might be encoded in the string args that are associated with ImageServer
/ ServerBuilder
.
Switching between implementations that give raw pixel access may be permitted.
The number of channels, z-slices, and timepoints are indicated, but I didn't find a way to get the uncompressed size from the web API.
You can calculate this by multiplying all the other dimensions, and then multiplying the result by the bytes per pixel - see here for an example.
Initial refactoring of the extension. I'm creating a pull request now to agree on some design decisions:
I organized packages by feature, not by layer (see this link). For example, the
OmeroExtension
class installs 3 actions/menus:Each of this feature has one package dedicated to it, located in the same package as the
OmeroExtension
class. The common package is an exception, as it contains functions used by every other parts of the project. I'll see if it's possible to integrate some parts of it in separate feature packages. With the Javadoc comments, it should be easy to understand how the project is structured.synchronized
keyword for that.Platform.runLater()
to come back to the UI thread.someUIElementModel
classes that propagate changes made to a property/list from any thread to the UI thread.string.properties
file.null
. When a function is not guaranteed to return a result, the JavaOptional
API is used.A few other things: