Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
7c361a0
Add logo spots and spot segmentation test datasets for BDV overlay
stefanhahmann Oct 7, 2026
32b7eb4
Let a BDV window's active pyramidal follow the current source of the BDV
stefanhahmann Oct 7, 2026
5d4284d
Merge BdvUtils' show methods into one
stefanhahmann Oct 8, 2026
1ec2431
Add OmeZarr.showInBdv(BdvHandle)
stefanhahmann Oct 8, 2026
1e08f1c
Relabel the BigDataViewer opener as "BigDataViewer (new window)"
stefanhahmann Oct 9, 2026
a811e92
Add "BigDataViewer (add to active window)" opener
stefanhahmann Oct 8, 2026
495d1fd
Make stringToUri's error messages independent of the clipboard
stefanhahmann Oct 8, 2026
b04a5e8
Route BDV opening through OmeZarrOpenActions.showInBdv(BdvHandle)
stefanhahmann Oct 8, 2026
e6f32d8
Add dropping OME-Zarrs onto BigDataViewer windows
stefanhahmann Oct 8, 2026
6afd978
Test dropping an OME-Zarr onto a BigDataViewer window
stefanhahmann Oct 9, 2026
d11dce8
Test the two BigDataViewer openers against each other
stefanhahmann Oct 9, 2026
187a49a
Add PyramidalService.getPyramidContents()
stefanhahmann Oct 12, 2026
78bc7d1
Add OmeZarrImagesChooser
stefanhahmann Oct 12, 2026
90b1baa
Add BdvUtils.showInOneWindow
stefanhahmann Oct 13, 2026
6e6f5ee
Let "Open Current OME-Zarr Image in BigDataViewer" also open several …
stefanhahmann Oct 12, 2026
30ea26d
Track the active image viewer in ActiveViewerService
stefanhahmann Oct 23, 2026
87377bb
Find the active BigDataViewer window directly, not via its pyramidal
stefanhahmann Oct 23, 2026
b6f236c
Document the active-viewer tracking in CLAUDE.md
stefanhahmann Oct 23, 2026
00ffadc
Make OmeZarrOpenActions.omeZarrFor public as withSettings
stefanhahmann Oct 23, 2026
eca31f1
Rename OmeZarrOpenerServiceTest.omeZarrFor to omeZarrWithoutBackend
stefanhahmann Oct 23, 2026
d3546cf
Load the opening settings once per drop
stefanhahmann Oct 23, 2026
93af66a
Name the drop's success flag transferSucceeded
stefanhahmann Oct 23, 2026
8396469
Rename OmeZarrDropListener to OmeZarrBdvDropListener
stefanhahmann Oct 23, 2026
be85dd3
Use array initializer shorthand in OmeZarrOpenActionsTest
stefanhahmann Oct 23, 2026
bc40c4d
Test ActiveViewerService.viewerOf
stefanhahmann Oct 23, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 22 additions & 6 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,14 @@ image: the
`@Parameter( type = ItemIO.OUTPUT )` on the `dataset` field only hands it to the caller, and a script that captures it
decides what to do with it. Being plain text rather than a chooser, it accepts `http(s):` too, but *not* `s3:`.

A second one is dropping onto the image area of a BDV window this plugin opened: `OmeZarrBdvDropListener.install`
(called by `OmeZarrOpenActions.showInBdv` for a new window, and by `OpenInBDVCommand`) puts an AWT `DropTarget` on the
viewer's image area, whose `OmeZarrBdvDropListener` adds the dropped OME-Zarr to *that* window via
`OmeZarr.showInBdv( BdvHandle )`, ignoring the configured opener — where it was dropped already says what the user wants. It validates with `isZarr` like every other route (so no `s3:`) and
reads with the persisted backend and preferred width, loaded at drop time. It lives in `fiji-ui` because those settings
do, so a script calling `OmeZarr.showInBdv()` directly gets a window without it. BDV windows opened by other tools get
no drop target: see issue #127 and bigdataviewer/bigdataviewer-core#227.

`OmeZarr.showInImageJ()`/`showInBdv()` return what they showed (`null` on failure or when the user
declined) and `OmeZarrOpenActions` mirrors that; the plugin's own call sites want only the side effect, hence the
`@SuppressWarnings( "UnusedReturnValue" )` — the values exist for API and script users.
Expand Down Expand Up @@ -240,8 +248,8 @@ i.e. a SciJava plugin anyway.
what
a persisted setting means (`effectiveOpenerName`).
- Shipped openers – `imagej-preferred-resolution`, `imagej-highest-resolution`, `bdv-multi-resolution`,
`n5-importer-dialog`, `n5-viewer-dialog`, `script-editor` – are one class each, next to the `OmeZarrOpenActions` they
drive. Only the extension point itself sits in `ome-zarr-fiji`: that is the artifact a downstream opener compiles
`bdv-add-to-active`, `n5-importer-dialog`, `n5-viewer-dialog`, `script-editor` – are one class each, next to the
`OmeZarrOpenActions` they drive. Only the extension point itself sits in `ome-zarr-fiji`: that is the artifact a downstream opener compiles
against, and it stays free of concrete openers. Help is a plain button, not an opener.

