Skip to content

Commit b7a1b46

Browse files
Merge pull request #90 from BioImageTools/dataset-lifecycle-tobi
Add Open Resolution Level command and Pyramidal dataset lifecycle management
2 parents 8d3269c + 9d88ea1 commit b7a1b46

37 files changed

Lines changed: 2296 additions & 583 deletions

src/main/java/sc/fiji/ome/zarr/open/ZarrOpenActions.java

Lines changed: 143 additions & 80 deletions
Original file line numberDiff line numberDiff line change
@@ -46,18 +46,20 @@
4646
import java.util.Collections;
4747
import java.util.function.Consumer;
4848
import java.util.function.Function;
49+
import java.util.function.Supplier;
4950

5051
import com.google.gson.JsonSyntaxException;
5152

5253
import net.imglib2.img.Img;
5354
import net.imglib2.img.display.imagej.ImageJFunctions;
5455
import net.imglib2.util.Cast;
5556

56-
import bdv.util.BdvFunctions;
5757
import ij.IJ;
5858
import sc.fiji.ome.zarr.pyramid.Pyramidal5DImageDataImpl;
59+
import sc.fiji.ome.zarr.pyramid.PyramidalBdv;
5960
import sc.fiji.ome.zarr.pyramid.exceptions.MultiImageDatasetException;
6061
import sc.fiji.ome.zarr.pyramid.exceptions.NoMatchingResolutionException;
62+
import sc.fiji.ome.zarr.pyramid.exceptions.NonExistingResolutionLevelException;
6163
import sc.fiji.ome.zarr.pyramid.exceptions.NotAMultiscaleImageException;
6264
import sc.fiji.ome.zarr.pyramid.exceptions.NotASingleScaleImageException;
6365
import sc.fiji.ome.zarr.pyramid.PyramidalDataset;
@@ -66,6 +68,7 @@
6668
import sc.fiji.ome.zarr.open.options.ZarrOpenBehavior;
6769
import sc.fiji.ome.zarr.open.options.ZarrReaderBackend;
6870
import sc.fiji.ome.zarr.ui.DnDActionChooser;
71+
import sc.fiji.ome.zarr.util.PyramidalService;
6972
import sc.fiji.ome.zarr.util.BdvUtils;
7073
import sc.fiji.ome.zarr.util.ScriptUtils;
7174

@@ -83,6 +86,8 @@ public class ZarrOpenActions
8386

8487
private final ZarrOpeningSettings settings;
8588

89+
private Pyramidal5DImageDataImpl< ?, ? > cachedPyramid;
90+
8691
/**
8792
* Loads {@link ZarrOpeningSettings} from {@code context} and opens
8893
* {@code inputUri} via the action selected by the user's configured
@@ -166,23 +171,131 @@ public void openViewerDialog()
166171

167172
public Object openIJWithImage()
168173
{
169-
return openImage(
170-
pyramidalDataset -> {
171-
context.getService( UIService.class ).show( pyramidalDataset );
172-
return null;
173-
},
174-
singleScaleImage -> ImageJFunctions.show( Cast.unchecked( singleScaleImage ) ),
175-
"ImageJ"
176-
);
174+
try
175+
{
176+
return openPyramidImage(
177+
() -> {
178+
final PyramidalDataset dataset = getPyramid().asPyramidalDataset();
179+
context.getService( UIService.class ).show( dataset );
180+
logger.info( "Opened dataset in ImageJ: {}", inputUri );
181+
return null;
182+
},
183+
singleScaleImage -> ImageJFunctions.show( Cast.unchecked( singleScaleImage ) ) );
184+
}
185+
catch ( NoMatchingResolutionException e )
186+
{
187+
showNonMatchingResolutionError( e );
188+
}
189+
return null;
190+
}
191+
192+
/**
193+
* Opens the resolution level at {@code resolutionLevel} index as a new ImageJ dataset.
194+
* Index 0 is the highest resolution; each increment is the next coarser level.
195+
* <p>
196+
* Multiple calls — whether at the same or different level indices — each produce a separate
197+
* ImageJ {@code Dataset} (and a separate window), but all of them are backed by the same
198+
* {@link sc.fiji.ome.zarr.pyramid.Pyramidal5DImageData} object. The underlying
199+
* cached cell images and volatile images in
200+
* {@link sc.fiji.ome.zarr.pyramid.backend.PyramidContents} are the single source of truth
201+
* and are never loaded more than once per resolution level.
202+
*
203+
* @param resolutionLevel 0-based index into the resolution pyramid
204+
*/
205+
public Object openIJWithImage( final int resolutionLevel )
206+
{
207+
try
208+
{
209+
return openPyramidImage(
210+
() -> {
211+
final PyramidalDataset dataset = getPyramid().asPyramidalDataset( resolutionLevel );
212+
context.getService( UIService.class ).show( dataset );
213+
logger.info( "Opened dataset at resolution level {} in ImageJ: {}", resolutionLevel, inputUri );
214+
return null;
215+
},
216+
singleScaleImage -> ImageJFunctions.show( Cast.unchecked( singleScaleImage ) ) );
217+
}
218+
catch ( NonExistingResolutionLevelException e )
219+
{
220+
showNonExistingResolutionLevelError( e );
221+
}
222+
return null;
177223
}
178224

