Skip to content

Commit 30e9510

Browse files
stefanhahmannclaude
andcommitted
Add static factory methods open / openWithN5 / openWithZarrJava to Pyramidal5DImageData
Lets API consumers open an OME-Zarr image without having to import Pyramidal5DImageDataImpl or the backend classes directly: Pyramidal5DImageData.open(context, path, preferredWidth) Pyramidal5DImageData.openWithN5(context, path, preferredWidth) Pyramidal5DImageData.openWithZarrJava(context, path, preferredWidth) open() reads the configured ZarrReaderBackend from the context's PrefService and delegates to one of the explicit factories, so callers who do not care about backend selection get the user-configured default. The backend-specific factories let callers force a particular library when they need to. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
1 parent 8cab531 commit 30e9510

2 files changed

Lines changed: 257 additions & 0 deletions

File tree

src/main/java/sc/fiji/ome/zarr/pyramid/Pyramidal5DImageData.java

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,14 @@
3737
import bdv.viewer.SourceAndConverter;
3838
import ij.ImagePlus;
3939
import mpicbg.spim.data.sequence.VoxelDimensions;
40+
import org.scijava.Context;
41+
import org.scijava.prefs.PrefService;
42+
43+
import sc.fiji.ome.zarr.pyramid.exceptions.NoMatchingResolutionException;
44+
import sc.fiji.ome.zarr.pyramid.backend.zarrjava.ZarrJavaPyramidBackend;
4045
import sc.fiji.ome.zarr.pyramid.metadata.Omero;
46+
import sc.fiji.ome.zarr.settings.ZarrDragAndDropOpenSettings;
47+
import sc.fiji.ome.zarr.settings.ZarrReaderBackend;
4148