The selection dialog (`OmeZarrOpenActionChooser`, one icon button per offered opener) is **not** an opener: it opens
Expand All @@ -265,9 +273,17 @@ it is not worth the code. A 0.7-and-earlier preference therefore reads as an unk
`info` log, nothing thrown – and the next visit to `OpeningBehaviorSettings` overwrites it. `getOpenerName()` returns
`null` for "never configured", which is what lets the priority rule above apply.

**Active-window tracking:** `PyramidalService` (a SciJava service) tracks the most-recently-focused `Pyramidal` window
(BDV or ImageJ) via AWT focus listening; `PyramidalPreprocessor` auto-fills any `Pyramidal`-typed command parameter
with the currently active one.
**Active-window tracking:** `ActiveViewerService` is the only AWT focus listener: it tracks the most recently focused
image viewer, an ImageJ `ImageWindow` or a BDV `ViewerPanel`, whoever opened it. `PyramidalService` follows it to track
the active `Pyramidal`, which `PyramidalPreprocessor` auto-fills into any `Pyramidal`-typed command parameter.
"BigDataViewer (add to active window)" asks it for the active viewer and `BdvUtils.bdvHandleOf` for its handle. **Keep
it one listener**: "an ImageJ window takes over, the toolbar and dialogs don't" is a precedence rule both consumers need,
and two trackers drifting apart is what caused the "sticky BDV" bug (`ed658c70`). It listens to `"focusOwner"`, not
`"activeWindow"`, so that two viewers in one window (BigWarp) stay apart. Focus in a BDV side panel lies outside the
`ViewerPanel`, hence the "only viewer in the window" fallback. Focus events arrive after `show()` returns, so
`OmeZarr.showInBdv` and `OmeZarr.showAsDataset` report the new viewer themselves. `bdvHandleOf` reads a client property
`BdvUtils.show` puts on the panel, mirroring the `BdvHandle.of( ViewerPanel )` proposed upstream in
bigdataviewer/bigdataviewer-core#227 — once that exists, delegate to it and BDVs opened by other tools work too.