179225
public Object openBDVWithImage()
180226
{
181-
return openImage(
182-
BdvUtils::showBdvAndRegisterDataset,
183-
singleScaleImage -> BdvFunctions.show( singleScaleImage, "Image" ),
184-
"BigDataViewer"
185-
);
227+
try
228+
{
229+
return openPyramidImage(
230+
() -> {
231+
final PyramidalBdv< ? > dataset = new PyramidalBdv<>( getPyramid() );
232+
final PyramidalService pyramidalService = context.getService( PyramidalService.class );
233+
final Object result = BdvUtils.showBdvAndRegisterDataset( dataset, pyramidalService );
234+
logger.info( "Opened dataset in BigDataViewer: {}", inputUri );
235+
return result;
236+
},
237+
singleScaleImage -> null );
238+
}
239+
catch ( NoMatchingResolutionException e )
240+
{
241+
showNonMatchingResolutionError( e );
242+
}
243+
return null;
244+
}
245+
246+
private Pyramidal5DImageDataImpl< ?, ? > getPyramid()
247+
{
248+
if ( cachedPyramid == null )
249+
{
250+
final Integer preferredWidth;
251+
if ( settings == null || settings.getOpenBehavior().equals( ZarrOpenBehavior.IMAGEJ_HIGHEST_RESOLUTION ) )
252+
preferredWidth = null;
253+
else
254+
preferredWidth = settings.getPreferredMaxWidth();
255+
256+
final ZarrReaderBackend backend = settings == null
257+
? ZarrOpeningSettings.DEFAULT_READER_BACKEND
258+
: settings.getReaderBackend();
259+
switch ( backend )
260+
{
261+
case ZARR_JAVA:
262+
{
263+
@SuppressWarnings( { "rawtypes", "unchecked" } )
264+
final Pyramidal5DImageDataImpl< ?, ? > zarrJavaData =
265+
new Pyramidal5DImageDataImpl( context, new ZarrJavaPyramidBackend( inputUri ) );
266+
cachedPyramid = zarrJavaData;
267+
break;
268+
}
269+
case N5:
270+
default:
271+
cachedPyramid = new Pyramidal5DImageDataImpl<>( context, inputUri, preferredWidth );
272+
break;
273+
}
274+
}
275+
return cachedPyramid;
276+
}
277+
278+
private Object openPyramidImage( final Supplier< Object > multiScaleOpener, final Function< Img< ? >, Object > singleScaleOpener )
279+
{
280+
try
281+
{
282+
return multiScaleOpener.get();
283+
}
284+
catch ( MultiImageDatasetException e )
285+
{
286+
showMultiImageNotSupported( e );
287+
}
288+
catch ( NotAMultiscaleImageException e )
289+
{
290+
logger.warn( "Not a multiscale image: {}", e.getMessage() );
291+
showSingleScaleNotSupported();
292+
// TODO: openSingleScaleImage( singleScaleOpener ) when single-scale support is added
293+
}
294+
catch ( IllegalArgumentException | JsonSyntaxException e )
295+
{
296+
showNonZarrError( e );
297+
}
298+
return null;
186299
}
187300

188301
private void showMultiImageNotSupported( final MultiImageDatasetException e )
@@ -220,83 +333,33 @@ private void showNonMatchingResolutionError( final Exception e )
220333
logger.warn( "Not opening dataset: {}. Error message: {}", inputUri, e.getMessage() );
221334
}
222335