4249
/**
4350
* 5D multi-resolution array data
@@ -88,4 +95,59 @@ default Omero getOmeroProperties()
8895
}
8996

9097
ImagePlus asImagePlus();
98+
99+
/**
100+
* Opens an OME-Zarr image using the backend configured in the context's
101+
* {@link ZarrDragAndDropOpenSettings}, falling back to the N5 backend if no
102+
* settings service is present.
103+
*
104+
* @param preferredWidth maximum width along x for the returned dataset;
105+
* {@code null} selects the highest available resolution
106+
* @throws NoMatchingResolutionException if {@code preferredWidth} is smaller
107+
* than the width of the coarsest resolution level
108+
*/
109+
static < T extends NativeType< T > & RealType< T > > Pyramidal5DImageData< T > open(
110+
final Context context, final String path, final Integer preferredWidth )
111+
{
112+
final ZarrReaderBackend backend = ZarrDragAndDropOpenSettings
113+
.loadSettingsFromPreferences( context.getService( PrefService.class ) )
114+
.getReaderBackend();
115+
switch ( backend )
116+
{
117+
case ZARR_JAVA:
118+
return openWithZarrJava( context, path, preferredWidth );
119+
case N5:
120+
default:
121+
return openWithN5( context, path, preferredWidth );
122+
}
123+
}
124+
125+
/**
126+
* Opens an OME-Zarr image using the N5-universe backend.
127+
*
128+
* @param preferredWidth maximum width along x for the returned dataset;
129+
* {@code null} selects the highest available resolution
130+
* @throws NoMatchingResolutionException if {@code preferredWidth} is smaller
131+
* than the width of the coarsest resolution level
132+
*/
133+
static < T extends NativeType< T > & RealType< T > > Pyramidal5DImageData< T > openWithN5(
134+
final Context context, final String path, final Integer preferredWidth )
135+
{
136+
return new Pyramidal5DImageDataImpl<>( context, path, preferredWidth );
137+
}
138+
139+
/**
140+
* Opens an OME-Zarr image using the zarr-java backend.
141+
*
142+
* @param preferredWidth maximum width along x for the returned dataset;
143+
* {@code null} selects the highest available resolution
144+
* @throws NoMatchingResolutionException if {@code preferredWidth} is smaller
145+
* than the width of the coarsest resolution level
146+
*/
147+
@SuppressWarnings( { "rawtypes", "unchecked" } )
148+
static < T extends NativeType< T > & RealType< T > > Pyramidal5DImageData< T > openWithZarrJava(
149+
final Context context, final String path, final Integer preferredWidth )
150+
{
151+
return new Pyramidal5DImageDataImpl( context, new ZarrJavaPyramidBackend( path, preferredWidth ) );
152+
}
91153
}
Lines changed: 195 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,195 @@
1+
/*-
2+
* #%L
3+
* OME-Zarr extras for Fiji
4+
* %%
5+
* Copyright (C) 2022 - 2026 SciJava developers
6+
* %%
7+
* Redistribution and use in source and binary forms, with or without
8+
* modification, are permitted provided that the following conditions are met:
9+
*
10+
* 1. Redistributions of source code must retain the above copyright notice,
11+
* this list of conditions and the following disclaimer.
12+
* 2. Redistributions in binary form must reproduce the above copyright notice,
13+
* this list of conditions and the following disclaimer in the documentation
14+
* and/or other materials provided with the distribution.
15+
*
16+
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
17+
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
18+
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
19+
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE
20+
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
21+
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
22+
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
23+
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
24+
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
25+
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
26+
* POSSIBILITY OF SUCH DAMAGE.
27+
* #L%
28+
*/
29+
package sc.fiji.ome.zarr.pyramid;
30+
31+
import static org.junit.jupiter.api.Assertions.assertEquals;
32+
import static org.junit.jupiter.api.Assertions.assertNotNull;
33+
import static org.junit.jupiter.api.Assertions.assertThrows;
34+
35+
import java.net.URISyntaxException;
36+
import java.nio.file.Path;
37+
38+
import org.junit.jupiter.api.Test;
39+
import org.scijava.Context;
40+
import org.scijava.prefs.PrefService;
41+
42+
import sc.fiji.ome.zarr.pyramid.exceptions.NoMatchingResolutionException;
43+
import sc.fiji.ome.zarr.settings.ZarrDragAndDropOpenSettings;
44+
import sc.fiji.ome.zarr.settings.ZarrReaderBackend;
45+
import sc.fiji.ome.zarr.util.ZarrTestUtils;
46+
47+
/**
48+
* Unit tests for the three static factory methods on
49+
* {@link Pyramidal5DImageData}: {@link Pyramidal5DImageData#openWithN5},
50+
* {@link Pyramidal5DImageData#openWithZarrJava}, and
51+
* {@link Pyramidal5DImageData#open} (which dispatches based on the user's
52+
* configured {@link ZarrReaderBackend}).
53+
* <p>
54+
* Backend-internal correctness (axis handling, omero metadata, pyramid
55+
* reading, etc.) is covered by {@link N5BackedPyramidal5DImageDataTest}
56+
* and {@link ZarrJavaBackedPyramidal5DImageDataTest} via
57+
* {@link Pyramidal5DImageDataTestBase}; this class only exercises the
58+
* factory layer.
59+
*/
60+
class Pyramidal5DImageDataOpenTest
61+
{
62+
private static final String TEST_RESOURCE = "sc/fiji/ome/zarr/util/5d_testing/5d_dataset_v5.ome.zarr";
63+
64+
@Test
65+
void testOpenWithN5ReturnsWorkingDataset() throws URISyntaxException
66+
{
67+
final Path path = ZarrTestUtils.resourcePath( TEST_RESOURCE );
68+
try (Context context = new Context())
69+
{
70+
final Pyramidal5DImageData< ? > data = Pyramidal5DImageData.openWithN5( context, path.toString(), null );
71+
assertWorkingDataset( data );
72+
}
73+
}
74+
75+
@Test
76+
void testOpenWithN5HonorsPreferredWidth() throws URISyntaxException
77+
{
78+
final Path path = ZarrTestUtils.resourcePath( TEST_RESOURCE );
79+
try (Context context = new Context())
80+
{
81+
// preferredWidth=50 fits the second pyramid level (32 px), not the
82+
// full-resolution one (64 px), so the returned dataset is 32x32.
83+
final Pyramidal5DImageData< ? > data = Pyramidal5DImageData.openWithN5( context, path.toString(), 50 );
84+
assertEquals( 32, data.asDataset().getImgPlus().dimension( 0 ) );
85+
assertEquals( 32, data.asDataset().getImgPlus().dimension( 1 ) );
86+
}
87+
}
88+
89+
@Test
90+
void testOpenWithN5ThrowsWhenPreferredWidthBelowAllLevels() throws URISyntaxException
91+
{
92+
final String pathStr = ZarrTestUtils.resourcePath( TEST_RESOURCE ).toString();
93+
try (Context context = new Context())
94+
{
95+
assertThrows( NoMatchingResolutionException.class,
96+
() -> Pyramidal5DImageData.openWithN5( context, pathStr, 16 ) );
97+
}
98+
}
99+
100+
@Test
101+
void testOpenWithZarrJavaReturnsWorkingDataset() throws URISyntaxException
102+
{
103+
final Path path = ZarrTestUtils.resourcePath( TEST_RESOURCE );
104+
try (Context context = new Context())
105+
{
106+
final Pyramidal5DImageData< ? > data = Pyramidal5DImageData.openWithZarrJava( context, path.toString(), null );
107+
assertWorkingDataset( data );
108+
}
109+
}
110+
111+
@Test
112+
void testOpenWithZarrJavaHonorsPreferredWidth() throws URISyntaxException
113+
{
114+
final Path path = ZarrTestUtils.resourcePath( TEST_RESOURCE );
115+
try (Context context = new Context())
116+
{
117+
final Pyramidal5DImageData< ? > data = Pyramidal5DImageData.openWithZarrJava( context, path.toString(), 50 );
118+
assertEquals( 32, data.asDataset().getImgPlus().dimension( 0 ) );
119+
assertEquals( 32, data.asDataset().getImgPlus().dimension( 1 ) );
120+
}
121+
}
122+
123+
@Test
124+
void testOpenWithZarrJavaThrowsWhenPreferredWidthBelowAllLevels() throws URISyntaxException
125+
{
126+
final String pathStr = ZarrTestUtils.resourcePath( TEST_RESOURCE ).toString();
127+
try (Context context = new Context())
128+
{
129+
assertThrows( NoMatchingResolutionException.class,
130+
() -> Pyramidal5DImageData.openWithZarrJava( context, pathStr, 16 ) );
131+
}
132+
}
133+
134+
@Test
135+
void testOpenDispatchesToN5WhenSettingsSelectN5() throws URISyntaxException
136+
{
137+
final Path path = ZarrTestUtils.resourcePath( TEST_RESOURCE );
138+
try (Context context = new Context())
139+
{
140+
persistBackendChoice( context, ZarrReaderBackend.N5 );
141+
final Pyramidal5DImageData< ? > data = Pyramidal5DImageData.open( context, path.toString(), null );
142+
assertWorkingDataset( data );
143+
}
144+
}
145+
146+
@Test
147+
void testOpenDispatchesToZarrJavaWhenSettingsSelectZarrJava() throws URISyntaxException
148+
{
149+
final Path path = ZarrTestUtils.resourcePath( TEST_RESOURCE );
150+
try (Context context = new Context())
151+
{
152+
persistBackendChoice( context, ZarrReaderBackend.ZARR_JAVA );
153+
final Pyramidal5DImageData< ? > data = Pyramidal5DImageData.open( context, path.toString(), null );
154+
assertWorkingDataset( data );
155+
}
156+
}
157+
158+
@Test
159+
void testOpenWithFreshContextUsesDefaultBackend() throws URISyntaxException
160+
{
161+
// No prefs written → loadSettingsFromPreferences falls back to the
162+
// project's DEFAULT_READER_BACKEND, and open() must succeed.
163+
final Path path = ZarrTestUtils.resourcePath( TEST_RESOURCE );
164+
try (Context context = new Context())
165+
{
166+
final Pyramidal5DImageData< ? > data = Pyramidal5DImageData.open( context, path.toString(), null );
167+
assertWorkingDataset( data );
168+
}
169+
}
170+
171+
private static void persistBackendChoice( final Context context, final ZarrReaderBackend backend )
172+
{
173+
final PrefService prefs = context.getService( PrefService.class );
174+
final ZarrDragAndDropOpenSettings settings = ZarrDragAndDropOpenSettings.loadSettingsFromPreferences( prefs );
175+
settings.setReaderBackend( backend );
176+
settings.saveSettingsToPreferences( prefs );
177+
}
178+
179+
/**
180+
* Sanity assertions that any of the three open() methods must satisfy
181+
* for our 5D test dataset (64x64x16, 3 channels, 4 timepoints, 2 levels).
182+
*/
183+
private static void assertWorkingDataset( final Pyramidal5DImageData< ? > data )
184+
{
185+
assertNotNull( data );
186+
assertNotNull( data.asDataset() );
187+
assertNotNull( data.asPyramidalDataset() );
188+
assertNotNull( data.asSources() );
189+
assertEquals( 64, data.asDataset().getImgPlus().dimension( 0 ) );
190+
assertEquals( 64, data.asDataset().getImgPlus().dimension( 1 ) );
191+
assertEquals( 3, data.numChannels() );
192+
assertEquals( 4, data.numTimepoints() );
193+
assertEquals( 2, data.numResolutionLevels() );
194+
}
195+
}

0 commit comments

Comments
 (0)