Note: `BdvHandleService` is test/example-only now (`ome-zarr-fiji-ui/src/test/java/ome/zarr/examples/demo/`), not part
of the shipped plugin.
Expand Down Expand Up @@ -329,7 +345,7 @@ registered) would be the alternative. Java package names stay `ome.zarr.*` throu
pyramids).
- **`ome-zarr-fiji-ui`** – `ome.zarr.fijiui` (+`.open`, `.open.openers`, `.open.options`, `.plugin`,
`.plugin.command.*`, `.dialog`, `.util`); the OME-Zarr `IOPlugin` (drag-and-drop and `fiji://` links) in `.plugin`,
Swing dialogs in `.dialog`, the six built-in `OmeZarrOpener`s in `.open.openers`. The SciJava commands sit in three
Swing dialogs in `.dialog`, the seven built-in `OmeZarrOpener`s in `.open.openers`. The SciJava commands sit in three
sibling packages under `.plugin.command`, one per menu location: `.fileimport` for the two `File > Import` entries
plus their shared `FileImportHelper` (package-private, so its tests live there too), `.tools` for the
`Plugins > OME-Zarr` entries, and `.settings` for `OpeningBehaviorSettings`, `S3Settings` and `UserScriptSettings`.
Expand Down
9 changes: 8 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,9 @@ If the dropped / pasted / linked target is not recognized as a **OME-Zarr v0.3 -
Drop a local OME-Zarr folder or an OME-Zarr URI onto Fiji. What happens next is set in the
[Opening Behavior Settings](#opening-behavior-settings).

Dropping onto the image area of a BigDataViewer window opened by this plugin instead adds the OME-Zarr to that
window, e.g., to overlay a segmentation on the raw data. This does not depend on the opening behavior settings.

### Copy & Paste of OME-Zarr URIs (local folder, http, https, s3)

* Supports local paths, http(s) URLs, and `s3://` URIs
Expand Down Expand Up @@ -162,9 +165,13 @@ What Fiji does with a dropped / pasted / linked OME-Zarr. The options shipped he
* **ImageJ (matching resolution)** (initial default): opens the highest single-resolution level that is not wider
than the [maximum width](#maximum-width). This avoids loading excessively large images.
* **ImageJ (highest resolution)**: opens the highest-resolution level in ImageJ.
* **BigDataViewer**: opens all resolution levels as a multi-resolution source. This is useful for large OME-Zarrs.
* **BigDataViewer (new window)**: opens all resolution levels as a multi-resolution source in a new BigDataViewer
window. This is useful for large OME-Zarrs.
Channel names, colors, contrast limits, and the time point are
[taken from the OME-Zarr metadata](#read-channel-information-from-ome-zarr-metadata), if available.
* **BigDataViewer (add to active window)**: adds the image as a multi-resolution source to the BigDataViewer
window that had focus most recently, e.g., to overlay a segmentation on the raw data. Opens a new BigDataViewer
window if none is open, or if an ImageJ image window had focus after it.
* **N5 importer dialog**: opens the N5 import dialog at the dropped OME-Zarr. It lists the resolution levels, lets you
choose one, possibly crop it, and opens it in ImageJ.
* **N5 viewer dialog**: opens the N5 viewer dialog at the dropped OME-Zarr. It lists the resolution levels, lets you
Expand Down
4 changes: 2 additions & 2 deletions doc/DEVELOPERS.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ graph TD
| `ome-zarr-n5` | `ome.zarr.n5` | N5-universe backed reader, OME-Zarr v0.3 – v0.5. | `ome-zarr-imglib2`, the N5 stack |
| `ome-zarr-zarrjava` | `ome.zarr.zarrjava` | zarr-java backed reader (the default), Zarr v2/v3 i.e. OME-Zarr v0.4 + v0.5. The only backend that reads `.ozx` archives. | `ome-zarr-imglib2`, `dev.zarr:zarr-java` |
| `ome-zarr-fiji` | `ome.zarr.fiji` `.read` `.open` `.plugins` `.util` | Opens a pyramid in ImageJ or BigDataViewer, tracks the active window, and defines the `OmeZarrOpener` extension point. No dialogs, no backend dependency — pick one at runtime. | `ome-zarr-imglib2`, bigdataviewer-core, spim_data, imagej-common, ij, scijava-common |
| `ome-zarr-fiji-ui` | `ome.zarr.fijiui` `.open` `.plugin` `.dialog` `.util` | The shipped plugin: `IOPlugin`, clipboard, Swing dialogs, SciJava commands, persisted settings, the six built-in openers. | the four above, scijava-common, script-editor, n5-viewer_fiji |
| `ome-zarr-fiji-ui` | `ome.zarr.fijiui` `.open` `.plugin` `.dialog` `.util` | The shipped plugin: `IOPlugin`, clipboard, Swing dialogs, SciJava commands, persisted settings, the seven built-in openers. | the four above, scijava-common, script-editor, n5-viewer_fiji |

Pick the smallest one you need:

Expand Down Expand Up @@ -248,7 +248,7 @@ jar for free, and the icon is resolved out of the *contributing* jar.

| Annotation element | What it does |
|------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `name` | Identifies the opener in the persisted settings, so **keep it stable across releases**. The shipped ones are `imagej-preferred-resolution`, `imagej-highest-resolution`, `bdv-multi-resolution`, `n5-importer-dialog`, `n5-viewer-dialog` and `script-editor`. |
| `name` | Identifies the opener in the persisted settings, so **keep it stable across releases**. The shipped ones are `imagej-preferred-resolution`, `imagej-highest-resolution`, `bdv-multi-resolution`, `bdv-add-to-active`, `n5-importer-dialog`, `n5-viewer-dialog` and `script-editor`. |
| `label`, `description` | What the user sees in the settings and as the button tooltip. |
| `iconPath` | Classpath path to the button icon in your jar. |
| `priority` | Orders the openers, and decides which one a user who never configured a choice gets. The shipped openers sit at `Priority.HIGH` and below, so declare `Priority.VERY_HIGH` to be the out-of-the-box default. |
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,169 @@
/*-
* #%L
* OME-Zarr extras for Fiji
* %%
* Copyright (C) 2022 - 2026 SciJava developers
* %%
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
* #L%
*/
package ome.zarr.fijiui.open;

import java.awt.datatransfer.DataFlavor;
import java.awt.datatransfer.Transferable;
import java.awt.datatransfer.UnsupportedFlavorException;
import java.awt.dnd.DnDConstants;
import java.awt.dnd.DropTarget;
import java.awt.dnd.DropTargetAdapter;
import java.awt.dnd.DropTargetDragEvent;
import java.awt.dnd.DropTargetDropEvent;
import java.io.IOException;
import java.lang.invoke.MethodHandles;
import java.net.URI;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

import org.scijava.Context;
import org.scijava.prefs.PrefService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import bdv.util.BdvHandle;
import ij.IJ;
import ome.zarr.fiji.read.OmeZarr;
import ome.zarr.fijiui.open.options.OmeZarrOpeningSettings;
import ome.zarr.fijiui.util.ClipboardUtils;
import ome.zarr.imglib2.util.ZarrUtils;

/**
* Lets OME-Zarrs be dropped onto the image area of a BigDataViewer window, which adds them to
* that window via {@link OmeZarr#showInBdv(BdvHandle)}. Accepts files and folders, e.g., from
* Finder or Explorer, and URLs, e.g., from a browser.
* <p>
* BDV itself accepts no drops on its image area, so nothing competes with this. Each dropped
* location is parsed like typed or pasted text ({@link ClipboardUtils#stringToUri}) and checked
* with {@link ZarrUtils#isZarr(URI)}, like every other entry route. Parsing, check and reading run
* on a background thread, since each may go over the network or show an error dialog.
*/
public class OmeZarrBdvDropListener extends DropTargetAdapter
{
private static final Logger logger = LoggerFactory.getLogger( MethodHandles.lookup().lookupClass() );

private final BdvHandle bdvHandle;

private final Context context;

private OmeZarrBdvDropListener( final BdvHandle bdvHandle, final Context context )
{
this.bdvHandle = bdvHandle;
this.context = context;
}

/**
* Makes the image area of {@code bdvHandle}'s window accept dropped OME-Zarrs.
* <p>
* Dropped OME-Zarrs are read with the backend and preferred width the user has configured at
* the time of the drop, and failures are reported via {@code IJ::error}.
*
* @param bdvHandle the BigDataViewer to add dropped OME-Zarrs to
* @param context the SciJava context the settings are read from
*/
public static void install( final BdvHandle bdvHandle, final Context context )
{
new DropTarget( bdvHandle.getViewerPanel().getDisplayComponent(), DnDConstants.ACTION_COPY,
new OmeZarrBdvDropListener( bdvHandle, context ) );
}

@Override
public void dragEnter( final DropTargetDragEvent event )
{
if ( event.isDataFlavorSupported( DataFlavor.javaFileListFlavor )
|| event.isDataFlavorSupported( DataFlavor.stringFlavor ) )
event.acceptDrag( DnDConstants.ACTION_COPY );
else
event.rejectDrag();
}

@Override
public void drop( final DropTargetDropEvent event )
{
event.acceptDrop( DnDConstants.ACTION_COPY );
final List< String > locations = droppedLocations( event.getTransferable() );
final boolean transferSucceeded = !locations.isEmpty();
event.dropComplete( transferSucceeded );
if ( transferSucceeded )
new Thread( () -> {
final OmeZarrOpeningSettings settings =
OmeZarrOpeningSettings.loadSettingsFromPreferences( context.getService( PrefService.class ) );
locations.forEach( location -> add( location, settings ) );
}, "OME-Zarr drop onto BigDataViewer" ).start();
}

private void add( final String location, final OmeZarrOpeningSettings settings )
{
final URI uri = ClipboardUtils.stringToUri( location, IJ::error );
if ( uri == null )
return;
if ( !ZarrUtils.isZarr( uri ) )
{
IJ.error( "The dropped location does not appear to be an OME-Zarr dataset:\n" + uri + "." );
return;
}
OmeZarrOpenActions.withSettings( uri, context, settings, IJ::error ).showInBdv( bdvHandle );
}

/**
* The locations in a drop, as text: a path per file or folder if the drop carries a file list,
* else one location per line of text, which is how URLs arrive from a browser (and files on some
* Linux desktops, as {@code file:} URIs). They are turned into URIs later, off the event thread,
* since {@link ClipboardUtils#stringToUri} may report an error in a dialog.
*/
static List< String > droppedLocations( final Transferable transferable )
{
final List< String > locations = new ArrayList<>();
try
{
if ( transferable.isDataFlavorSupported( DataFlavor.javaFileListFlavor ) )
{
for ( final Object file : ( List< ? > ) transferable.getTransferData( DataFlavor.javaFileListFlavor ) )
locations.add( file.toString() );
}
else if ( transferable.isDataFlavorSupported( DataFlavor.stringFlavor ) )
{
for ( final String line : ( ( String ) transferable.getTransferData( DataFlavor.stringFlavor ) ).split( "\\R" ) )
{
final String location = line.trim();
// text/uri-list allows comment lines starting with '#'
if ( !location.isEmpty() && !location.startsWith( "#" ) )
locations.add( location );
}
}
}
catch ( final UnsupportedFlavorException | IOException e )
{
logger.warn( "Could not read the dropped data: {}", e.getMessage() );
return Collections.emptyList();
}
return locations;
}
}
Loading
Loading