223-
Object openImage( final Function< PyramidalDataset< ? >, Object > multiScaleImageOpener,
224-
final Function< Img< ? >, Object > singleScaleImageOpener,
225-
final String message )
336+
private void showNonExistingResolutionLevelError( final NonExistingResolutionLevelException e )
337+
{
338+
errorHandler.accept( "Could not open resolution level from dataset: " + inputUri + "\n\n" + e.getMessage() );
339+
logger.warn( "Could not open resolution level: {}. Error message: {}", inputUri, e.getMessage() );
340+
}
341+
342+
Object openImage( final Function< PyramidalDataset, Object > multiScaleImageOpener,
343+
final Function< Img< ? >, Object > singleScaleImageOpener )
226344
{
227345
try
228346
{
229-
Object result = openMultiScaleImage( multiScaleImageOpener );
230-
logger.info( "Opened dataset in {}: {}", message, inputUri );
231-
return result;
232-
}
233-
catch ( MultiImageDatasetException e )
234-
{
235-
logger.warn( "Tried to open a multi image dataset: {}", e.getMessage() );
236-
showMultiImageNotSupported( e );
237-
}
238-
catch ( NotAMultiscaleImageException e )
239-
{
240-
logger.warn( "Not a multiscale image: {}", e.getMessage() );
241-
logger.info( "Try opening as single-scale image instead." );
242-
try
243-
{
244-
showSingleScaleNotSupported();
245-
//Object result = openSingleScaleImage( singleScaleImageOpener ); // currently not supported
246-
//logger.info( "Opened single scale image in {}: {}", message, inputUri );
247-
return null;
248-
}
249-
catch ( NotASingleScaleImageException ex )
250-
{
251-
showSingleScaleError( ex );
252-
}
347+
return openPyramidImage(
348+
() -> {
349+
final PyramidalDataset dataset = getPyramid().asPyramidalDataset();
350+
final Object result = multiScaleImageOpener.apply( dataset );
351+
logger.info( "Opened dataset: {}", inputUri );
352+
return result;
353+
},
354+
singleScaleImageOpener );
253355
}
254356
catch ( NoMatchingResolutionException e )
255357
{
256358
showNonMatchingResolutionError( e );
257359
}
258-
catch ( IllegalArgumentException | JsonSyntaxException e )
259-
{
260-
showNonZarrError( e );
261-
}
262360
return null;
263361
}
264362

