3434import static org .junit .jupiter .api .Assertions .assertFalse ;
3535import static org .junit .jupiter .api .Assertions .assertNotNull ;
3636import static org .junit .jupiter .api .Assertions .assertNotSame ;
37- import static org .junit .jupiter .api .Assertions .assertNull ;
3837import static org .junit .jupiter .api .Assertions .assertSame ;
3938import static org .junit .jupiter .api .Assertions .assertTrue ;
4039import static org .mockito .Mockito .any ;
7776import java .util .Arrays ;
7877import java .util .List ;
7978import java .util .concurrent .atomic .AtomicBoolean ;
80- import java .util .concurrent .atomic .AtomicInteger ;
8179import java .util .concurrent .atomic .AtomicReference ;
8280import java .util .function .Consumer ;
83- import java .util .function .Function ;
8481import java .util .stream .Stream ;
8582
8683import bdv .tools .brightness .ConverterSetup ;
9794import ij .ImagePlus ;
9895import ome .zarr .fijiui .settings .UserScriptSettings ;
9996import ome .zarr .fiji .Pyramidal ;
97+ import ome .zarr .imglib2 .PyramidBackend ;
10098import ome .zarr .imglib2 .PyramidContents ;
99+ import ome .zarr .fiji .open .ZarrOpener ;
101100import ome .zarr .fiji .PyramidalBdv ;
102101import ome .zarr .fiji .PyramidalDataset ;
103102import ome .zarr .fijiui .open .options .ZarrOpeningSettings ;
110109
111110class ZarrOpenActionsTest
112111{
112+
113113 static Stream < ZarrReaderBackend > readerBackends ()
114114 {
115115 return Stream .of ( ZarrReaderBackend .N5 , ZarrReaderBackend .ZARR_JAVA );
116116 }
117117
118+ /**
119+ * Loads the dataset headlessly through {@link ZarrOpener#getContents()} with
120+ * the given backend, without instantiating any UI. Returns the loaded
121+ * {@link PyramidContents}; throws the relevant domain exception (e.g.
122+ * {@link ome.zarr.imglib2.exceptions.NotAMultiscaleImageException} or
123+ * {@link ome.zarr.imglib2.exceptions.MultiImageDatasetException}).
124+ * Lets tests assert that a dataset opens as a multiscale image without showing a window.
125+ */
126+ private static PyramidContents < ? > loadMultiscaleHeadless ( final URI uri , final Context context ,
127+ final ZarrReaderBackend backend )
128+ {
129+ final PyramidBackend pyramidBackend = backend .createBackend ();
130+ final ZarrOpener opener = new ZarrOpener ( uri , context , pyramidBackend , null , error -> {} );
131+ return opener .getContents ();
132+ }
133+
118134 static Stream < String > omeZarrExamples ()
119135 {
120136 return Stream .of (
@@ -287,18 +303,13 @@ void testOpenValidMultiScaleImagePath( String resource ) throws URISyntaxExcepti
287303 Path path = ZarrTestUtils .resourcePath ( resource );
288304 try (Context context = new Context ())
289305 {
290- ZarrOpenActions actions = new ZarrOpenActions ( path .toUri (), context );
291- AtomicInteger multiScaleCounter = new AtomicInteger ( 0 );
292- AtomicInteger singleScaleCounter = new AtomicInteger ( 0 );
293- Function < PyramidalDataset , Object > multiScaleOpeningCounter = dataset -> multiScaleCounter .incrementAndGet ();
294- Function < Img < ? >, Object > singleScaleOpeningCounter = img -> singleScaleCounter .incrementAndGet ();
295- actions .openImage ( multiScaleOpeningCounter , singleScaleOpeningCounter );
296- assertEquals ( 1 , multiScaleCounter .get () );
297- assertEquals ( 0 , singleScaleCounter .get () );
306+ assertNotNull ( loadMultiscaleHeadless ( path .toUri (), context , ZarrOpeningSettings .DEFAULT_READER_BACKEND ),
307+ "Expected " + resource + " to open as a multiscale image" );
298308 }
299309 }
300310
301311 @ Test
312+ @ SuppressWarnings ( "java:S1612" )
302313 void testOpenValidSingleScaleImagePath () throws URISyntaxException
303314 {
304315 String [] validPaths = {
@@ -310,19 +321,19 @@ void testOpenValidSingleScaleImagePath() throws URISyntaxException
310321 for ( String invalidPath : validPaths )
311322 {
312323 Path path = ZarrTestUtils .resourcePath ( invalidPath );
313- ZarrOpenActions actions = new ZarrOpenActions ( path .toUri (), context , null , System .out ::println );
314- AtomicInteger multiScaleCounter = new AtomicInteger ( 0 );
315- AtomicInteger singleScaleCounter = new AtomicInteger ( 0 );
316- Function < PyramidalDataset , Object > multiScaleOpeningCounter = dataset -> multiScaleCounter .incrementAndGet ();
317- Function < Img < ? >, Object > singleScaleOpeningCounter = img -> singleScaleCounter .incrementAndGet ();
318- actions .openImage ( multiScaleOpeningCounter , singleScaleOpeningCounter );
319- assertEquals ( 0 , multiScaleCounter .get () );
320- assertEquals ( 0 , singleScaleCounter .get () ); // currently not supported
324+ AtomicReference < String > capturedError = new AtomicReference <>();
325+ ZarrOpenActions actions = new ZarrOpenActions ( path .toUri (), context , null , capturedError ::set );
326+ assertDoesNotThrow ( () -> {
327+ actions .openIJWithImage ();
328+ } );
329+ assertNotNull ( capturedError .get (),
330+ "Single-scale path " + invalidPath + " should be reported as not (yet) supported" );
321331 }
322332 }
323333 }
324334
325335 @ Test
336+ @ SuppressWarnings ( "java:S1612" )
326337 void testOpenInvalidImagePaths () throws URISyntaxException
327338 {
328339 String [] invalidPaths = {
@@ -335,15 +346,16 @@ void testOpenInvalidImagePaths() throws URISyntaxException
335346 {
336347 Path path = ZarrTestUtils .resourcePath ( invalidPath );
337348 ZarrOpenActions actions = new ZarrOpenActions ( path .toUri (), context , null , System .out ::println );
338- Function < PyramidalDataset , Object > multiScaleNoOp = pyramidalDataset -> null ;
339- Function < Img < ? >, Object > singleScaleNoOp = img -> null ;
340- assertDoesNotThrow ( () -> actions . openImage ( multiScaleNoOp , singleScaleNoOp ) );
349+ assertDoesNotThrow ( () -> {
350+ actions . openIJWithImage () ;
351+ } );
341352 }
342353 }
343354 }
344355
345356 @ ParameterizedTest
346357 @ MethodSource ( "readerBackends" )
358+ @ SuppressWarnings ( "java:S1612" )
347359 void testOpenBioformats2rawCollectionRootReportsMultiImage ( ZarrReaderBackend backend ) throws URISyntaxException
348360 {
349361 Path path = ZarrTestUtils .resourcePath ( "ome/zarr/testdata/bioformats2raw_testing/bf2raw_dataset_v5.ome.zarr" );
@@ -354,13 +366,9 @@ void testOpenBioformats2rawCollectionRootReportsMultiImage( ZarrReaderBackend ba
354366 ZarrOpeningSettings settings = new ZarrOpeningSettings ();
355367 settings .setReaderBackend ( backend );
356368 ZarrOpenActions actions = new ZarrOpenActions ( path .toUri (), context , settings , errorHandler );
357- AtomicInteger multiScaleCounter = new AtomicInteger ( 0 );
358- AtomicInteger singleScaleCounter = new AtomicInteger ( 0 );
359- Function < PyramidalDataset , Object > multiScaleOpener = dataset -> multiScaleCounter .incrementAndGet ();
360- Function < Img < ? >, Object > singleScaleOpener = img -> singleScaleCounter .incrementAndGet ();
361- assertDoesNotThrow ( () -> actions .openImage ( multiScaleOpener , singleScaleOpener ) );
362- assertEquals ( 0 , multiScaleCounter .get (), "Multi-image collection must not be opened as a single multiscale image" );
363- assertEquals ( 0 , singleScaleCounter .get () );
369+ assertDoesNotThrow ( () -> {
370+ actions .openIJWithImage ();
371+ } );
364372 assertNotNull ( capturedError .get (), "Error handler should have been called for backend " + backend );
365373 assertTrue ( capturedError .get ().contains ( "multiple images" ),
366374 "Expected multi-image message from backend, got: " + capturedError .get () );
@@ -380,37 +388,24 @@ void testOpenBioformats2rawCollectionChildOpens( ZarrReaderBackend backend ) thr
380388 for ( String childPath : childPaths )
381389 {
382390 Path path = ZarrTestUtils .resourcePath ( childPath );
383- AtomicReference < String > capturedError = new AtomicReference <>();
384- Consumer < String > errorHandler = capturedError ::set ;
385- ZarrOpeningSettings settings = new ZarrOpeningSettings ();
386- settings .setReaderBackend ( backend );
387- ZarrOpenActions actions = new ZarrOpenActions ( path .toUri (), context , settings , errorHandler );
388- AtomicInteger multiScaleCounter = new AtomicInteger ( 0 );
389- AtomicInteger singleScaleCounter = new AtomicInteger ( 0 );
390- Function < PyramidalDataset , Object > multiScaleOpener = dataset -> multiScaleCounter .incrementAndGet ();
391- Function < Img < ? >, Object > singleScaleOpener = img -> singleScaleCounter .incrementAndGet ();
392- assertDoesNotThrow ( () -> actions .openImage ( multiScaleOpener , singleScaleOpener ),
393- "Opening child image " + childPath + " should not throw" );
394- assertEquals ( 1 , multiScaleCounter .get (),
395- "Child image " + childPath + " should be opened as a multiscale image" );
396- assertEquals ( 0 , singleScaleCounter .get () );
397- assertNull ( capturedError .get (),
398- "Error handler should not have been called for child " + childPath + ", got: " + capturedError .get () );
391+ assertNotNull ( loadMultiscaleHeadless ( path .toUri (), context , backend ),
392+ "Child image " + childPath + " should open as a multiscale image" );
399393 }
400394 }
401395 }
402396
403397 @ Test
398+ @ SuppressWarnings ( "java:S1612" )
404399 void testOpenNonMatchingResolution () throws URISyntaxException
405400 {
406401 try (Context context = new Context ())
407402 {
408403 Path path = ZarrTestUtils .resourcePath ( "ome/zarr/testdata/5d_testing/5d_dataset_v4.ome.zarr" );
409404 ZarrOpeningSettings settings = new ZarrOpeningSettings ( ZarrOpenBehavior .IMAGEJ_CUSTOM_RESOLUTION , 10 );
410405 ZarrOpenActions actions = new ZarrOpenActions ( path .toUri (), context , settings , System .out ::println );
411- Function < PyramidalDataset , Object > multiScaleNoOp = pyramidalDataset -> null ;
412- Function < Img < ? >, Object > singleScaleNoOp = img -> null ;
413- assertDoesNotThrow ( () -> actions . openImage ( multiScaleNoOp , singleScaleNoOp ) );
406+ assertDoesNotThrow ( () -> {
407+ actions . openIJWithImage () ;
408+ } );
414409 }
415410 }
416411
@@ -659,6 +654,45 @@ void testOpenSingleScaleImageInBDV( String resource ) throws URISyntaxException
659654 }
660655 }
661656
657+ // --- S3 integration tests (require network access) ---
658+
659+ static final URI S3_JANELIA_CHOROID_PLEXUS =
660+ URI .create ( "s3://janelia-cosem-datasets/jrc_mus-choroid-plexus-3/jrc_mus-choroid-plexus-3.zarr/recon-1/em/fibsem-uint8" );
661+
662+ @ ParameterizedTest
663+ @ MethodSource ( "readerBackends" )
664+ void openImageFromS3 ( final ZarrReaderBackend backend )
665+ {
666+ try (Context context = new Context ())
667+ {
668+ final PyramidContents < ? > contents = loadMultiscaleHeadless ( S3_JANELIA_CHOROID_PLEXUS , context , backend );
669+ assertNotNull ( contents , "Expected the S3 dataset to open as a multiscale image for backend " + backend );
670+ }
671+ }
672+
673+ @ ParameterizedTest
674+ @ MethodSource ( "readerBackends" )
675+ @ SuppressWarnings ( "java:S1612" )
676+ void storeAccessErrorIsReportedToErrorHandler ( final ZarrReaderBackend backend )
677+ {
678+ try (Context context = new Context ())
679+ {
680+ final URI uri = URI .create ( "s3://nonexistent-bucket/some/path" );
681+ final AtomicReference < String > capturedError = new AtomicReference <>();
682+ final ZarrOpeningSettings settings = new ZarrOpeningSettings ();
683+ settings .setReaderBackend ( backend );
684+
685+ final ZarrOpenActions actions = new ZarrOpenActions ( uri , context , settings , capturedError ::set );
686+ assertDoesNotThrow ( () -> {
687+ actions .openIJWithImage ();
688+ }, "Store access failures must not escape openIJWithImage() for backend " + backend );
689+
690+ assertNotNull ( capturedError .get (), "Error handler should have been called for backend " + backend );
691+ assertTrue ( capturedError .get ().contains ( uri .toString () ),
692+ "Error message should contain the URI for backend " + backend + ", got: " + capturedError .get () );
693+ }
694+ }
695+
662696 @ Test
663697 void testRunScriptWithNoScriptSpecified () throws URISyntaxException , InterruptedException , InvocationTargetException
664698 {
0 commit comments