265-
private Object openMultiScaleImage( final Function< PyramidalDataset< ? >, Object > multiScaleImageOpener )
266-
throws NotAMultiscaleImageException, NoMatchingResolutionException
267-
{
268-
final Integer preferredWidth;
269-
if ( settings == null || settings.getOpenBehavior().equals( ZarrOpenBehavior.IMAGEJ_HIGHEST_RESOLUTION ) )
270-
preferredWidth = null;
271-
else
272-
preferredWidth = settings.getPreferredMaxWidth();
273-
274-
final ZarrReaderBackend backend = settings == null
275-
? ZarrOpeningSettings.DEFAULT_READER_BACKEND
276-
: settings.getReaderBackend();
277-
278-
final Pyramidal5DImageDataImpl< ?, ? > data;
279-
switch ( backend )
280-
{
281-
case ZARR_JAVA:
282-
{
283-
@SuppressWarnings( { "rawtypes", "unchecked" } )
284-
final Pyramidal5DImageDataImpl< ?, ? > zarrJavaData =
285-
new Pyramidal5DImageDataImpl( context, new ZarrJavaPyramidBackend( inputUri ) );
286-
data = zarrJavaData;
287-
break;
288-
}
289-
case N5:
290-
default:
291-
data = new Pyramidal5DImageDataImpl<>( context, inputUri, preferredWidth );
292-
break;
293-
}
294-
295-
final Object result = multiScaleImageOpener.apply( data.asPyramidalDataset() );
296-
logger.info( "Opened multiscale image with {} backend: {}", backend, inputUri );
297-
return result;
298-
}
299-
300363
private Object openSingleScaleImage( final Function< Img< ? >, Object > singleScaleImageOpener ) throws NotASingleScaleImageException
301364
{
302365
N5Reader reader = new N5Factory().openReader( inputUri.toString() );

src/main/java/sc/fiji/ome/zarr/plugins/DnDHandlerPlugin.java

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,13 @@
66
* %%
77
* Redistribution and use in source and binary forms, with or without
88
* modification, are permitted provided that the following conditions are met:
9-
*
9+
*
1010
* 1. Redistributions of source code must retain the above copyright notice,
1111
* this list of conditions and the following disclaimer.
1212
* 2. Redistributions in binary form must reproduce the above copyright notice,
1313
* this list of conditions and the following disclaimer in the documentation
1414
* and/or other materials provided with the distribution.
15-
*
15+
*
1616
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
1717
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
1818
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
@@ -48,7 +48,6 @@
4848
import java.util.ArrayList;
4949

5050
import sc.fiji.ome.zarr.open.ZarrOpenActions;
51-
import sc.fiji.ome.zarr.util.BdvHandleService;
5251
import sc.fiji.ome.zarr.util.ZarrUtils;
5352

5453
@Plugin( type = IOPlugin.class, attrs = @Attr( name = "eager" ) )
@@ -59,9 +58,6 @@ public class DnDHandlerPlugin extends AbstractIOPlugin< Object >
5958
//the "innocent" product of the (hypothetical) file reading... which Fiji will not display
6059
private static final Object FAKE_INPUT = new ArrayList<>( 0 );
6160

62-
@Parameter
63-
private BdvHandleService bdvHandleService; //TODO, is it really used down-stream?
64-
6561
@Parameter
6662
private PrefService prefService;
6763

src/main/java/sc/fiji/ome/zarr/plugins/OpenInBDVCommand.java

Lines changed: 24 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,13 @@
66
* %%
77
* Redistribution and use in source and binary forms, with or without
88
* modification, are permitted provided that the following conditions are met:
9-
*
9+
*
1010
* 1. Redistributions of source code must retain the above copyright notice,
1111
* this list of conditions and the following disclaimer.
1212
* 2. Redistributions in binary form must reproduce the above copyright notice,
1313
* this list of conditions and the following disclaimer in the documentation
1414
* and/or other materials provided with the distribution.
15-
*
15+
*
1616
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
1717
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
1818
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
@@ -28,42 +28,45 @@
2828
*/
2929
package sc.fiji.ome.zarr.plugins;
3030

31-
import net.imagej.Dataset;
32-
import net.imglib2.util.Cast;
31+
import java.lang.invoke.MethodHandles;
3332

3433
import org.scijava.command.Command;
35-
import org.scijava.log.LogLevel;
36-
import org.scijava.log.LogService;
3734
import org.scijava.plugin.Parameter;
3835
import org.scijava.plugin.Plugin;
36+
import org.scijava.ui.UIService;
3937

40-
import sc.fiji.ome.zarr.pyramid.PyramidalDataset;
38+
import org.slf4j.Logger;
39+
import org.slf4j.LoggerFactory;
40+
41+
import sc.fiji.ome.zarr.pyramid.Pyramidal;
42+
import sc.fiji.ome.zarr.pyramid.PyramidalBdv;
43+
import sc.fiji.ome.zarr.util.PyramidalService;
4144
import sc.fiji.ome.zarr.util.BdvUtils;
4245

4346
@Plugin( type = Command.class, menuPath = "Plugins > OME-Zarr > Open Current OME-Zarr Image in BigDataViewer" )
4447
public class OpenInBDVCommand implements Command
4548
{
46-
@Parameter
47-
LogService logService;
49+
private static final Logger logger = LoggerFactory.getLogger( MethodHandles.lookup().lookupClass() );
4850

4951
@Parameter
50-
public Dataset dataset;
52+
private UIService uiService;
53+
54+
@Parameter( required = false )
55+
private PyramidalService pyramidalService;
56+
57+
@Parameter( required = false )
58+
private Pyramidal pyramidal;
5159

5260
@Override
5361
public void run()
5462
{
55-
logService.log( LogLevel.DEBUG, "Trying to open: " + dataset.getName() );
56-
logService.log( LogLevel.DEBUG, "Class: " + dataset.getClass() );
57-
logService.log( LogLevel.DEBUG, "Dataset instanceof PyramidalDataset: " + ( dataset instanceof PyramidalDataset ) );
58-
if ( dataset instanceof PyramidalDataset )
59-
{
60-
logService.log( 0, "Opening " + dataset.getClass() + " in BDV..." );
61-
PyramidalDataset< ? > pyramidalDataset = Cast.unchecked( dataset );
62-
BdvUtils.showBdvAndRegisterDataset( pyramidalDataset );
63-
}
64-
else
63+
logger.trace( "Running OpenInBDVCommand. pyramidal={}", pyramidal );
64+
if ( pyramidal == null )
6565
{
66-
logService.error( "Cannot display datasets of type " + dataset.getClass() + " in BDV." );
66+
uiService.showDialog( "The active image is not an OME-Zarr dataset.", "Open in BigDataViewer" );
67+
return;
6768
}
69+
final PyramidalBdv< ? > bdvDataset = new PyramidalBdv<>( pyramidal.getPyramidal5DImageData() );
70+
BdvUtils.showBdvAndRegisterDataset( bdvDataset, pyramidalService );
6871
}
6972
}

0 commit comments

Comments
 (0)