diff --git a/pom.xml b/pom.xml
index d6d2d1a3a..dba5174a5 100644
--- a/pom.xml
+++ b/pom.xml
@@ -63,7 +63,7 @@
+ * Default access types are + *
+ *+ * {@code SimpleCacheArrayLoader} is supposed to load data one specific + * image. {@code loadArray()} will not get information about which + * timepoint, resolution level, etc a requested block belongs to. + *
+ * This is in contrast to + * {@link CacheArrayLoader#loadArray(int, int, int, int[], long[])}, where + * all information to identify a particular block in a whole dataset is + * provided. + * + * @param gridPosition + * the coordinate of the cell in the cell grid. + * @param cellDimensions + * the dimensions of the cell. + * @param timepoint + * the timepoint. + * + * @return loaded cell data. + */ + A loadArrayAtTimepoint( long[] gridPosition, int[] cellDimensions, int timepoint ) throws IOException; +} diff --git a/src/main/java/org/mastodon/mamut/io/img/cache/MastodonVolatileGlobalCellCache.java b/src/main/java/org/mastodon/mamut/io/img/cache/MastodonVolatileGlobalCellCache.java new file mode 100644 index 000000000..cd5b3d0b1 --- /dev/null +++ b/src/main/java/org/mastodon/mamut/io/img/cache/MastodonVolatileGlobalCellCache.java @@ -0,0 +1,301 @@ +/*- + * #%L + * Mastodon + * %% + * Copyright (C) 2014 - 2024 Mastodon 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% + */ +/* + * #%L + * BigDataViewer core classes with minimal dependencies. + * %% + * Copyright (C) 2012 - 2023 BigDataViewer 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 org.mastodon.mamut.io.img.cache; + +import bdv.cache.SharedQueue; +import bdv.img.cache.CacheArrayLoader; +import bdv.img.cache.CreateInvalidVolatileCell; +import bdv.img.cache.EmptyArrayCreator; +import bdv.img.cache.SimpleCacheArrayLoader; +import bdv.img.cache.VolatileCachedCellImg; +import bdv.img.cache.VolatileGlobalCellCache; +import java.util.concurrent.Callable; + +import bdv.cache.CacheControl; +import net.imglib2.cache.Cache; +import net.imglib2.cache.CacheLoader; +import net.imglib2.cache.LoaderCache; +import net.imglib2.cache.queue.BlockingFetchQueues; +import net.imglib2.cache.ref.SoftRefLoaderCache; +import net.imglib2.cache.ref.WeakRefVolatileCache; +import net.imglib2.cache.util.KeyBimap; +import net.imglib2.cache.volatiles.CacheHints; +import net.imglib2.cache.volatiles.VolatileCache; +import net.imglib2.img.basictypeaccess.DataAccess; +import net.imglib2.img.cell.Cell; +import net.imglib2.img.cell.CellGrid; +import net.imglib2.type.NativeType; + +public class MastodonVolatileGlobalCellCache implements CacheControl +{ + /** + * Key for a cell identified by timepoint, setup, level, and index + * (flattened spatial coordinate). + */ + public static class Key + { + private final int timepoint; + + private final int setup; + + private final int level; + + private final long index; + + /** + * Create a Key for the specified cell. + * + * @param timepoint + * timepoint coordinate of the cell + * @param setup + * setup coordinate of the cell + * @param level + * level coordinate of the cell + * @param index + * index of the cell (flattened spatial coordinate of the + * cell) + */ + public Key( final int timepoint, final int setup, final int level, final long index ) + { + this.timepoint = timepoint; + this.setup = setup; + this.level = level; + this.index = index; + + int value = Long.hashCode( index ); + value = 31 * value + level; + value = 31 * value + setup; + value = 31 * value + timepoint; + hashcode = value; + } + + @Override + public boolean equals( final Object other ) + { + if ( this == other ) + return true; + if ( !( other instanceof VolatileGlobalCellCache.Key ) ) + return false; + final Key that = ( Key ) other; + return ( this.index == that.index ) && ( this.timepoint == that.timepoint ) && ( this.setup == that.setup ) + && ( this.level == that.level ); + } + + final int hashcode; + + @Override + public int hashCode() + { + return hashcode; + } + } + + private final BlockingFetchQueues< Callable< ? > > queue; + + protected final LoaderCache< Key, Cell< ? > > backingCache; + + /** + * Create a new global cache with a new fetch queue served by the specified + * number of fetcher threads. + * + * @param maxNumLevels + * the highest occurring mipmap level plus 1. + * @param numFetcherThreads + * how many threads should be created to load data. + */ + public MastodonVolatileGlobalCellCache( final int maxNumLevels, final int numFetcherThreads ) + { + queue = new SharedQueue( numFetcherThreads, maxNumLevels ); + backingCache = new SoftRefLoaderCache<>(); + } + + /** + * Create a new global cache with the specified fetch queue. (It is the + * callers responsibility to create fetcher threads that serve the queue.) + * + * @param queue + * queue to which asynchronous data loading jobs are submitted + */ + public MastodonVolatileGlobalCellCache( final BlockingFetchQueues< Callable< ? > > queue ) + { + this.queue = queue; + backingCache = new SoftRefLoaderCache<>(); + } + + /** + * Prepare the cache for providing data for the "next frame", + * by moving pending cell request to the prefetch queue + * ({@link BlockingFetchQueues#clearToPrefetch()}). + */ + @Override + public void prepareNextFrame() + { + queue.clearToPrefetch(); + } + + /** + * Remove all references to loaded data. + *
+ * Note that there may be pending cell requests which will re-populate the cache + * unless the fetch queue is cleared as well. + */ + public void clearCache() + { + backingCache.invalidateAll(); + } + + /** + * Create a {@link VolatileCachedCellImg} backed by this {@link VolatileGlobalCellCache}, + * using the provided {@link CacheArrayLoader} to load data. + * + * @param grid + * @param timepoint + * @param setup + * @param level + * @param cacheHints + * @param cacheArrayLoader + * @param type + * @return + */ + public < T extends NativeType< T >, A extends DataAccess > VolatileCachedCellImg< T, A > createImg( + final CellGrid grid, + final int timepoint, + final int setup, + final int level, + final CacheHints cacheHints, + final CacheArrayLoader< A > cacheArrayLoader, + final T type ) + { + final CacheLoader< Long, Cell< ? > > loader = key -> { + final int n = grid.numDimensions(); + final long[] cellMin = new long[ n ]; + final int[] cellDims = new int[ n ]; + grid.getCellDimensions( key, cellMin, cellDims ); + return new Cell<>( + cellDims, + cellMin, + cacheArrayLoader.loadArray( timepoint, setup, level, cellDims, cellMin ) ); + }; + return createImg( grid, timepoint, setup, level, cacheHints, loader, cacheArrayLoader.getEmptyArrayCreator(), type ); + } + + /** + * Create a {@link VolatileCachedCellImg} backed by this {@link VolatileGlobalCellCache}, + * using the provided {@link SimpleCacheArrayLoader} to load data. + * + * @param grid + * @param timepoint + * @param setup + * @param level + * @param cacheHints + * @param cacheArrayLoader + * @param type + * @return + */ + public < T extends NativeType< T >, A extends DataAccess > VolatileCachedCellImg< T, A > createImg( + final CellGrid grid, + final int timepoint, + final int setup, + final int level, + final CacheHints cacheHints, + final SimpleCacheArrayLoader< A > cacheArrayLoader, + final T type ) + { + final CacheLoader< Long, Cell< ? > > loader = key -> { + final int n = grid.numDimensions(); + final long[] cellMin = new long[ n ]; + final int[] cellDims = new int[ n ]; + final long[] cellGridPosition = new long[ n ]; + grid.getCellDimensions( key, cellMin, cellDims ); + grid.getCellGridPositionFlat( key, cellGridPosition ); + return new Cell<>( cellDims, cellMin, cacheArrayLoader.loadArray( cellGridPosition, cellDims ) ); + }; + return createImg( grid, timepoint, setup, level, cacheHints, loader, cacheArrayLoader.getEmptyArrayCreator(), type ); + } + + public < T extends NativeType< T >, A extends DataAccess > VolatileCachedCellImg< T, A > createImg( + final CellGrid grid, + final int timepoint, + final int setup, + final int level, + final CacheHints cacheHints, + final CacheLoader< Long, Cell< ? > > loader, + final EmptyArrayCreator< A > emptyArrayCreator, // optional, can be null + final T type ) + { + final KeyBimap< Long, Key > bimap = KeyBimap.build( + index -> new Key( timepoint, setup, level, index ), + key -> ( key.timepoint == timepoint && key.setup == setup && key.level == level ) + ? key.index + : null ); + + final Cache< Long, Cell< ? > > cache = backingCache + .mapKeys( bimap ) + .withLoader( loader ); + + final CreateInvalidVolatileCell< ? > createInvalid = ( emptyArrayCreator == null ) + ? CreateInvalidVolatileCell.get( grid, type, false ) + : new CreateInvalidVolatileCell<>( grid, type.getEntitiesPerPixel(), emptyArrayCreator ); + + final VolatileCache< Long, Cell< ? > > vcache = new WeakRefVolatileCache<>( cache, queue, createInvalid ); + + @SuppressWarnings( "unchecked" ) + final VolatileCachedCellImg< T, A > img = new VolatileCachedCellImg<>( grid, type, cacheHints, ( VolatileCache ) vcache ); + + return img; + } +} diff --git a/src/main/java/org/mastodon/mamut/io/loader/N5UniverseImgLoader.java b/src/main/java/org/mastodon/mamut/io/loader/N5UniverseImgLoader.java new file mode 100644 index 000000000..3fbbbec1b --- /dev/null +++ b/src/main/java/org/mastodon/mamut/io/loader/N5UniverseImgLoader.java @@ -0,0 +1,499 @@ +/*- + * #%L + * Mastodon + * %% + * Copyright (C) 2014 - 2024 Mastodon 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% + */ +/* + * Original code from bigdataviewer-core: + * https://github.com/bigdataviewer/bigdataviewer-core/blob/c47e2370ae9b9e281444f127cb36cd9ef1497336/src/main/java/bdv/img/n5/N5ImageLoader.java + * LICENSE is shown below: + * + * #%L + * BigDataViewer core classes with minimal dependencies. + * %% + * Copyright (C) 2012 - 2023 BigDataViewer 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 org.mastodon.mamut.io.loader; + +import java.io.IOException; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import org.janelia.saalfeldlab.n5.DataType; +import org.janelia.saalfeldlab.n5.DatasetAttributes; +import org.janelia.saalfeldlab.n5.N5Exception; +import org.janelia.saalfeldlab.n5.N5KeyValueReader; +import org.janelia.saalfeldlab.n5.N5Reader; +import org.janelia.saalfeldlab.n5.hdf5.N5HDF5Reader; +import org.janelia.saalfeldlab.n5.universe.N5Factory; +import org.janelia.saalfeldlab.n5.zarr.ZarrCompressor; +import org.janelia.saalfeldlab.n5.zarr.ZarrKeyValueReader; +import org.mastodon.mamut.io.img.cache.MastodonSimpleCacheArrayLoader; +import org.mastodon.mamut.io.img.cache.MastodonVolatileGlobalCellCache; +import org.mastodon.mamut.io.loader.adapter.N5HDF5ReaderToViewerImgLoaderAdapter; +import org.mastodon.mamut.io.loader.adapter.N5KeyValueReaderToViewerImgLoaderAdapter; +import org.mastodon.mamut.io.loader.adapter.ZarrKeyValueReaderToViewerImgLoaderAdapter; +import org.mastodon.mamut.io.loader.adapter.N5ReaderToViewerImgLoaderAdapter; +import org.mastodon.mamut.io.loader.util.mobie.OmeZarrMultiscales; +import org.mastodon.mamut.io.loader.util.mobie.OmeZarrMultiscalesAdapter; +import org.mastodon.mamut.io.loader.util.mobie.ZarrAxes; +import org.mastodon.mamut.io.loader.util.mobie.ZarrAxesAdapter; + +import com.amazonaws.auth.AWSCredentials; +import com.amazonaws.auth.DefaultAWSCredentialsProviderChain; +import com.google.gson.GsonBuilder; + +import bdv.AbstractViewerSetupImgLoader; +import bdv.ViewerImgLoader; +import bdv.cache.CacheControl; +import bdv.cache.SharedQueue; +import bdv.img.cache.SimpleCacheArrayLoader; +import bdv.img.n5.DataTypeProperties; +import bdv.util.ConstantRandomAccessible; +import bdv.util.MipmapTransforms; +import mpicbg.spim.data.generic.sequence.AbstractSequenceDescription; +import mpicbg.spim.data.generic.sequence.BasicViewSetup; +import mpicbg.spim.data.generic.sequence.ImgLoaderHint; +import mpicbg.spim.data.sequence.MultiResolutionImgLoader; +import mpicbg.spim.data.sequence.MultiResolutionSetupImgLoader; +import mpicbg.spim.data.sequence.VoxelDimensions; +import net.imglib2.Dimensions; +import net.imglib2.FinalDimensions; +import net.imglib2.FinalInterval; +import net.imglib2.RandomAccessibleInterval; +import net.imglib2.Volatile; +import net.imglib2.cache.CacheLoader; +import net.imglib2.cache.volatiles.CacheHints; +import net.imglib2.cache.volatiles.LoadingStrategy; +import net.imglib2.img.basictypeaccess.DataAccess; +import net.imglib2.img.cell.Cell; +import net.imglib2.img.cell.CellGrid; +import net.imglib2.img.cell.CellImg; +import net.imglib2.realtransform.AffineTransform3D; +import net.imglib2.type.NativeType; +import net.imglib2.util.Cast; +import net.imglib2.view.Views; + +/** + * {@link ViewerImgLoader} for N5/OME-Zarr/HDF5 image data using {@link N5Reader}. + */ +public class N5UniverseImgLoader implements ViewerImgLoader, MultiResolutionImgLoader +{ + private N5Reader n5; + + private N5Factory factory; + + private final String url; + + public String getUrl() + { + return url; + } + + private final String dataset; + + public String getDataset() + { + return dataset; + } + + private N5ReaderToViewerImgLoaderAdapter< ? > adapter; + + // TODO: it would be good if this would not be needed + // find available setups from the n5 + private final AbstractSequenceDescription< ?, ?, ? > seq; + + /** + * Maps setup id to {@link SetupImgLoader}. + */ + private final Map< Integer, SetupImgLoader< ?, ? > > setupImgLoaders = new HashMap<>(); + + /** + * Create a new {@link N5UniverseImgLoader} for the given URI and dataset. + * @param uri + * @param dataset + * @param sequenceDescription + * @param s3Credentials use the {@link DefaultAWSCredentialsProviderChain} if null + */ + public N5UniverseImgLoader( final String uri, final String dataset, final AbstractSequenceDescription< ?, ?, ? > sequenceDescription, + final AWSCredentials s3Credentials ) + { + this( uri, dataset, sequenceDescription ); + if ( s3Credentials == null ) + { + this.factory = this.factory.s3UseCredentials(); + } + else + { + this.factory = this.factory.s3UseCredentials( s3Credentials ); + } + } + + public N5UniverseImgLoader( final String uri, final String dataset, final AbstractSequenceDescription< ?, ?, ? > sequenceDescription ) + { + GsonBuilder gsonBuilder = new GsonBuilder(); + gsonBuilder.registerTypeAdapter( ZarrCompressor.class, ZarrCompressor.jsonAdapter ); + gsonBuilder.registerTypeAdapter( ZarrAxes.class, new ZarrAxesAdapter() ); + gsonBuilder.registerTypeAdapter( OmeZarrMultiscales.class, new OmeZarrMultiscalesAdapter() ); + this.factory = new N5Factory() + .cacheAttributes( true ) + .hdf5DefaultBlockSize( 64 ) + .zarrDimensionSeparator( "/" ) + .zarrMapN5Attributes( true ) + .gsonBuilder( gsonBuilder ); + this.url = uri; + this.dataset = dataset.endsWith( "/" ) ? dataset : dataset + "/"; + this.seq = sequenceDescription; + } + + public N5Reader getN5Reader() + { + return n5; + } + + private volatile boolean isOpen = false; + + private SharedQueue createdSharedQueue; + + private MastodonVolatileGlobalCellCache cache; + + private int requestedNumFetcherThreads = -1; + + private SharedQueue requestedSharedQueue; + + @Override + public synchronized void setNumFetcherThreads( final int n ) + { + requestedNumFetcherThreads = n; + } + + @Override + public void setCreatedSharedQueue( final SharedQueue createdSharedQueue ) + { + requestedSharedQueue = createdSharedQueue; + } + + public static N5ReaderToViewerImgLoaderAdapter< ? extends N5Reader > getAdapter( final N5Reader n5, final String dataset, + final AbstractSequenceDescription< ?, ?, ? > seq ) + { + if ( n5 instanceof N5HDF5Reader ) + { + return new N5HDF5ReaderToViewerImgLoaderAdapter( ( N5HDF5Reader ) n5, dataset ); + } + else if ( n5 instanceof ZarrKeyValueReader ) + { + return new ZarrKeyValueReaderToViewerImgLoaderAdapter( ( ZarrKeyValueReader ) n5, dataset ); + } + else if ( n5 instanceof N5KeyValueReader ) + { + return new N5KeyValueReaderToViewerImgLoaderAdapter( ( N5KeyValueReader ) n5, dataset ); + } + else + { + throw new UnsupportedOperationException( "Unsupported format'" ); + } + } + + /** + * Validate if the n5 is readable. + */ + public boolean validate() + { + try + { + open(); + return true; + } + catch ( Throwable e ) + { + return false; + } + } + + private void open() + { + if ( !isOpen ) + { + synchronized ( this ) + { + if ( isOpen ) + return; + + try + { + this.n5 = factory.openReader( url ); + this.adapter = getAdapter( n5, dataset, seq ); + int maxNumLevels = 0; + final List< ? extends BasicViewSetup > setups = seq.getViewSetupsOrdered(); + for ( final BasicViewSetup setup : setups ) + { + final int setupId = setup.getId(); + final SetupImgLoader< ?, ? > setupImgLoader = createSetupImgLoader( setupId ); + setupImgLoaders.put( setupId, setupImgLoader ); + maxNumLevels = Math.max( maxNumLevels, setupImgLoader.numMipmapLevels() ); + } + + final int numFetcherThreads = requestedNumFetcherThreads >= 0 + ? requestedNumFetcherThreads + : Math.max( 1, Runtime.getRuntime().availableProcessors() ); + final SharedQueue queue = requestedSharedQueue != null + ? requestedSharedQueue + : ( createdSharedQueue = new SharedQueue( numFetcherThreads, maxNumLevels ) ); + cache = new MastodonVolatileGlobalCellCache( queue ); + } + catch ( final IOException e ) + { + throw new RuntimeException( e ); + } + + isOpen = true; + } + } + } + + /** + * Clear the cache. Images that were obtained from + * this loader before {@link #close()} will stop working. Requesting images + * after {@link #close()} will cause the n5 to be reopened (with a + * new cache). + */ + public void close() + { + if ( isOpen ) + { + synchronized ( this ) + { + if ( !isOpen ) + return; + + if ( createdSharedQueue != null ) + createdSharedQueue.shutdown(); + cache.clearCache(); + + createdSharedQueue = null; + isOpen = false; + } + } + } + + @Override + public SetupImgLoader< ?, ? > getSetupImgLoader( final int setupId ) + { + open(); + return setupImgLoaders.get( setupId ); + } + + private < T extends NativeType< T >, V extends Volatile< T > & NativeType< V > > SetupImgLoader< T, V > + createSetupImgLoader( final int setupId ) throws IOException + { + DataType dataType = adapter.getSetupDataType( setupId ); + return new SetupImgLoader<>( setupId, Cast.unchecked( DataTypeProperties.of( dataType ) ) ); + } + + @Override + public CacheControl getCacheControl() + { + open(); + return cache; + } + + public class SetupImgLoader< T extends NativeType< T >, V extends Volatile< T > & NativeType< V > > + extends AbstractViewerSetupImgLoader< T, V > + implements MultiResolutionSetupImgLoader< T > + { + private final int setupId; + + private final double[][] mipmapResolutions; + + private final AffineTransform3D[] mipmapTransforms; + + public SetupImgLoader( final int setupId, final DataTypeProperties< T, V, ?, ? > props ) throws IOException + { + this( setupId, props.type(), props.volatileType() ); + } + + public SetupImgLoader( final int setupId, final T type, final V volatileType ) throws IOException + { + super( type, volatileType ); + this.setupId = setupId; + mipmapResolutions = adapter.getMipmapResolutions( setupId ); + mipmapTransforms = new AffineTransform3D[ mipmapResolutions.length ]; + for ( int level = 0; level < mipmapResolutions.length; level++ ) + mipmapTransforms[ level ] = MipmapTransforms.getMipmapTransformDefault( mipmapResolutions[ level ] ); + } + + @Override + public RandomAccessibleInterval< V > getVolatileImage( final int timepointId, final int level, final ImgLoaderHint... hints ) + { + return prepareCachedImage( timepointId, level, LoadingStrategy.BUDGETED, volatileType ); + } + + @Override + public RandomAccessibleInterval< T > getImage( final int timepointId, final int level, final ImgLoaderHint... hints ) + { + return prepareCachedImage( timepointId, level, LoadingStrategy.BLOCKING, type ); + } + + @Override + public Dimensions getImageSize( final int timepointId, final int level ) + { + try + { + final String pathName = getFullPathName( adapter.getPathNameFromSetupTimepointLevel( setupId, timepointId, level ) ); + final DatasetAttributes attributes = n5.getDatasetAttributes( pathName ); + return new FinalDimensions( attributes.getDimensions() ); + } + catch ( final RuntimeException e ) + { + return null; + } + } + + @Override + public double[][] getMipmapResolutions() + { + return mipmapResolutions; + } + + @Override + public AffineTransform3D[] getMipmapTransforms() + { + return mipmapTransforms; + } + + @Override + public int numMipmapLevels() + { + return mipmapResolutions.length; + } + + @Override + public VoxelDimensions getVoxelSize( final int timepointId ) + { + return null; + } + + /** + * Create a {@link CellImg} backed by the cache. + */ + private < K extends NativeType< K > > RandomAccessibleInterval< K > prepareCachedImage( final int timepointId, final int level, + final LoadingStrategy loadingStrategy, final K type ) + { + try + { + final long[] dimensions = adapter.getDimensions( setupId, timepointId, level ); + final int[] cellDimensions = adapter.getCellDimensions( setupId, timepointId, level ); + final CellGrid grid = new CellGrid( dimensions, cellDimensions ); + final int n = grid.numDimensions(); + + final int priority = numMipmapLevels() - 1 - level; + final CacheHints cacheHints = new CacheHints( loadingStrategy, priority, false ); + + final SimpleCacheArrayLoader< ? > cacheArrayLoader = + adapter.createCacheArrayLoader( setupId, timepointId, level, grid ); + final CacheLoader< Long, Cell< ? > > loader = key -> { + final long[] cellGridMin = new long[ n ]; + final int[] cellGridDims = new int[ n ]; + final long[] cellGridPosition = new long[ n ]; + grid.getCellDimensions( key, cellGridMin, cellGridDims ); + grid.getCellGridPositionFlat( key, cellGridPosition ); + final long[] cellMin = Arrays.copyOf( cellGridMin, 3 ); + final int[] cellDims = Arrays.copyOf( cellGridDims, 3 ); + if ( n < 3 ) + { + Arrays.fill( cellDims, n, 3, 1 ); + } + DataAccess dataAccess = null; + if ( adapter instanceof N5ReaderToViewerImgLoaderAdapter && n == 4 ) + { + dataAccess = ( ( MastodonSimpleCacheArrayLoader< ? > ) cacheArrayLoader ).loadArrayAtTimepoint( cellGridPosition, + cellDims, timepointId ); + } + else + { + dataAccess = cacheArrayLoader.loadArray( cellGridPosition, cellDims ); + } + return new Cell<>( cellDims, cellMin, dataAccess ); + }; + final long[] imgDimensions = Arrays.copyOf( dimensions, 3 ); + if ( n < 3 ) + { + Arrays.fill( imgDimensions, n, 3, 1 ); + } + final int[] imgCellDimensions = Arrays.copyOf( cellDimensions, 3 ); + if ( n < 3 ) + { + Arrays.fill( imgCellDimensions, n, 3, 1 ); + } + final CellGrid imgGrid = new CellGrid( imgDimensions, imgCellDimensions ); + return cache.createImg( imgGrid, timepointId, setupId, level, cacheHints, loader, cacheArrayLoader.getEmptyArrayCreator(), + type ); + } + catch ( final IOException | N5Exception e ) + { + System.err.println( String.format( + "image data for timepoint %d setup %d level %d could not be found.", + timepointId, setupId, level ) ); + return Views.interval( + new ConstantRandomAccessible<>( type.createVariable(), 3 ), + new FinalInterval( 1, 1, 1 ) ); + } + } + } + + private String getFullPathName( final String pathName ) + { + String fullPathName = dataset + pathName; + while ( fullPathName.contains( "//" ) ) + { + fullPathName = fullPathName.replace( "//", "/" ); + } + return fullPathName; + } +} diff --git a/src/main/java/org/mastodon/mamut/io/loader/XmlIoN5UniverseImgLoader.java b/src/main/java/org/mastodon/mamut/io/loader/XmlIoN5UniverseImgLoader.java new file mode 100644 index 000000000..50c04b0c8 --- /dev/null +++ b/src/main/java/org/mastodon/mamut/io/loader/XmlIoN5UniverseImgLoader.java @@ -0,0 +1,203 @@ +/*- + * #%L + * Mastodon + * %% + * Copyright (C) 2014 - 2024 Mastodon 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% + */ +/* + * Original code from bigdataviewer-core: + * https://github.com/bigdataviewer/bigdataviewer-core/blob/c47e2370ae9b9e281444f127cb36cd9ef1497336/src/main/java/bdv/img/n5/N5ImageLoader.java + * LICENSE is shown below: + * + * #%L + * BigDataViewer core classes with minimal dependencies. + * %% + * Copyright (C) 2012 - 2023 BigDataViewer 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 org.mastodon.mamut.io.loader; + +import java.io.File; +import java.net.URI; +import java.net.URISyntaxException; +import org.janelia.saalfeldlab.n5.N5URI; +import org.jdom2.Element; +import org.mastodon.mamut.io.loader.util.credentials.AWSCredentialsManager; +import org.mastodon.mamut.io.loader.util.credentials.AWSCredentialsTools; + +import mpicbg.spim.data.XmlHelpers; +import mpicbg.spim.data.generic.sequence.AbstractSequenceDescription; +import mpicbg.spim.data.generic.sequence.ImgLoaderIo; +import mpicbg.spim.data.generic.sequence.XmlIoBasicImgLoader; + +import static mpicbg.spim.data.XmlKeys.IMGLOADER_FORMAT_ATTRIBUTE_NAME; + +@ImgLoaderIo( format = "bdv.n5.universe", type = N5UniverseImgLoader.class ) +public class XmlIoN5UniverseImgLoader implements XmlIoBasicImgLoader< N5UniverseImgLoader > +{ + public static final String URL = "Url"; + + public static final String DATASET = "Dataset"; + + @Override + public Element toXml( N5UniverseImgLoader imgLoader, File basePath ) + { + final Element elem = new Element( "ImageLoader" ); + elem.setAttribute( IMGLOADER_FORMAT_ATTRIBUTE_NAME, "bdv.n5.universe" ); + elem.setAttribute( "version", "1.0" ); + String url = imgLoader.getUrl(); + String scheme = getScheme( url ); + if ( scheme == null ) + { + // url is a path + elem.addContent( XmlHelpers.pathElement( URL, new File( url ), basePath ) ); + } + else if ( scheme.equals( "file" ) ) + { + // remove "file:" prefix + elem.addContent( XmlHelpers.pathElement( URL, new File( url.substring( "file:".length() ) ), basePath ) ); + } + else + { + elem.addContent( XmlHelpers.textElement( URL, imgLoader.getUrl() ) ); + } + elem.addContent( XmlHelpers.textElement( DATASET, imgLoader.getDataset() ) ); + return elem; + } + + @Override + public N5UniverseImgLoader fromXml( Element elem, File basePath, AbstractSequenceDescription< ?, ?, ? > sequenceDescription ) + { + String url = XmlHelpers.getText( elem, URL ); + if ( getScheme( url ) == null ) + { + url = XmlHelpers.loadPath( elem, URL, basePath ).toString(); + } + final String dataset = XmlHelpers.getText( elem, DATASET ); + try + { + N5UniverseImgLoader imgLoader; + if ( AWSCredentialsManager.getInstance().getCredentials() != null ) + { + try + { + // use stored BasicAWSCredentials + imgLoader = new N5UniverseImgLoader( url, dataset, sequenceDescription, + AWSCredentialsManager.getInstance().getCredentials() ); + if ( imgLoader.validate() ) + { + return imgLoader; + } + } + catch ( final Throwable e ) + {} + AWSCredentialsManager.getInstance().setCredentials( null ); + } + // try anonymous access + imgLoader = new N5UniverseImgLoader( url, dataset, sequenceDescription ); + if ( imgLoader.validate() ) + { + return imgLoader; + } + // use DefaultAWSCredentialsProviderChain + imgLoader = new N5UniverseImgLoader( url, dataset, sequenceDescription, null ); + if ( imgLoader.validate() ) + { + return imgLoader; + } + else + { + // use BasicAWSCredentials + if ( AWSCredentialsManager.getInstance().getCredentials() == null ) + AWSCredentialsManager.getInstance().setCredentials( AWSCredentialsTools.getBasicAWSCredentials() ); + if ( AWSCredentialsManager.getInstance().getCredentials() == null ) + throw new RuntimeException( "No credentials provided" ); + imgLoader = + new N5UniverseImgLoader( url, dataset, sequenceDescription, AWSCredentialsManager.getInstance().getCredentials() ); + if ( imgLoader.validate() ) + { + return imgLoader; + } + else + { + // for some reason, credentials are not valid + AWSCredentialsManager.getInstance().setCredentials( AWSCredentialsTools.getBasicAWSCredentials() ); + if ( AWSCredentialsManager.getInstance().getCredentials() == null ) + throw new RuntimeException( "No credentials provided" ); + imgLoader = new N5UniverseImgLoader( url, dataset, sequenceDescription, + AWSCredentialsManager.getInstance().getCredentials() ); + if ( imgLoader.validate() ) + { + return imgLoader; + } + else + { + throw new RuntimeException( "Could not create N5UniverseImgLoader with credentials" ); + } + } + } + } + catch ( final Throwable e ) + { + AWSCredentialsManager.getInstance().setCredentials( null ); + throw new RuntimeException( e ); + } + } + + private static String getScheme( final String url ) + { + String scheme = null; + try + { + final URI encodedUri = N5URI.encodeAsUri( url ); + scheme = encodedUri.getScheme(); + } + catch ( URISyntaxException ignored ) + {} + return scheme; + } + +} diff --git a/src/main/java/org/mastodon/mamut/io/loader/adapter/N5HDF5ReaderToViewerImgLoaderAdapter.java b/src/main/java/org/mastodon/mamut/io/loader/adapter/N5HDF5ReaderToViewerImgLoaderAdapter.java new file mode 100644 index 000000000..91dadd276 --- /dev/null +++ b/src/main/java/org/mastodon/mamut/io/loader/adapter/N5HDF5ReaderToViewerImgLoaderAdapter.java @@ -0,0 +1,165 @@ +/*- + * #%L + * Mastodon + * %% + * Copyright (C) 2014 - 2024 Mastodon 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 org.mastodon.mamut.io.loader.adapter; + +import org.janelia.saalfeldlab.n5.hdf5.N5HDF5Reader; +import org.janelia.saalfeldlab.n5.universe.metadata.SpatialMultiscaleMetadata; +import org.mastodon.mamut.io.img.cache.MastodonSimpleCacheArrayLoader; +import org.mastodon.mamut.io.loader.util.mobie.N5CacheArrayLoader; + +import bdv.img.n5.BdvN5Format; +import bdv.img.n5.DataTypeProperties; + +import java.io.IOException; + +import org.janelia.saalfeldlab.n5.DataType; +import org.janelia.saalfeldlab.n5.DatasetAttributes; +import org.janelia.saalfeldlab.n5.N5Exception; +import org.janelia.saalfeldlab.n5.N5Exception.N5IOException; +import ch.systemsx.cisd.hdf5.HDF5Factory; +import ch.systemsx.cisd.hdf5.IHDF5Reader; +import hdf.hdf5lib.exceptions.HDF5Exception; +import net.imglib2.img.cell.CellGrid; + +public class N5HDF5ReaderToViewerImgLoaderAdapter implements N5ReaderToViewerImgLoaderAdapter< N5HDF5Reader > +{ + + private final N5HDF5Reader n5; + + private final String dataset; + + private static IHDF5Reader openHdf5Reader( String hdf5Path ) + { + try + { + return HDF5Factory.openForReading( hdf5Path ); + } + catch ( HDF5Exception e ) + { + throw new N5IOException( "Cannot open HDF5 Reader", new IOException( e ) ); + } + } + + public N5HDF5ReaderToViewerImgLoaderAdapter( final N5HDF5Reader n5, final String dataset ) + { + this.n5 = n5; + this.dataset = dataset; + } + + @Override + public String getDataset() + { + return dataset; + } + + @Override + public N5HDF5Reader getN5Reader() + { + return n5; + } + + @Override + public DataType getSetupDataType( int setupId ) throws IOException + { + DataType dataType = null; + try + { + final String pathName = getFullPathName( getPathNameFromSetup( setupId ) ); + dataType = n5.getAttribute( pathName, BdvN5Format.DATA_TYPE_KEY, DataType.class ); + } + catch ( final N5Exception e ) + { + dataType = null; + } + return dataType == null ? DataType.UINT16 : dataType; + } + + @Override + public double[][] getMipmapResolutions( int setupId ) throws IOException + { + final String pathName = getFullPathName( getPathNameFromSetup( setupId ) ) + "/resolutions"; + final IHDF5Reader reader = openHdf5Reader( ( ( N5HDF5Reader ) n5 ).getFilename().toString() ); + final double[][] mipmapResolutions = reader.readDoubleMatrix( pathName ); + return mipmapResolutions; + } + + @Override + public long[] getDimensions( int setupId, int timepointId, int level ) + { + final String pathName = getFullPathName( getPathNameFromSetupTimepointLevel( setupId, timepointId, level ) ); + final DatasetAttributes attributes = n5.getDatasetAttributes( pathName ); + return attributes.getDimensions(); + } + + @Override + public int[] getCellDimensions( int setupId, int timepointId, int level ) + { + final String pathName = getFullPathName( getPathNameFromSetupTimepointLevel( setupId, timepointId, level ) ); + final DatasetAttributes attributes = n5.getDatasetAttributes( pathName ); + return attributes.getBlockSize(); + } + + @Override + public MastodonSimpleCacheArrayLoader< ? > createCacheArrayLoader( int setupId, int timepointId, int level, CellGrid grid ) + throws IOException + { + final String pathName = getFullPathName( getPathNameFromSetupTimepointLevel( setupId, timepointId, level ) ); + final DatasetAttributes attributes; + try + { + attributes = n5.getDatasetAttributes( pathName ); + } + catch ( final N5Exception e ) + { + throw new IOException( e ); + } + return new N5CacheArrayLoader<>( n5, pathName, attributes, DataTypeProperties.of( attributes.getDataType() ) ); + } + + public String getPathNameFromSetup( int setupId ) + { + return String.format( "s%02d", setupId ); + } + + public String getPathNameFromSetupTimepoint( int setupId, int timepointId ) + { + return String.format( "t%05d/s%02d", timepointId, setupId ); + } + + public String getPathNameFromSetupTimepointLevel( int setupId, int timepointId, int level ) + { + return String.format( "t%05d/s%02d/%d/cells", timepointId, setupId, level ); + } + + @Override + public SpatialMultiscaleMetadata< ? > getMetadata() + { + throw new UnsupportedOperationException( "Not supported" ); + } +} diff --git a/src/main/java/org/mastodon/mamut/io/loader/adapter/N5KeyValueReaderToViewerImgLoaderAdapter.java b/src/main/java/org/mastodon/mamut/io/loader/adapter/N5KeyValueReaderToViewerImgLoaderAdapter.java new file mode 100644 index 000000000..cca04bd9a --- /dev/null +++ b/src/main/java/org/mastodon/mamut/io/loader/adapter/N5KeyValueReaderToViewerImgLoaderAdapter.java @@ -0,0 +1,211 @@ +/*- + * #%L + * Mastodon + * %% + * Copyright (C) 2014 - 2024 Mastodon 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 org.mastodon.mamut.io.loader.adapter; + +import java.io.IOException; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.Executors; +import java.util.stream.Collectors; + +import org.janelia.saalfeldlab.n5.DataType; +import org.janelia.saalfeldlab.n5.DatasetAttributes; +import org.janelia.saalfeldlab.n5.N5Exception; +import org.janelia.saalfeldlab.n5.N5KeyValueReader; +import org.janelia.saalfeldlab.n5.N5Reader; +import org.janelia.saalfeldlab.n5.bdv.N5ViewerCreator; +import org.janelia.saalfeldlab.n5.metadata.N5ViewerMultichannelMetadata; +import org.janelia.saalfeldlab.n5.metadata.imagej.N5ImagePlusMetadata; +import org.janelia.saalfeldlab.n5.universe.N5DatasetDiscoverer; +import org.janelia.saalfeldlab.n5.universe.N5TreeNode; +import org.janelia.saalfeldlab.n5.universe.metadata.MultiscaleMetadata; +import org.janelia.saalfeldlab.n5.universe.metadata.N5CosemMetadata; +import org.janelia.saalfeldlab.n5.universe.metadata.N5Metadata; +import org.mastodon.mamut.io.loader.util.mobie.N5CacheArrayLoader; + +import bdv.img.cache.SimpleCacheArrayLoader; +import bdv.img.n5.DataTypeProperties; +import net.imglib2.img.cell.CellGrid; + +public class N5KeyValueReaderToViewerImgLoaderAdapter implements N5ReaderToViewerImgLoaderAdapter< N5KeyValueReader > +{ + + private final N5KeyValueReader n5; + + private final String dataset; + + private final N5ViewerMultichannelMetadata metadata; + + private final Map< Integer, List< DatasetAttributes > > setupToAttributesList = new HashMap<>(); + + public N5KeyValueReaderToViewerImgLoaderAdapter( final N5KeyValueReader n5, final String dataset ) + { + this.n5 = n5; + this.dataset = dataset; + this.metadata = getMetadata( n5 ); + for ( int i = 0; i < metadata.getChildrenMetadata().length; i++ ) + { + MultiscaleMetadata< ? > setupMetadata = metadata.getChildrenMetadata()[ i ]; + final List< DatasetAttributes > attributesList = Arrays.stream( setupMetadata.getChildrenMetadata() ) + .map( levelMetadata -> levelMetadata.getAttributes() ) + .collect( Collectors.toList() ); + setupToAttributesList.put( i, attributesList ); + } + } + + @Override + public String getDataset() + { + return dataset; + } + + @Override + public N5KeyValueReader getN5Reader() + { + return n5; + } + + @Override + public DataType getSetupDataType( int setupId ) throws IOException + { + return setupToAttributesList.get( setupId ).get( 0 ).getDataType(); + } + + @Override + public double[][] getMipmapResolutions( int setupId ) throws IOException + { + final List< DatasetAttributes > attributesList = setupToAttributesList.get( setupId ); + if ( attributesList == null || attributesList.isEmpty() ) + return null; + double[][] mipmapResolutions = new double[ attributesList.size() ][]; + long[] dimensionsOfLevel0 = attributesList.get( 0 ).getDimensions(); + for ( int level = 0; level < attributesList.size(); level++ ) + { + long[] dimensions = attributesList.get( level ).getDimensions(); + mipmapResolutions[ level ] = new double[ 3 ]; + for ( int d = 0; d < 2; d++ ) + { + mipmapResolutions[ level ][ d ] = Math.round( 1.0 * dimensionsOfLevel0[ d ] / dimensions[ d ] ); + } + mipmapResolutions[ level ][ 2 ] = + attributesList.get( level ).getNumDimensions() == 3 ? Math.round( 1.0 * dimensionsOfLevel0[ 2 ] / dimensions[ 2 ] ) + : 1.0; + } + return mipmapResolutions; + } + + @Override + public long[] getDimensions( int setupId, int timepointId, int level ) + { + final DatasetAttributes attributes = setupToAttributesList.get( setupId ).get( level ); + return attributes.getDimensions(); + } + + @Override + public int[] getCellDimensions( int setupId, int timepointId, int level ) + { + final DatasetAttributes attributes = setupToAttributesList.get( setupId ).get( level ); + return attributes.getBlockSize(); + } + + @Override + public String getPathNameFromSetupTimepointLevel( int setupId, int timepointId, int level ) + { + return String.format( "c%d/s%d", setupId, level ); + } + + @Override + public SimpleCacheArrayLoader< ? > createCacheArrayLoader( int setupId, int timepointId, int level, CellGrid grid ) + throws IOException + { + String pathName = getFullPathName( String.format( "c%d/s%d", setupId, level ) ); + DatasetAttributes attributes = setupToAttributesList.get( setupId ).get( level ); + return new N5CacheArrayLoader<>( n5, pathName, attributes, DataTypeProperties.of( attributes.getDataType() ) ); + } + + private static boolean isSupportedMetadata( final N5Metadata meta ) + { + if ( meta instanceof N5ImagePlusMetadata || + meta instanceof N5CosemMetadata || + meta instanceof N5ViewerMultichannelMetadata ) + return true; + return false; + } + + private static N5Metadata getMetadataRecursively( final N5TreeNode node ) + { + N5Metadata meta = node.getMetadata(); + if ( isSupportedMetadata( meta ) ) + { + return meta; + } + for ( final N5TreeNode child : node.childrenList() ) + { + meta = getMetadataRecursively( child ); + if ( meta != null ) + { + return meta; + } + } + return null; + } + + private static N5ViewerMultichannelMetadata getMetadata( final N5Reader n5 ) + { + N5TreeNode node = null; + final N5DatasetDiscoverer discoverer = new N5DatasetDiscoverer( + n5, + Executors.newCachedThreadPool(), + Arrays.asList( N5ViewerCreator.n5vParsers ), + Arrays.asList( N5ViewerCreator.n5vGroupParsers ) + ); + try + { + node = discoverer.discoverAndParseRecursive( "" ); + } + catch ( final IOException e ) + {} + if ( node == null ) + return null; + N5Metadata meta = getMetadataRecursively( node ); + if ( isSupportedMetadata( meta ) ) + return ( N5ViewerMultichannelMetadata ) meta; + else + throw new N5Exception( "No N5ViewerMultichannelMetadata found" ); + } + + @Override + public N5ViewerMultichannelMetadata getMetadata() + { + return metadata; + } + +} diff --git a/src/main/java/org/mastodon/mamut/io/loader/adapter/N5ReaderToViewerImgLoaderAdapter.java b/src/main/java/org/mastodon/mamut/io/loader/adapter/N5ReaderToViewerImgLoaderAdapter.java new file mode 100644 index 000000000..150befeab --- /dev/null +++ b/src/main/java/org/mastodon/mamut/io/loader/adapter/N5ReaderToViewerImgLoaderAdapter.java @@ -0,0 +1,83 @@ +/*- + * #%L + * Mastodon + * %% + * Copyright (C) 2014 - 2024 Mastodon 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 org.mastodon.mamut.io.loader.adapter; + +import java.io.IOException; +import org.janelia.saalfeldlab.n5.DataType; +import org.janelia.saalfeldlab.n5.N5Reader; +import org.janelia.saalfeldlab.n5.universe.metadata.N5Metadata; +import bdv.img.cache.SimpleCacheArrayLoader; +import net.imglib2.img.cell.CellGrid; + +public interface N5ReaderToViewerImgLoaderAdapter< T extends N5Reader > +{ + + String getDataset(); + + T getN5Reader(); + + DataType getSetupDataType( final int setupId ) throws IOException; + + double[][] getMipmapResolutions( final int setupId ) throws IOException; + + long[] getDimensions( int setupId, int timepointId, int level ); + + int[] getCellDimensions( int setupId, int timepointId, int level ); + + String getPathNameFromSetupTimepointLevel( int setupId, int timepointId, int level ); + + SimpleCacheArrayLoader< ? > createCacheArrayLoader( int setupId, int timepointId, int level, CellGrid grid ) + throws IOException; + + N5Metadata getMetadata(); + + default int getNumSetups() + { + int numSetups = 0; + while ( true ) + { + try + { + if ( getMipmapResolutions( numSetups ) == null ) + break; + } + catch ( IOException e ) + { + break; + } + numSetups++; + } + return numSetups; + } + + default String getFullPathName( final String pathName ) + { + return ( getDataset() + pathName ).replaceAll( "/+", "/" ); + } +} diff --git a/src/main/java/org/mastodon/mamut/io/loader/adapter/ZarrKeyValueReaderToViewerImgLoaderAdapter.java b/src/main/java/org/mastodon/mamut/io/loader/adapter/ZarrKeyValueReaderToViewerImgLoaderAdapter.java new file mode 100644 index 000000000..3472becb0 --- /dev/null +++ b/src/main/java/org/mastodon/mamut/io/loader/adapter/ZarrKeyValueReaderToViewerImgLoaderAdapter.java @@ -0,0 +1,308 @@ +/*- + * #%L + * Mastodon + * %% + * Copyright (C) 2014 - 2024 Mastodon 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 org.mastodon.mamut.io.loader.adapter; + +import static org.mastodon.mamut.io.loader.util.mobie.OmeZarrMultiscales.MULTI_SCALE_KEY; + +import java.io.IOException; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.Executors; + +import org.janelia.saalfeldlab.n5.DataType; +import org.janelia.saalfeldlab.n5.DatasetAttributes; +import org.janelia.saalfeldlab.n5.N5Exception; +import org.janelia.saalfeldlab.n5.universe.N5DatasetDiscoverer; +import org.janelia.saalfeldlab.n5.universe.N5TreeNode; +import org.janelia.saalfeldlab.n5.universe.metadata.N5GenericSingleScaleMetadataParser; +import org.janelia.saalfeldlab.n5.universe.metadata.N5Metadata; +import org.janelia.saalfeldlab.n5.universe.metadata.ome.ngff.v04.OmeNgffMetadata; +import org.janelia.saalfeldlab.n5.universe.metadata.ome.ngff.v04.OmeNgffMetadataParser; +import org.janelia.saalfeldlab.n5.zarr.ZarrKeyValueReader; +import org.mastodon.mamut.io.loader.util.mobie.N5OMEZarrCacheArrayLoader; +import org.mastodon.mamut.io.loader.util.mobie.OmeZarrMultiscales; +import org.mastodon.mamut.io.loader.util.mobie.ZarrAxes; + +import bdv.img.cache.SimpleCacheArrayLoader; +import bdv.img.n5.BdvN5Format; +import net.imglib2.Dimensions; +import net.imglib2.FinalDimensions; +import net.imglib2.img.cell.CellGrid; + +public class ZarrKeyValueReaderToViewerImgLoaderAdapter implements N5ReaderToViewerImgLoaderAdapter< ZarrKeyValueReader > +{ + + private final ZarrKeyValueReader n5; + + private final String dataset; + + // Used for ZarrKeyValueReader + private final Map< Integer, String > setupToPathname = new HashMap<>(); + + private final Map< Integer, OmeZarrMultiscales > setupToMultiscale = new HashMap<>(); + + private final Map< Integer, DatasetAttributes > setupToAttributes = new HashMap<>(); + + private final Map< Integer, Integer > setupToChannel = new HashMap<>(); + + /** + * https://github.com/mobie/mobie-io/blob/0216a25b2fa6f4e3fd7f9f6976de278b8eaa1b76/src/main/java/org/embl/mobie/io/ome/zarr/loaders/N5OMEZarrImageLoader.java#L422 + * @param setupId + * @param attributes + * @return + */ + private Dimensions getSpatialDimensions( int setupId, DatasetAttributes attributes ) + { + final long[] spatialDimensions = new long[ 3 ]; + long[] attributeDimensions = attributes.getDimensions(); + Arrays.fill( spatialDimensions, 1 ); + ZarrAxes zarrAxes = setupToMultiscale.get( setupId ).axes; + final Map< Integer, Integer > spatialToZarr = zarrAxes.spatialToZarr(); + for ( Map.Entry< Integer, Integer > entry : spatialToZarr.entrySet() ) + { + spatialDimensions[ entry.getKey() ] = attributeDimensions[ entry.getValue() ]; + } + return new FinalDimensions( spatialDimensions ); + } + + /** + * https://github.com/mobie/mobie-io/blob/0216a25b2fa6f4e3fd7f9f6976de278b8eaa1b76/src/main/java/org/embl/mobie/io/ome/zarr/loaders/N5OMEZarrImageLoader.java#L529 + * @param attributes + * @return + */ + private int[] fillBlockSize( DatasetAttributes attributes ) + { + int[] tmp = new int[ 3 ]; + tmp[ 0 ] = Arrays.stream( attributes.getBlockSize() ).toArray()[ 0 ]; + tmp[ 1 ] = Arrays.stream( attributes.getBlockSize() ).toArray()[ 1 ]; + tmp[ 2 ] = 1; + return tmp; + } + + /** + * https://github.com/mobie/mobie-io/blob/0216a25b2fa6f4e3fd7f9f6976de278b8eaa1b76/src/main/java/org/embl/mobie/io/ome/zarr/loaders/N5OMEZarrImageLoader.java#L520 + * @param setupId + * @param attributes + * @return + */ + private int[] getBlockSize( int setupId, DatasetAttributes attributes ) + { + ZarrAxes zarrAxes = setupToMultiscale.get( setupId ).axes; + if ( !zarrAxes.hasZAxis() ) + { + return fillBlockSize( attributes ); + } + else + { + return Arrays.stream( attributes.getBlockSize() ).limit( 3 ).toArray(); + } + } + + /** + * @param setupId + * @param level + * @return + */ + private String getPathNameLevel( int setupId, int level ) + { + return setupToPathname.get( setupId ) + "/" + setupToMultiscale.get( setupId ).datasets[ level ].path; + } + + public ZarrKeyValueReaderToViewerImgLoaderAdapter( final ZarrKeyValueReader n5, final String dataset ) + { + this.n5 = n5; + this.dataset = dataset; + int setupId = -1; + final OmeZarrMultiscales[] multiscales = n5.getAttribute( dataset, MULTI_SCALE_KEY, OmeZarrMultiscales[].class ); + for ( OmeZarrMultiscales multiscale : multiscales ) + { + final DatasetAttributes attributes = n5.getDatasetAttributes( dataset + multiscale.datasets[ 0 ].path ); + long nC = 1; + if ( multiscale.axes.hasChannels() ) + { + nC = attributes.getDimensions()[ multiscale.axes.channelIndex() ]; + } + + for ( int c = 0; c < nC; c++ ) + { + // each channel is one setup + setupId++; + setupToChannel.put( setupId, c ); + + // all channels have the same multiscale and attributes + setupToMultiscale.put( setupId, multiscale ); + setupToAttributes.put( setupId, attributes ); + setupToPathname.put( setupId, "" ); + } + } + } + + @Override + public String getDataset() + { + return dataset; + } + + @Override + public ZarrKeyValueReader getN5Reader() + { + return n5; + } + + @Override + public DataType getSetupDataType( int setupId ) throws IOException + { + DataType dataType = null; + try + { + final String pathName = getFullPathName( setupToPathname.get( setupId ) ); + dataType = n5.getAttribute( pathName, BdvN5Format.DATA_TYPE_KEY, DataType.class ); + } + catch ( final N5Exception e ) + { + throw new IOException( e ); + } + return dataType == null ? DataType.UINT16 : dataType; + } + + @Override + public double[][] getMipmapResolutions( int setupId ) throws IOException + { + double[][] mipmapResolutions = null; + try + { + OmeZarrMultiscales multiscale = setupToMultiscale.get( setupId ); + if ( multiscale == null ) + { + throw new IOException( "Multiscale not found for setup " + setupId ); + } + mipmapResolutions = new double[ multiscale.datasets.length ][]; + + long[] dimensionsOfLevel0 = setupToAttributes.get( setupId ).getDimensions(); + mipmapResolutions[ 0 ] = new double[] { 1.0, 1.0, 1.0 }; + + for ( int level = 1; level < mipmapResolutions.length; level++ ) + { + long[] dimensions = n5.getDatasetAttributes( getFullPathName( getPathNameLevel( setupId, level ) ) ).getDimensions(); + mipmapResolutions[ level ] = new double[ 3 ]; + for ( int d = 0; d < 2; d++ ) + { + mipmapResolutions[ level ][ d ] = Math.round( 1.0 * dimensionsOfLevel0[ d ] / dimensions[ d ] ); + } + mipmapResolutions[ level ][ 2 ] = + multiscale.axes.hasZAxis() ? Math.round( 1.0 * dimensionsOfLevel0[ 2 ] / dimensions[ 2 ] ) : 1.0; + } + } + catch ( final N5Exception e ) + { + throw new IOException( e ); + } + return mipmapResolutions; + } + + private DatasetAttributes getDatasetAttributes( int setupId, int level ) throws N5Exception + { + return n5.getDatasetAttributes( getFullPathName( getPathNameLevel( setupId, level ) ) ); + } + + @Override + public long[] getDimensions( int setupId, int timepointId, int level ) + { + final DatasetAttributes attributes = getDatasetAttributes( setupId, level ); + return getSpatialDimensions( setupId, attributes ).dimensionsAsLongArray(); + } + + @Override + public int[] getCellDimensions( int setupId, int timepointId, int level ) + { + final DatasetAttributes attributes = getDatasetAttributes( setupId, level ); + return getBlockSize( setupId, attributes ); + } + + @Override + public SimpleCacheArrayLoader< ? > createCacheArrayLoader( int setupId, int timepointId, int level, CellGrid grid ) + throws IOException + { + final DatasetAttributes attributes; + try + { + attributes = getDatasetAttributes( setupId, level ); + } + catch ( final N5Exception e ) + { + throw new IOException( e ); + } + final String pathName = getFullPathName( getPathNameLevel( setupId, level ) ); + return new N5OMEZarrCacheArrayLoader<>( n5, pathName, setupToChannel.get( setupId ), timepointId, attributes, grid, + setupToMultiscale.get( setupId ).axes ); + } + + @Override + public String getPathNameFromSetupTimepointLevel( int setupId, int timepointId, int level ) + { + return getPathNameLevel( setupId, level ); + } + + @Override + public OmeNgffMetadata getMetadata() + { + final N5DatasetDiscoverer discoverer = new N5DatasetDiscoverer( n5, + Executors.newCachedThreadPool(), + Collections.singletonList( new N5GenericSingleScaleMetadataParser() ), + Collections.singletonList( new OmeNgffMetadataParser() ) ); + N5TreeNode root = null; + try + { + root = discoverer.discoverAndParseRecursive( "" ); + } + catch ( IOException e ) + { + e.printStackTrace(); + } + + final N5TreeNode metaNode = root.getDescendant( dataset ) + .filter( node -> { + return node.getMetadata() != null; + } ) + .get(); + if ( metaNode == null ) + { + return null; + } + final N5Metadata meta = metaNode.getMetadata(); + if ( meta instanceof OmeNgffMetadata ) + { + return ( OmeNgffMetadata ) meta; + } + return null; + } + +} diff --git a/src/main/java/org/mastodon/mamut/io/loader/util/credentials/AWSCredentialsManager.java b/src/main/java/org/mastodon/mamut/io/loader/util/credentials/AWSCredentialsManager.java new file mode 100644 index 000000000..3ae1974c6 --- /dev/null +++ b/src/main/java/org/mastodon/mamut/io/loader/util/credentials/AWSCredentialsManager.java @@ -0,0 +1,54 @@ +/*- + * #%L + * Mastodon + * %% + * Copyright (C) 2014 - 2024 Mastodon 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 org.mastodon.mamut.io.loader.util.credentials; + +import com.amazonaws.auth.AWSCredentials; + +public enum AWSCredentialsManager +{ + INSTANCE; + + private AWSCredentials credentials; + + public static AWSCredentialsManager getInstance() + { + return INSTANCE; + } + + public void setCredentials( final AWSCredentials credentials ) + { + this.credentials = credentials; + } + + public AWSCredentials getCredentials() + { + return credentials; + } + +} diff --git a/src/main/java/org/mastodon/mamut/io/loader/util/credentials/AWSCredentialsTools.java b/src/main/java/org/mastodon/mamut/io/loader/util/credentials/AWSCredentialsTools.java new file mode 100644 index 000000000..866e96bd5 --- /dev/null +++ b/src/main/java/org/mastodon/mamut/io/loader/util/credentials/AWSCredentialsTools.java @@ -0,0 +1,70 @@ +/*- + * #%L + * Mastodon + * %% + * Copyright (C) 2014 - 2024 Mastodon 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 org.mastodon.mamut.io.loader.util.credentials; + +import java.util.Arrays; + +import javax.swing.JLabel; +import javax.swing.JOptionPane; +import javax.swing.JPasswordField; +import javax.swing.JTextField; + +import com.amazonaws.auth.BasicAWSCredentials; + +public class AWSCredentialsTools +{ + public static BasicAWSCredentials getBasicAWSCredentials() + { + final JLabel lblUsername = new JLabel( "Username" ); + final JTextField textFieldUsername = new JTextField(); + final JLabel lblPassword = new JLabel( "Password" ); + final JPasswordField passwordField = new JPasswordField(); + final Object[] ob = { lblUsername, textFieldUsername, lblPassword, passwordField }; + final int result = JOptionPane.showConfirmDialog( null, ob, "Please input credentials", JOptionPane.OK_CANCEL_OPTION ); + + if ( result == JOptionPane.OK_OPTION ) + { + final String username = textFieldUsername.getText(); + final char[] password = passwordField.getPassword(); + try + { + return new BasicAWSCredentials( username, String.valueOf( password ) ); + } + finally + { + Arrays.fill( password, '0' ); + } + } + else + { + return null; + } + } + +} diff --git a/src/main/java/org/mastodon/mamut/io/loader/util/mobie/ArrayCreator.java b/src/main/java/org/mastodon/mamut/io/loader/util/mobie/ArrayCreator.java new file mode 100644 index 000000000..2e205ac5b --- /dev/null +++ b/src/main/java/org/mastodon/mamut/io/loader/util/mobie/ArrayCreator.java @@ -0,0 +1,162 @@ +/*- + * #%L + * Mastodon + * %% + * Copyright (C) 2014 - 2024 Mastodon 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% + */ +/*- + * #%L + * Readers and writers for image data in MoBIE projects + * %% + * Copyright (C) 2021 - 2023 EMBL + * %% + * 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 org.mastodon.mamut.io.loader.util.mobie; + +import java.util.function.BiConsumer; + +import org.janelia.saalfeldlab.n5.DataBlock; +import org.janelia.saalfeldlab.n5.DataType; +import org.janelia.saalfeldlab.n5.imglib2.N5CellLoader; +import org.jetbrains.annotations.NotNull; + +import net.imglib2.img.array.ArrayImg; +import net.imglib2.img.array.ArrayImgs; +import net.imglib2.img.basictypeaccess.volatiles.array.VolatileByteArray; +import net.imglib2.img.basictypeaccess.volatiles.array.VolatileDoubleArray; +import net.imglib2.img.basictypeaccess.volatiles.array.VolatileFloatArray; +import net.imglib2.img.basictypeaccess.volatiles.array.VolatileIntArray; +import net.imglib2.img.basictypeaccess.volatiles.array.VolatileLongArray; +import net.imglib2.img.basictypeaccess.volatiles.array.VolatileShortArray; +import net.imglib2.img.cell.CellGrid; +import net.imglib2.type.NativeType; +import net.imglib2.util.Cast; + +public abstract class ArrayCreator< A, T extends NativeType< T > > +{ + protected final CellGrid cellGrid; + + protected final DataType dataType; + + protected final BiConsumer< ArrayImg< T, ? >, DataBlock< ? > > copyFromBlock; + + public ArrayCreator( CellGrid cellGrid, DataType dataType ) + { + this.cellGrid = cellGrid; + this.dataType = dataType; + this.copyFromBlock = N5CellLoader.createCopy( dataType ); + } + + @NotNull + public A VolatileDoubleArray( DataBlock< ? > dataBlock, long[] cellDims, int n ) + { + switch ( dataType ) + { + case UINT8: + case INT8: + byte[] bytes = new byte[ n ]; + copyFromBlock.accept( Cast.unchecked( ArrayImgs.bytes( bytes, cellDims ) ), dataBlock ); + return ( A ) new VolatileByteArray( bytes, true ); + case UINT16: + case INT16: + short[] shorts = new short[ n ]; + copyFromBlock.accept( Cast.unchecked( ArrayImgs.shorts( shorts, cellDims ) ), dataBlock ); + return ( A ) new VolatileShortArray( shorts, true ); + case UINT32: + case INT32: + int[] ints = new int[ n ]; + copyFromBlock.accept( Cast.unchecked( ArrayImgs.ints( ints, cellDims ) ), dataBlock ); + return ( A ) new VolatileIntArray( ints, true ); + case UINT64: + case INT64: + long[] longs = new long[ n ]; + copyFromBlock.accept( Cast.unchecked( ArrayImgs.longs( longs, cellDims ) ), dataBlock ); + return ( A ) new VolatileLongArray( longs, true ); + case FLOAT32: + float[] floats = new float[ n ]; + copyFromBlock.accept( Cast.unchecked( ArrayImgs.floats( floats, cellDims ) ), dataBlock ); + return ( A ) new VolatileFloatArray( floats, true ); + case FLOAT64: + double[] doubles = new double[ n ]; + copyFromBlock.accept( Cast.unchecked( ArrayImgs.doubles( doubles, cellDims ) ), dataBlock ); + return ( A ) new VolatileDoubleArray( doubles, true ); + default: + throw new IllegalArgumentException(); + } + } + + public A createEmptyArray( long[] gridPosition ) + { + long[] cellDims = getCellDims( gridPosition ); + int n = ( int ) ( cellDims[ 0 ] * cellDims[ 1 ] * cellDims[ 2 ] ); + switch ( dataType ) + { + case UINT8: + case INT8: + return Cast.unchecked( new VolatileByteArray( new byte[ n ], true ) ); + case UINT16: + case INT16: + return Cast.unchecked( new VolatileShortArray( new short[ n ], true ) ); + case UINT32: + case INT32: + return Cast.unchecked( new VolatileIntArray( new int[ n ], true ) ); + case UINT64: + case INT64: + return Cast.unchecked( new VolatileLongArray( new long[ n ], true ) ); + case FLOAT32: + return Cast.unchecked( new VolatileFloatArray( new float[ n ], true ) ); + case FLOAT64: + return Cast.unchecked( new VolatileDoubleArray( new double[ n ], true ) ); + default: + throw new IllegalArgumentException(); + } + } + + public long[] getCellDims( long[] gridPosition ) + { + return null; + } +} diff --git a/src/main/java/org/mastodon/mamut/io/loader/util/mobie/AxesTypes.java b/src/main/java/org/mastodon/mamut/io/loader/util/mobie/AxesTypes.java new file mode 100644 index 000000000..f1cef5c82 --- /dev/null +++ b/src/main/java/org/mastodon/mamut/io/loader/util/mobie/AxesTypes.java @@ -0,0 +1,108 @@ +/*- + * #%L + * Mastodon + * %% + * Copyright (C) 2014 - 2024 Mastodon 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% + */ +/*- + * #%L + * Readers and writers for image data in MoBIE projects + * %% + * Copyright (C) 2021 - 2023 EMBL + * %% + * 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 org.mastodon.mamut.io.loader.util.mobie; + +public enum AxesTypes +{ + TIME( "time" ), + CHANNEL( "channel" ), + SPACE( "space" ); + + private final String typeName; + + AxesTypes( String typeName ) + { + this.typeName = typeName; + } + + public static boolean contains( String test ) + { + for ( AxesTypes c : AxesTypes.values() ) + { + if ( c.typeName.equals( test ) ) + { + return true; + } + } + return false; + } + + public static AxesTypes getAxisType( String axisString ) + { + if ( axisString.equals( "x" ) || axisString.equals( "y" ) || axisString.equals( "z" ) ) + { + return AxesTypes.SPACE; + } + else if ( axisString.equals( "t" ) ) + { + return AxesTypes.TIME; + } + else if ( axisString.equals( "c" ) ) + { + return AxesTypes.CHANNEL; + } + else + { + return null; + } + } + + public String getTypeName() + { + return typeName; + } +} diff --git a/src/main/java/org/mastodon/mamut/io/loader/util/mobie/N5CacheArrayLoader.java b/src/main/java/org/mastodon/mamut/io/loader/util/mobie/N5CacheArrayLoader.java new file mode 100644 index 000000000..1e543fe03 --- /dev/null +++ b/src/main/java/org/mastodon/mamut/io/loader/util/mobie/N5CacheArrayLoader.java @@ -0,0 +1,219 @@ +/*- + * #%L + * Mastodon + * %% + * Copyright (C) 2014 - 2024 Mastodon 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% + */ +/*- + * #%L + * Readers and writers for image data in MoBIE projects + * %% + * Copyright (C) 2021 - 2023 EMBL + * %% + * 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 org.mastodon.mamut.io.loader.util.mobie; + +import java.io.IOException; +import java.util.Arrays; +import java.util.function.Function; +import java.util.function.IntFunction; + +import org.janelia.saalfeldlab.n5.DataBlock; +import org.janelia.saalfeldlab.n5.DatasetAttributes; +import org.janelia.saalfeldlab.n5.N5Exception; +import org.janelia.saalfeldlab.n5.N5Reader; +import org.mastodon.mamut.io.img.cache.MastodonSimpleCacheArrayLoader; + +import bdv.img.n5.DataTypeProperties; +import net.imglib2.img.basictypeaccess.DataAccess; +import net.imglib2.util.Cast; +import net.imglib2.util.Intervals; + +public class N5CacheArrayLoader< T, A extends DataAccess > implements MastodonSimpleCacheArrayLoader< A > +{ + private final N5Reader n5; + + private final String pathName; + + private final DatasetAttributes attributes; + + private final IntFunction< T > createPrimitiveArray; + + private final Function< T, A > createVolatileArrayAccess; + + public N5CacheArrayLoader( final N5Reader n5, final String pathName, final DatasetAttributes attributes, + final DataTypeProperties< ?, ?, T, A > dataTypeProperties ) + { + this( n5, pathName, attributes, dataTypeProperties.createPrimitiveArray(), dataTypeProperties.createVolatileArrayAccess() ); + } + + public N5CacheArrayLoader( final N5Reader n5, final String pathName, final DatasetAttributes attributes, + final IntFunction< T > createPrimitiveArray, + final Function< T, A > createVolatileArrayAccess ) + { + this.n5 = n5; + this.pathName = pathName; + this.attributes = attributes; + this.createPrimitiveArray = createPrimitiveArray; + this.createVolatileArrayAccess = createVolatileArrayAccess; + } + + @Override + public A loadArray( final long[] gridPosition, final int[] cellDimensions ) throws IOException + { + return loadArrayAtTimepoint( gridPosition, cellDimensions, -1 ); + } + + @Override + public A loadArrayAtTimepoint( final long[] gridPosition, final int[] cellDimensions, int timepoint ) throws IOException + { + final DataBlock< T > dataBlock; + try + { + dataBlock = Cast.unchecked( n5.readBlock( pathName, attributes, gridPosition ) ); + } + catch ( final N5Exception e ) + { + throw new IOException( e ); + } + if ( dataBlock != null && Arrays.equals( dataBlock.getSize(), cellDimensions ) ) + { + return createVolatileArrayAccess.apply( dataBlock.getData() ); + } + else + { + final T data = createPrimitiveArray.apply( ( int ) Intervals.numElements( cellDimensions ) ); + if ( dataBlock != null ) + { + final T src = dataBlock.getData(); + final int[] srcDims = dataBlock.getSize(); + final int[] dstDims = Arrays.copyOf( cellDimensions, srcDims.length ); + if ( cellDimensions.length < dstDims.length ) + { + Arrays.fill( dstDims, cellDimensions.length, dstDims.length, 1 ); + } + final int[] srcPos = new int[ srcDims.length ]; + final int[] dstPos = new int[ srcDims.length ]; + final int[] size = new int[ srcDims.length ]; + if ( srcDims.length == 4 && timepoint >= 0 ) + { + srcPos[ srcDims.length - 1 ] = timepoint % srcDims[ srcDims.length - 1 ]; + } + Arrays.setAll( size, d -> Math.min( srcDims[ d ], dstDims[ d ] ) ); + ndArrayCopy( src, srcDims, srcPos, data, dstDims, dstPos, size ); + } + return createVolatileArrayAccess.apply( data ); + } + } + + /** + * Like `System.arrayCopy()` but for flattened nD arrays. + * + * @param src + * the (flattened) source array. + * @param srcSize + * dimensions of the source array. + * @param srcPos + * starting position in the source array. + * @param dest + * the (flattened destination array. + * @param destSize + * dimensions of the source array. + * @param destPos + * starting position in the destination data. + * @param size + * the number of array elements to be copied. + */ + // TODO: This will be moved to a new imglib2-blk artifact later. Re-use it from there when that happens. + private static < T > void ndArrayCopy( + final T src, final int[] srcSize, final int[] srcPos, + final T dest, final int[] destSize, final int[] destPos, + final int[] size ) + { + final int n = srcSize.length; + int srcStride = 1; + int destStride = 1; + int srcOffset = 0; + int destOffset = 0; + for ( int d = 0; d < n; ++d ) + { + srcOffset += srcStride * srcPos[ d ]; + srcStride *= srcSize[ d ]; + destOffset += destStride * destPos[ d ]; + destStride *= destSize[ d ]; + } + ndArrayCopy( n - 1, src, srcSize, srcOffset, dest, destSize, destOffset, size ); + } + + private static < T > void ndArrayCopy( + final int d, + final T src, final int[] srcSize, final int srcPos, + final T dest, final int[] destSize, final int destPos, + final int[] size ) + { + if ( d == 0 ) + System.arraycopy( src, srcPos, dest, destPos, size[ d ] ); + else + { + int srcStride = 1; + int destStride = 1; + for ( int dd = 0; dd < d; ++dd ) + { + srcStride *= srcSize[ dd ]; + destStride *= destSize[ dd ]; + } + + final int w = size[ d ]; + for ( int x = 0; x < w; ++x ) + { + ndArrayCopy( d - 1, + src, srcSize, srcPos + x * srcStride, + dest, destSize, destPos + x * destStride, + size ); + } + } + } +} diff --git a/src/main/java/org/mastodon/mamut/io/loader/util/mobie/N5OMEZarrCacheArrayLoader.java b/src/main/java/org/mastodon/mamut/io/loader/util/mobie/N5OMEZarrCacheArrayLoader.java new file mode 100644 index 000000000..5a681438c --- /dev/null +++ b/src/main/java/org/mastodon/mamut/io/loader/util/mobie/N5OMEZarrCacheArrayLoader.java @@ -0,0 +1,146 @@ +/*- + * #%L + * Mastodon + * %% + * Copyright (C) 2014 - 2024 Mastodon 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% + */ +/*- + * #%L + * Readers and writers for image data in MoBIE projects + * %% + * Copyright (C) 2021 - 2023 EMBL + * %% + * 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 org.mastodon.mamut.io.loader.util.mobie; + +import java.io.IOException; +import java.util.Map; + +import org.janelia.saalfeldlab.n5.DataBlock; +import org.janelia.saalfeldlab.n5.DataType; +import org.janelia.saalfeldlab.n5.DatasetAttributes; +import org.janelia.saalfeldlab.n5.N5Reader; + +import com.amazonaws.SdkClientException; + +import bdv.img.cache.SimpleCacheArrayLoader; +import net.imglib2.img.basictypeaccess.DataAccess; +import net.imglib2.img.cell.CellGrid; + +public class N5OMEZarrCacheArrayLoader< A extends DataAccess > implements SimpleCacheArrayLoader< A > +{ + private final N5Reader n5; + + private final String pathName; + + private final int channel; + + private final int timepoint; + + private final DatasetAttributes attributes; + + private final ZarrArrayCreator< A, ? > zarrArrayCreator; + + private final ZarrAxes zarrAxes; + + public N5OMEZarrCacheArrayLoader( final N5Reader n5, final String pathName, final int channel, final int timepoint, + final DatasetAttributes attributes, CellGrid grid, ZarrAxes zarrAxes ) + { + this.n5 = n5; + this.pathName = pathName; // includes the level + this.channel = channel; + this.timepoint = timepoint; + this.attributes = attributes; + final DataType dataType = attributes.getDataType(); + this.zarrArrayCreator = new ZarrArrayCreator<>( grid, dataType, zarrAxes ); + this.zarrAxes = zarrAxes; + } + + @Override + public A loadArray( final long[] gridPosition, int[] cellDimensions ) throws IOException + { + DataBlock< ? > block = null; + + long[] dataBlockIndices = toZarrChunkIndices( gridPosition ); + + try + { + block = n5.readBlock( pathName, attributes, dataBlockIndices ); + } + catch ( SdkClientException e ) + { + System.err.println( e.getMessage() ); // this happens sometimes, not sure yet why... + } + + if ( block == null ) + { + return ( A ) zarrArrayCreator.createEmptyArray( gridPosition ); + } + else + { + return zarrArrayCreator.createArray( block, gridPosition ); + } + } + + private long[] toZarrChunkIndices( long[] gridPosition ) + { + + long[] chunkInZarr = new long[ zarrAxes.getNumDimension() ]; + + // fill in the spatial dimensions + final Map< Integer, Integer > spatialToZarr = zarrAxes.spatialToZarr(); + for ( Map.Entry< Integer, Integer > entry : spatialToZarr.entrySet() ) + chunkInZarr[ entry.getValue() ] = gridPosition[ entry.getKey() ]; + + if ( zarrAxes.hasChannels() ) + chunkInZarr[ zarrAxes.channelIndex() ] = channel; + + if ( zarrAxes.hasTimepoints() ) + chunkInZarr[ zarrAxes.timeIndex() ] = timepoint; + + return chunkInZarr; + } +} diff --git a/src/main/java/org/mastodon/mamut/io/loader/util/mobie/OmeZarrMultiscales.java b/src/main/java/org/mastodon/mamut/io/loader/util/mobie/OmeZarrMultiscales.java new file mode 100644 index 000000000..2f8ad29c5 --- /dev/null +++ b/src/main/java/org/mastodon/mamut/io/loader/util/mobie/OmeZarrMultiscales.java @@ -0,0 +1,157 @@ +/*- + * #%L + * Mastodon + * %% + * Copyright (C) 2014 - 2024 Mastodon 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% + */ +/*- + * #%L + * Readers and writers for image data in MoBIE projects + * %% + * Copyright (C) 2021 - 2023 EMBL + * %% + * 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 org.mastodon.mamut.io.loader.util.mobie; + +import java.util.List; + +import mpicbg.spim.data.sequence.VoxelDimensions; + +public class OmeZarrMultiscales +{ + + // key in json for multiscales + public static final String MULTI_SCALE_KEY = "multiscales"; + + public transient ZarrAxes axes; + + public List< ZarrAxis > zarrAxisList; + + public Dataset[] datasets; + + public String name; + + public String type; + + public String version; + + public CoordinateTransformations[] coordinateTransformations; + + public OmeZarrMultiscales() + {} + + public OmeZarrMultiscales( ZarrAxes axes, String name, String type, String version, + VoxelDimensions voxelDimensions, double[][] resolutions, String timeUnit, + double frameInterval ) + { + this.version = version; + this.name = name; + this.type = type; + this.axes = axes; + this.zarrAxisList = axes.toAxesList( voxelDimensions.unit(), timeUnit ); + generateDatasets( voxelDimensions, frameInterval, resolutions ); + } + + private void generateDatasets( VoxelDimensions voxelDimensions, double frameInterval, double[][] resolutions ) + { + + Dataset[] datasets = new Dataset[ resolutions.length ]; + for ( int i = 0; i < resolutions.length; i++ ) + { + Dataset dataset = new Dataset(); + + CoordinateTransformations coordinateTransformations = new CoordinateTransformations(); + coordinateTransformations.scale = getScale( voxelDimensions, frameInterval, resolutions[ i ] ); + coordinateTransformations.type = "scale"; + + dataset.path = "s" + i; + dataset.coordinateTransformations = new CoordinateTransformations[] { coordinateTransformations }; + datasets[ i ] = dataset; + } + this.datasets = datasets; + } + + private double[] getScale( VoxelDimensions voxelDimensions, double frameInterval, double[] xyzScale ) + { + int nDimensions = zarrAxisList.size(); + double[] scale = new double[ nDimensions ]; + if ( axes.timeIndex() != -1 ) + { + scale[ axes.timeIndex() ] = frameInterval; + } + + if ( axes.channelIndex() != -1 ) + { + scale[ axes.channelIndex() ] = 1; + } + + for ( int i = 0; i < 3; i++ ) + { + double dimension = voxelDimensions.dimension( i ) * xyzScale[ i ]; + scale[ nDimensions - ( i + 1 ) ] = dimension; + } + + return scale; + } + + public static class Dataset + { + public String path; + + public CoordinateTransformations[] coordinateTransformations; + } + + public static class CoordinateTransformations + { + public String type; + + public double[] scale; + + public double[] translation; + + public String path; + } +} diff --git a/src/main/java/org/mastodon/mamut/io/loader/util/mobie/OmeZarrMultiscalesAdapter.java b/src/main/java/org/mastodon/mamut/io/loader/util/mobie/OmeZarrMultiscalesAdapter.java new file mode 100644 index 000000000..c41a7c1a0 --- /dev/null +++ b/src/main/java/org/mastodon/mamut/io/loader/util/mobie/OmeZarrMultiscalesAdapter.java @@ -0,0 +1,108 @@ +/*- + * #%L + * Mastodon + * %% + * Copyright (C) 2014 - 2024 Mastodon 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% + */ +/*- + * #%L + * Readers and writers for image data in MoBIE projects + * %% + * Copyright (C) 2021 - 2023 EMBL + * %% + * 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 org.mastodon.mamut.io.loader.util.mobie; + +import java.lang.reflect.Type; +import java.util.List; + +import org.mastodon.mamut.io.loader.util.mobie.OmeZarrMultiscales.Dataset; + +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonParseException; +import com.google.common.reflect.TypeToken; +import com.google.gson.JsonDeserializationContext; +import com.google.gson.JsonDeserializer; +import com.google.gson.JsonSerializationContext; +import com.google.gson.JsonSerializer; + +public class OmeZarrMultiscalesAdapter implements JsonDeserializer< OmeZarrMultiscales >, JsonSerializer< OmeZarrMultiscales > +{ + + @Override + public OmeZarrMultiscales deserialize( JsonElement json, Type typeOfT, JsonDeserializationContext context ) throws JsonParseException + { + JsonObject jsonObject = json.getAsJsonObject(); + Type zarrAxisListType = new TypeToken< List< ZarrAxis > >() + {}.getType(); + List< ZarrAxis > zarrAxisList = context.deserialize( jsonObject.get( "axes" ), zarrAxisListType ); + ZarrAxes axes = context.deserialize( jsonObject.get( "axes" ), ZarrAxes.class ); + Type datasetsType = new TypeToken< Dataset[] >() + {}.getType(); + Dataset[] datasets = context.deserialize( jsonObject.get( "datasets" ), datasetsType ); + String version = jsonObject.get( "version" ).getAsString(); + OmeZarrMultiscales multiscales = new OmeZarrMultiscales(); + multiscales.axes = axes; + multiscales.zarrAxisList = zarrAxisList; + multiscales.datasets = datasets; + multiscales.version = version; + return multiscales; + } + + @Override + public JsonElement serialize( OmeZarrMultiscales src, Type typeOfSrc, JsonSerializationContext context ) + { + JsonObject obj = new JsonObject(); + obj.add( "axes", context.serialize( src.zarrAxisList ) ); + obj.add( "datasets", context.serialize( src.datasets ) ); + obj.add( "name", context.serialize( src.name ) ); + obj.add( "type", context.serialize( src.type ) ); + obj.add( "version", context.serialize( src.version ) ); + obj.add( "coordinateTransformations", context.serialize( src.coordinateTransformations ) ); + return obj; + } +} diff --git a/src/main/java/org/mastodon/mamut/io/loader/util/mobie/UnitTypes.java b/src/main/java/org/mastodon/mamut/io/loader/util/mobie/UnitTypes.java new file mode 100644 index 000000000..24a5e19b0 --- /dev/null +++ b/src/main/java/org/mastodon/mamut/io/loader/util/mobie/UnitTypes.java @@ -0,0 +1,172 @@ +/*- + * #%L + * Mastodon + * %% + * Copyright (C) 2014 - 2024 Mastodon 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% + */ +/*- + * #%L + * Readers and writers for image data in MoBIE projects + * %% + * Copyright (C) 2021 - 2023 EMBL + * %% + * 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 org.mastodon.mamut.io.loader.util.mobie; + +import ucar.units.PrefixDBException; +import ucar.units.SpecificationException; +import ucar.units.Unit; +import ucar.units.UnitDBException; +import ucar.units.UnitFormat; +import ucar.units.UnitFormatManager; +import ucar.units.UnitSystemException; + +public enum UnitTypes +{ + ANGSTROM( "angstrom" ), + ATTOMETER( "attometer" ), + CENTIMETER( "centimeter" ), + DECIMETER( "decimeter" ), + EXAMETER( "exameter" ), + FEMTOMETER( "femtometer" ), + FOOT( "foot" ), + GIGAMETER( "gigameter" ), + HECTOMETER( "hectometer" ), + INCH( "inch" ), + KILOMETER( "kilometer" ), + MEGAMETER( "megameter" ), + METER( "meter" ), + MICROMETER( "micrometer" ), + MILE( "mile" ), + MILLIMETER( "millimeter" ), + NANOMETER( "nanometer" ), + PARSEC( "parsec" ), + PETAMETER( "petameter" ), + PICOMETER( "picometer" ), + TERAMETER( "terameter" ), + YARD( "yard" ), + YOCTOMETER( "yoctometer" ), + YOTTAMETER( "yottameter" ), + ZEPTOMETER( "zeptometer" ), + ZETTAMETER( "zettameter" ), + + ATTOSECOND( "attosecond" ), + CENTISECOND( "centisecond" ), + DAY( "day" ), + DECISECOND( "decisecond" ), + EXASECOND( "exasecond" ), + FEMTOSECOND( "femtosecond" ), + GIGASECOND( "gigasecond" ), + HECTOSECOND( "hectosecond" ), + HOUR( "hour" ), + KILOSECOND( "kilosecond" ), + MEGASECOND( "megasecond" ), + MICROSECOND( "microsecond" ), + MILLISECOND( "millisecond" ), + MINUTE( "minute" ), + NANOSECOND( "nanosecond" ), + PETASECOND( "petasecond" ), + PICOSECOND( "picosecond" ), + SECOND( "second" ), + TERASECOND( "terasecond" ), + YOCTOSECOND( "yoctosecond" ), + YOTTASECOND( "yottasecond" ), + ZEPTOSECOND( "zeptosecond" ), + ZETTASECOND( "zettasecond" ); + + private final String typeName; + + UnitTypes( String typeName ) + { + this.typeName = typeName; + } + + public static boolean contains( String test ) + { + for ( UnitTypes c : UnitTypes.values() ) + { + if ( c.typeName.equals( test ) ) + { + return true; + } + } + return false; + } + + public static UnitTypes convertUnit( String unit ) + { + // Convert the mu symbol into "u". + String unitString = unit.replace( "\u00B5", "u" ); + + try + { + UnitFormat unitFormatter = UnitFormatManager.instance(); + Unit inputUnit = unitFormatter.parse( unitString ); + + for ( UnitTypes unitType : UnitTypes.values() ) + { + Unit zarrUnit = unitFormatter.parse( unitType.typeName ); + if ( zarrUnit.getCanonicalString().equals( inputUnit.getCanonicalString() ) ) + { + System.out.println( "Converted unit: " + unit + " to recommended ome-zarr unit: " + unitType.getTypeName() ); + return unitType; + } + } + } + catch ( SpecificationException | UnitDBException | PrefixDBException | UnitSystemException e ) + { + e.printStackTrace(); + } + + System.out.println( unit + " is not one of the recommended units for ome-zarr" ); + return null; + } + + public String getTypeName() + { + return typeName; + } +} diff --git a/src/main/java/org/mastodon/mamut/io/loader/util/mobie/ZarrArrayCreator.java b/src/main/java/org/mastodon/mamut/io/loader/util/mobie/ZarrArrayCreator.java new file mode 100644 index 000000000..e1f281c76 --- /dev/null +++ b/src/main/java/org/mastodon/mamut/io/loader/util/mobie/ZarrArrayCreator.java @@ -0,0 +1,106 @@ +/*- + * #%L + * Mastodon + * %% + * Copyright (C) 2014 - 2024 Mastodon 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% + */ +/*- + * #%L + * Readers and writers for image data in MoBIE projects + * %% + * Copyright (C) 2021 - 2023 EMBL + * %% + * 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 org.mastodon.mamut.io.loader.util.mobie; + +import java.util.Arrays; + +import org.janelia.saalfeldlab.n5.DataBlock; +import org.janelia.saalfeldlab.n5.DataType; + +import net.imglib2.img.cell.CellGrid; +import net.imglib2.type.NativeType; + +public class ZarrArrayCreator< A, T extends NativeType< T > > extends ArrayCreator< A, T > +{ + private final ZarrAxes zarrAxes; + + public ZarrArrayCreator( CellGrid cellGrid, DataType dataType, ZarrAxes zarrAxes ) + { + super( cellGrid, dataType ); + this.zarrAxes = zarrAxes; + } + + public A createArray( DataBlock< ? > dataBlock, long[] gridPosition ) + { + long[] cellDims = getCellDims( gridPosition ); + int n = ( int ) ( cellDims[ 0 ] * cellDims[ 1 ] * cellDims[ 2 ] ); + + if ( zarrAxes.getNumDimension() == 2 ) + cellDims = Arrays.stream( cellDims ).limit( 2 ).toArray(); + + return ( A ) VolatileDoubleArray( dataBlock, cellDims, n ); + } + + @Override + public long[] getCellDims( long[] gridPosition ) + { + long[] cellMin = new long[ Math.max( zarrAxes.getNumDimension(), 3 ) ]; + int[] cellDims = new int[ Math.max( zarrAxes.getNumDimension(), 3 ) ]; + + if ( zarrAxes.hasChannels() ) + { + cellDims[ zarrAxes.channelIndex() ] = 1; + } + if ( zarrAxes.hasTimepoints() ) + { + cellDims[ zarrAxes.timeIndex() ] = 1; + } + + cellGrid.getCellDimensions( gridPosition, cellMin, cellDims ); + return Arrays.stream( cellDims ).mapToLong( i -> i ).toArray(); // casting to long for creating ArrayImgs.* + } +} diff --git a/src/main/java/org/mastodon/mamut/io/loader/util/mobie/ZarrAxes.java b/src/main/java/org/mastodon/mamut/io/loader/util/mobie/ZarrAxes.java new file mode 100644 index 000000000..27d48e22a --- /dev/null +++ b/src/main/java/org/mastodon/mamut/io/loader/util/mobie/ZarrAxes.java @@ -0,0 +1,214 @@ +/*- + * #%L + * Mastodon + * %% + * Copyright (C) 2014 - 2024 Mastodon 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% + */ +/*- + * #%L + * Readers and writers for image data in MoBIE projects + * %% + * Copyright (C) 2021 - 2023 EMBL + * %% + * 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 org.mastodon.mamut.io.loader.util.mobie; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import java.util.stream.Stream; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonFormat; +import com.google.common.collect.Lists; + +@JsonFormat( shape = JsonFormat.Shape.ARRAY ) +public enum ZarrAxes +{ + YX( "[\"y\",\"x\"]" ), + CYX( "[\"c\",\"y\",\"x\"]" ), + TYX( "[\"t\",\"y\",\"x\"]" ), + ZYX( "[\"z\",\"y\",\"x\"]" ), + CZYX( "[\"c\",\"z\",\"y\",\"x\"]" ), + TZYX( "[\"t\",\"z\",\"y\",\"x\"]" ), + TCYX( "[\"t\",\"c\",\"y\",\"x\"]" ), + TCZYX( "[\"t\",\"c\",\"z\",\"y\",\"x\"]" ); // v0.2 + + private final String axes; + + ZarrAxes( String axes ) + { + this.axes = axes; + } + + @JsonCreator + public static ZarrAxes decode( final String axes ) + { + return Stream.of( ZarrAxes.values() ).filter( targetEnum -> targetEnum.axes.equals( axes ) ).findFirst().orElse( TCZYX ); + } + + public List< String > getAxesList() + { + String pattern = "([a-z])"; + List< String > allMatches = new ArrayList<>(); + Matcher m = Pattern.compile( pattern ) + .matcher( axes ); + while ( m.find() ) + { + allMatches.add( m.group() ); + } + return allMatches; + } + + public List< ZarrAxis > toAxesList( String spaceUnit, String timeUnit ) + { + List< ZarrAxis > zarrAxesList = new ArrayList<>(); + List< String > zarrAxesStrings = getAxesList(); + + String[] units = new String[] { spaceUnit, timeUnit }; + + // convert to valid ome-zarr units, if possible, otherwise just go ahead with + // given unit + for ( int i = 0; i < units.length; i++ ) + { + String unit = units[ i ]; + if ( !UnitTypes.contains( unit ) ) + { + UnitTypes unitType = UnitTypes.convertUnit( unit ); + if ( unitType != null ) + { + units[ i ] = unitType.getTypeName(); + } + } + } + + for ( int i = 0; i < zarrAxesStrings.size(); i++ ) + { + String axisString = zarrAxesStrings.get( i ); + AxesTypes axisType = AxesTypes.getAxisType( axisString ); + + String unit; + if ( axisType == AxesTypes.SPACE ) + { + unit = units[ 0 ]; + } + else if ( axisType == AxesTypes.TIME ) + { + unit = units[ 1 ]; + } + else + { + unit = null; + } + + zarrAxesList.add( new ZarrAxis( i, axisString, axisType.getTypeName(), unit ) ); + } + + return zarrAxesList; + } + + public boolean hasTimepoints() + { + return this.axes.equals( TCYX.axes ) || this.axes.equals( TZYX.axes ) || this.axes.equals( TYX.axes ) + || this.axes.equals( TCZYX.axes ); + } + + public boolean hasChannels() + { + return this.axes.equals( CZYX.axes ) || this.axes.equals( CYX.axes ) || this.axes.equals( TCYX.axes ) + || this.axes.equals( TCZYX.axes ); + } + + // the flag reverseAxes determines whether the index will be given w.r.t. + // reversedAxes=true corresponds to the java/bdv axis convention + // reversedAxes=false corresponds to the zarr axis convention + public int axisIndex( String axisName, boolean reverseAxes ) + { + if ( reverseAxes ) + { + List< String > reverseAxesList = Lists.reverse( getAxesList() ); + return reverseAxesList.indexOf( axisName ); + } + return getAxesList().indexOf( axisName ); + } + + public int timeIndex() + { + return axisIndex( "t", true ); + } + + public int channelIndex() + { + return axisIndex( "c", true ); + } + + // spatial: 0,1,2 (x,y,z) + public Map< Integer, Integer > spatialToZarr() + { + final HashMap< Integer, Integer > map = new HashMap<>(); + map.put( 0, 0 ); + map.put( 1, 1 ); + if ( hasZAxis() ) + { + map.put( 2, 2 ); + } + return map; + } + + public boolean hasZAxis() + { + return this.axes.equals( TCZYX.axes ) || this.axes.equals( CZYX.axes ) || this.axes.equals( TZYX.axes ) + || this.axes.equals( ZYX.axes ); + } + + public int getNumDimension() + { + return getAxesList().size(); + } +} diff --git a/src/main/java/org/mastodon/mamut/io/loader/util/mobie/ZarrAxesAdapter.java b/src/main/java/org/mastodon/mamut/io/loader/util/mobie/ZarrAxesAdapter.java new file mode 100644 index 000000000..cbcfe281f --- /dev/null +++ b/src/main/java/org/mastodon/mamut/io/loader/util/mobie/ZarrAxesAdapter.java @@ -0,0 +1,128 @@ +/*- + * #%L + * Mastodon + * %% + * Copyright (C) 2014 - 2024 Mastodon 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% + */ +/*- + * #%L + * Readers and writers for image data in MoBIE projects + * %% + * Copyright (C) 2021 - 2023 EMBL + * %% + * 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 org.mastodon.mamut.io.loader.util.mobie; + +import java.lang.reflect.Type; +import java.util.List; + +import com.google.gson.JsonArray; +import com.google.gson.JsonDeserializationContext; +import com.google.gson.JsonDeserializer; +import com.google.gson.JsonElement; +import com.google.gson.JsonParseException; +import com.google.gson.JsonSerializationContext; +import com.google.gson.JsonSerializer; + +public class ZarrAxesAdapter implements JsonDeserializer< ZarrAxes >, JsonSerializer< ZarrAxes > +{ + + @Override + public ZarrAxes deserialize( JsonElement json, Type typeOfT, JsonDeserializationContext context ) throws JsonParseException + { + JsonArray array = json.getAsJsonArray(); + if ( array.size() > 0 ) + { + StringBuilder axisString = new StringBuilder( "[" ); + for ( int i = 0; i < array.size(); i++ ) + { + String element; + try + { + element = array.get( i ).getAsString(); + } + catch ( UnsupportedOperationException e ) + { + try + { + JsonElement jj = array.get( i ); + element = jj.getAsJsonObject().get( "name" ).getAsString(); + } + catch ( Exception exception ) + { + throw new JsonParseException( "" + e ); + } + } + if ( i != 0 ) + { + axisString.append( "," ); + } + axisString.append( "\"" ); + axisString.append( element ); + axisString.append( "\"" ); + + } + axisString.append( "]" ); + return ZarrAxes.decode( axisString.toString() ); + } + else + { + return null; + } + } + + @Override + public JsonElement serialize( ZarrAxes axes, Type typeOfSrc, JsonSerializationContext context ) + { + List< String > axisList = axes.getAxesList(); + JsonArray jsonArray = new JsonArray(); + for ( String axis : axisList ) + { + jsonArray.add( axis ); + } + return jsonArray; + } +} diff --git a/src/main/java/org/mastodon/mamut/io/loader/util/mobie/ZarrAxis.java b/src/main/java/org/mastodon/mamut/io/loader/util/mobie/ZarrAxis.java new file mode 100644 index 000000000..7d2e3adc6 --- /dev/null +++ b/src/main/java/org/mastodon/mamut/io/loader/util/mobie/ZarrAxis.java @@ -0,0 +1,130 @@ +/*- + * #%L + * Mastodon + * %% + * Copyright (C) 2014 - 2024 Mastodon 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% + */ +/*- + * #%L + * Readers and writers for image data in MoBIE projects + * %% + * Copyright (C) 2021 - 2023 EMBL + * %% + * 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 org.mastodon.mamut.io.loader.util.mobie; + +import java.util.List; + +import com.google.gson.Gson; +import com.google.gson.JsonElement; + +public class ZarrAxis +{ + private transient final int index; + + private final String name; + + private final String type; + + private String unit; + + public ZarrAxis( int index, String name, String type, String unit ) + { + this.index = index; + this.name = name; + this.type = type; + this.unit = unit; + } + + public ZarrAxis( int index, String name, String type ) + { + this.index = index; + this.name = name; + this.type = type; + } + + public static JsonElement convertToJson( List< ZarrAxis > zarrAxes ) + { + StringBuilder axes = new StringBuilder(); + axes.append( "[" ); + for ( ZarrAxis axis : zarrAxes ) + { + axes.append( "\"" ).append( axis.getName() ).append( "\"" ); + if ( axis.getIndex() < zarrAxes.size() - 1 ) + { + axes.append( "," ); + } + } + axes.append( "]" ); + Gson gson = new Gson(); + return gson.fromJson( axes.toString(), JsonElement.class ); + } + + public int getIndex() + { + return index; + } + + public String getName() + { + return name; + } + + public String getType() + { + return type; + } + + public String getUnit() + { + return unit; + } + + public void setUnit( String unit ) + { + this.unit = unit; + } +} diff --git a/src/main/java/org/mastodon/mamut/launcher/LauncherGUI.java b/src/main/java/org/mastodon/mamut/launcher/LauncherGUI.java index 699e39703..d5bea035d 100644 --- a/src/main/java/org/mastodon/mamut/launcher/LauncherGUI.java +++ b/src/main/java/org/mastodon/mamut/launcher/LauncherGUI.java @@ -102,6 +102,8 @@ class LauncherGUI extends JPanel final NewMastodonProjectPanel newMastodonProjectPanel; + final NewFromUrlPanel newFromUrlPanel; + final LoggerPanel logger; final ImportTGMMPanel importTGMMPanel; @@ -208,6 +210,9 @@ public LauncherGUI( final Consumer< String > projectOpener ) newMastodonProjectPanel = new NewMastodonProjectPanel( "New Mastodon project", "create" ); centralPanel.add( newMastodonProjectPanel, NEW_MASTODON_PROJECT_KEY ); + newFromUrlPanel = new NewFromUrlPanel( "New From URL", "create" ); + centralPanel.add( newFromUrlPanel, NEW_FROM_URL_KEY ); + recentProjectsPanel = new RecentProjectsPanel( projectOpener ); centralPanel.add( recentProjectsPanel, RECENT_PROJECTS_KEY ); @@ -350,6 +355,7 @@ public void log( final String string ) { log( string, NORMAL_COLOR ); } + public void log( final String message, final Color color ) { final StyleContext sc = StyleContext.getDefaultStyleContext(); diff --git a/src/main/java/org/mastodon/mamut/launcher/MastodonLauncher.java b/src/main/java/org/mastodon/mamut/launcher/MastodonLauncher.java index cc758a782..37815d195 100644 --- a/src/main/java/org/mastodon/mamut/launcher/MastodonLauncher.java +++ b/src/main/java/org/mastodon/mamut/launcher/MastodonLauncher.java @@ -113,6 +113,7 @@ public MastodonLauncher( final Context context ) gui.btnHelp.addActionListener( l -> showHelpPanel() ); gui.newMastodonProjectPanel.btnCreate.addActionListener( l -> createNewProject() ); + gui.newFromUrlPanel.btnCreate.addActionListener( l -> createNewProjectFromURL() ); gui.importTGMMPanel.btnImport.addActionListener( l -> importTgmm() ); gui.importSimiBioCellPanel.btnImport.addActionListener( l -> importSimi() ); @@ -318,6 +319,32 @@ else if ( entries.length == 9 ) return null; } + private void createNewProjectFromURL() + { + gui.clearLog(); + + /* + * Open from a URL. + */ + + final EverythingDisablerAndReenabler disabler = + new EverythingDisablerAndReenabler( gui, new Class[] { JLabel.class } ); + disabler.disable(); + new Thread( () -> { + try + { + final ProjectModel appModel = + LauncherUtil.createProjectFromBdvFileWithDialog( gui.newFromUrlPanel.xmlFile, context, gui, gui::error ); + new MainWindow( appModel ).setVisible( true ); + dispose(); + } + finally + { + disabler.reenable(); + } + } ).start(); + } + private void createNewProject() { gui.clearLog(); @@ -571,7 +598,8 @@ private void loadMastodonProject( final String projectPath ) } catch ( final IOException e ) { - gui.error( "Invalid Mastodon file.\nMaybe it is not a Mastodon file?\n\n" + LauncherUtil.getProblemDescription( null, e ) ); + gui.error( "Invalid Mastodon file.\nMaybe it is not a Mastodon file?\n\n" + + LauncherUtil.getProblemDescription( null, e ) ); } } finally @@ -592,7 +620,8 @@ public synchronized void drop( final DropTargetDropEvent dropTargetDropEvent ) { dropTargetDropEvent.acceptDrop( DnDConstants.ACTION_COPY ); @SuppressWarnings( "unchecked" ) - final List< File > droppedFiles = ( List< File > ) dropTargetDropEvent.getTransferable().getTransferData( DataFlavor.javaFileListFlavor ); + final List< File > droppedFiles = + ( List< File > ) dropTargetDropEvent.getTransferable().getTransferData( DataFlavor.javaFileListFlavor ); for ( final File file : droppedFiles ) { // process files diff --git a/src/main/java/org/mastodon/mamut/launcher/NewFromUrlPanel.java b/src/main/java/org/mastodon/mamut/launcher/NewFromUrlPanel.java new file mode 100644 index 000000000..7ff410ab5 --- /dev/null +++ b/src/main/java/org/mastodon/mamut/launcher/NewFromUrlPanel.java @@ -0,0 +1,394 @@ +/*- + * #%L + * Mastodon + * %% + * Copyright (C) 2014 - 2024 Tobias Pietzsch, Jean-Yves Tinevez + * %% + * 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 org.mastodon.mamut.launcher; + +import java.awt.Font; +import java.awt.GridBagConstraints; +import java.awt.GridBagLayout; +import java.awt.Insets; +import java.io.File; +import java.net.URI; +import java.net.URISyntaxException; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.function.Function; +import java.util.stream.Collectors; +import java.util.stream.IntStream; + +import javax.swing.JButton; +import javax.swing.JFileChooser; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.SwingUtilities; +import javax.swing.filechooser.FileNameExtensionFilter; + +import org.janelia.saalfeldlab.n5.N5Exception; +import org.janelia.saalfeldlab.n5.N5Reader; +import org.janelia.saalfeldlab.n5.N5URI; +import org.janelia.saalfeldlab.n5.bdv.N5ViewerCreator; +import org.janelia.saalfeldlab.n5.bdv.N5ViewerTreeCellRenderer; +import org.janelia.saalfeldlab.n5.ij.N5Importer; +import org.janelia.saalfeldlab.n5.metadata.N5ViewerMultichannelMetadata; +import org.janelia.saalfeldlab.n5.ui.DatasetSelectorDialog; +import org.janelia.saalfeldlab.n5.universe.N5Factory; +import org.janelia.saalfeldlab.n5.universe.N5Factory.StorageFormat; +import org.janelia.saalfeldlab.n5.universe.metadata.MultiscaleMetadata; +import org.janelia.saalfeldlab.n5.universe.metadata.N5Metadata; +import org.janelia.saalfeldlab.n5.universe.metadata.N5SingleScaleMetadata; +import org.janelia.saalfeldlab.n5.universe.metadata.axes.AxisUtils; +import org.janelia.saalfeldlab.n5.universe.metadata.axes.DefaultAxisMetadata; +import org.janelia.saalfeldlab.n5.universe.metadata.ome.ngff.v04.NgffSingleScaleAxesMetadata; +import org.janelia.saalfeldlab.n5.universe.metadata.ome.ngff.v04.OmeNgffMetadata; +import org.mastodon.mamut.io.loader.N5UniverseImgLoader; +import org.mastodon.mamut.io.loader.util.credentials.AWSCredentialsManager; +import org.mastodon.mamut.io.loader.util.credentials.AWSCredentialsTools; + +import bdv.spimdata.SequenceDescriptionMinimal; +import bdv.spimdata.SpimDataMinimal; +import bdv.spimdata.XmlIoSpimDataMinimal; +import ij.IJ; +import mpicbg.spim.data.SpimDataException; +import mpicbg.spim.data.generic.sequence.BasicImgLoader; +import mpicbg.spim.data.generic.sequence.BasicViewSetup; +import mpicbg.spim.data.registration.ViewRegistration; +import mpicbg.spim.data.registration.ViewRegistrations; +import mpicbg.spim.data.sequence.FinalVoxelDimensions; +import mpicbg.spim.data.sequence.TimePoint; +import mpicbg.spim.data.sequence.TimePoints; +import mpicbg.spim.data.sequence.VoxelDimensions; +import net.imglib2.Dimensions; +import net.imglib2.FinalDimensions; +import net.imglib2.realtransform.AffineTransform3D; +import net.imglib2.util.Pair; + +class NewFromUrlPanel extends JPanel +{ + + private static final long serialVersionUID = 1L; + + final JButton btnCreate; + + final JLabel labelInfo; + + File xmlFile; + + private String lastOpenedContainer = ""; + + public NewFromUrlPanel( final String panelTitle, final String buttonTitle ) + { + final GridBagLayout gblNewMastodonProjectPanel = new GridBagLayout(); + gblNewMastodonProjectPanel.columnWidths = new int[] { 0, 0 }; + gblNewMastodonProjectPanel.rowHeights = new int[] { 35, 70, 65, 0, 25, 45, 0, 0, 25, 0, 0, 0 }; + gblNewMastodonProjectPanel.columnWeights = new double[] { 1.0, Double.MIN_VALUE }; + gblNewMastodonProjectPanel.rowWeights = + new double[] { 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, Double.MIN_VALUE }; + setLayout( gblNewMastodonProjectPanel ); + + int row = 0; + + final JLabel lblNewMastodonProject = new JLabel( panelTitle ); + lblNewMastodonProject.setFont( + lblNewMastodonProject.getFont().deriveFont( lblNewMastodonProject.getFont().getStyle() | Font.BOLD ) ); + final GridBagConstraints gbcLblNewMastodonProject = new GridBagConstraints(); + gbcLblNewMastodonProject.insets = new Insets( 5, 5, 5, 5 ); + gbcLblNewMastodonProject.gridx = 0; + gbcLblNewMastodonProject.gridy = row++; + add( lblNewMastodonProject, gbcLblNewMastodonProject ); + + final JLabel lblFetchFromURL = new JLabel( "Browse URL:" ); + final GridBagConstraints gbcLblFetchFromURL = new GridBagConstraints(); + gbcLblFetchFromURL.insets = new Insets( 5, 5, 5, 5 ); + gbcLblFetchFromURL.anchor = GridBagConstraints.SOUTHWEST; + gbcLblFetchFromURL.gridx = 0; + gbcLblFetchFromURL.gridy = row++; + add( lblFetchFromURL, gbcLblFetchFromURL ); + + final JButton btnBrowse = new JButton( "browse" ); + final GridBagConstraints gbcBtnBrowse = new GridBagConstraints(); + gbcBtnBrowse.insets = new Insets( 5, 5, 5, 5 ); + gbcBtnBrowse.anchor = GridBagConstraints.EAST; + gbcBtnBrowse.gridx = 0; + gbcBtnBrowse.gridy = row++; + add( btnBrowse, gbcBtnBrowse ); + + /* + * Wire listeners. + */ + btnBrowse.addActionListener( l -> { + xmlFile = null; + final ExecutorService exec = Executors.newFixedThreadPool( ij.Prefs.getThreads() ); + final DatasetSelectorDialog dialog = new DatasetSelectorDialog( + new N5ViewerReaderFun(), + new N5Importer.N5BasePathFun(), + lastOpenedContainer, + N5ViewerCreator.n5vGroupParsers, + N5ViewerCreator.n5vParsers ); + + dialog.setLoaderExecutor( exec ); + dialog.setContainerPathUpdateCallback( x -> lastOpenedContainer = x ); + dialog.setTreeRenderer( new N5ViewerTreeCellRenderer( false ) ); + + dialog.run( selection -> { + N5Metadata metadata = selection.metadata.get( 0 ); + long[] dimensions = null; + double[] scales = null; + String[] axisLabels = null; + String unit = null; + AffineTransform3D calib = null; + int x = 1; + int y = 1; + int z = 1; + int c = 1; + int t = 1; + double sx = 1.0; + double sy = 1.0; + double sz = 1.0; + if ( metadata instanceof OmeNgffMetadata ) + { + final NgffSingleScaleAxesMetadata setup0Level0Metadata = + ( ( OmeNgffMetadata ) metadata ).multiscales[ 0 ].getChildrenMetadata()[ 0 ]; + dimensions = setup0Level0Metadata.getAttributes().getDimensions(); + scales = setup0Level0Metadata.getScale(); + axisLabels = setup0Level0Metadata.getAxisLabels(); + unit = setup0Level0Metadata.unit(); + if ( unit == null || unit.isEmpty() ) + unit = "pixel"; + calib = setup0Level0Metadata.spatialTransform3d(); + } + else if ( metadata instanceof N5ViewerMultichannelMetadata ) + { + c = ( ( N5ViewerMultichannelMetadata ) metadata ).getChildrenMetadata().length; + final MultiscaleMetadata< ? > setup0Metadata = + ( ( N5ViewerMultichannelMetadata ) metadata ).getChildrenMetadata()[ 0 ]; + final N5SingleScaleMetadata setup0Level0Metadata = ( N5SingleScaleMetadata ) setup0Metadata.getChildrenMetadata()[ 0 ]; + dimensions = setup0Level0Metadata.getAttributes().getDimensions(); + scales = setup0Level0Metadata.getPixelResolution(); + final DefaultAxisMetadata axes = AxisUtils.defaultN5ViewerAxes( ( setup0Level0Metadata ) ); + axisLabels = axes.getAxisLabels(); + unit = setup0Level0Metadata.unit(); + if ( unit == null || unit.isEmpty() ) + unit = "pixel"; + calib = setup0Level0Metadata.spatialTransform3d(); + } + else + { + SwingUtilities.invokeLater( + () -> IJ.error( "Metadata not supported", "The metadata is not supported: " + metadata.getName() ) ); + return; + } + final AffineTransform3D calibFinal = calib.copy(); + + for ( int i = 0; i < axisLabels.length; i++ ) + { + if ( axisLabels[ i ].toLowerCase().equals( "x" ) ) + { + x = ( int ) dimensions[ i ]; + sx = scales[ i ]; + } + else if ( axisLabels[ i ].toLowerCase().equals( "y" ) ) + { + y = ( int ) dimensions[ i ]; + sy = scales[ i ]; + } + else if ( axisLabels[ i ].toLowerCase().equals( "z" ) ) + { + z = ( int ) dimensions[ i ]; + sz = scales[ i ]; + } + else if ( axisLabels[ i ].toLowerCase().equals( "c" ) ) + c = ( int ) dimensions[ i ]; + else if ( axisLabels[ i ].toLowerCase().equals( "t" ) ) + t = ( int ) dimensions[ i ]; + } + final Dimensions imageSize = new FinalDimensions( x, y, z ); + final TimePoints timepoints = new TimePoints( + IntStream.range( 0, t ).mapToObj( TimePoint::new ).collect( Collectors.toList() ) ); + final Map< Integer, BasicViewSetup > setups = new HashMap<>(); + final VoxelDimensions voxelDimensions = new FinalVoxelDimensions( unit, sx, sy, sz ); + for ( int i = 0; i < c; i++ ) + setups.put( i, new BasicViewSetup( i, String.format( "channel %d", i ), imageSize, voxelDimensions ) ); + final BasicImgLoader imgLoader = + new N5UniverseImgLoader( selection.n5.getURI().toString(), metadata.getPath(), null ); + final SequenceDescriptionMinimal sequenceDescription = + new SequenceDescriptionMinimal( timepoints, setups, imgLoader, null ); + final ViewRegistrations viewRegistrations = new ViewRegistrations( + IntStream.range( 0, t ) + .boxed() + .flatMap( tp -> IntStream.range( 0, setups.size() ) + .mapToObj( setup -> new ViewRegistration( tp, setup, calibFinal ) ) ) + .collect( Collectors.toList() ) + ); + + final JFileChooser fileChooser = new JFileChooser(); + fileChooser.setDialogTitle( "Save BigDataViewer XML File" ); + final FileNameExtensionFilter xmlFilter = new FileNameExtensionFilter( "XML Files", "xml" ); + fileChooser.setFileFilter( xmlFilter ); + final String currDir = IJ.getDirectory( "current" ); + if ( currDir != null ) + fileChooser.setCurrentDirectory( new File( currDir ) ); + int userSelection = fileChooser.showSaveDialog( null ); + if ( userSelection == JFileChooser.APPROVE_OPTION ) + { + File fileToSave = fileChooser.getSelectedFile(); + + // Ensure the file has the .xml extension + if ( !fileToSave.getAbsolutePath().endsWith( ".xml" ) ) + { + fileToSave = new File( fileToSave.getAbsolutePath() + ".xml" ); + } + + final SpimDataMinimal spimData = + new SpimDataMinimal( fileToSave.getParentFile(), sequenceDescription, viewRegistrations ); + + try + { + new XmlIoSpimDataMinimal().save( spimData, fileToSave.getAbsolutePath() ); + } + catch ( SpimDataException e ) + { + e.printStackTrace(); + } + + if ( checkBDVFile( fileToSave ) ) + { + xmlFile = fileToSave; + } + + } + } ); + } ); + + labelInfo = new JLabel( "" ); + final GridBagConstraints gbcLabelInfo = new GridBagConstraints(); + gbcLabelInfo.insets = new Insets( 5, 5, 5, 5 ); + gbcLabelInfo.fill = GridBagConstraints.BOTH; + gbcLabelInfo.gridx = 0; + gbcLabelInfo.gridy = row++; + add( labelInfo, gbcLabelInfo ); + + btnCreate = new JButton( buttonTitle ); + final GridBagConstraints gbcBtnCreate = new GridBagConstraints(); + gbcBtnCreate.anchor = GridBagConstraints.EAST; + gbcBtnCreate.gridx = 0; + gbcBtnCreate.gridy = row++; + add( btnCreate, gbcBtnCreate ); + } + + boolean checkBDVFile( final File file ) + { + try + { + final SpimDataMinimal spimData = new XmlIoSpimDataMinimal().load( file.getAbsolutePath() ); + final String str = LauncherUtil.buildInfoString( spimData ); + labelInfo.setText( str ); + return true; + } + catch ( final SpimDataException | RuntimeException e ) + { + labelInfo.setText( "Invalid BDV xml file.
" + e.getMessage() + "" );
+ return false;
+ }
+ }
+
+ public static class N5ViewerReaderFun implements Function< String, N5Reader >
+ {
+
+ public String message;
+
+ @Override
+ public N5Reader apply( final String n5UriOrPath )
+ {
+ if ( n5UriOrPath == null || n5UriOrPath.isEmpty() )
+ return null;
+
+ String rootPath = null;
+ if ( n5UriOrPath.contains( "?" ) )
+ {
+ try
+ {
+ // need to strip off storage format for n5uri to correctly remove query;
+ final Pair< StorageFormat, URI > fmtUri = N5Factory.StorageFormat.parseUri( n5UriOrPath );
+ final StorageFormat format = fmtUri.getA();
+
+ final N5URI n5uri = new N5URI( URI.create( fmtUri.getB().toString() ) );
+ // add the format prefix back if it was present
+ rootPath = format == null ? n5uri.getContainerPath() : format.toString().toLowerCase() + ":" + n5uri.getContainerPath();
+ }
+ catch ( final URISyntaxException e )
+ {
+ SwingUtilities.invokeLater(
+ () -> IJ.error( "Invalid URI", "The URI is not valid or credentials are missing: " + n5UriOrPath ) );
+ }
+ }
+
+ if ( rootPath == null )
+ rootPath = upToLastExtension( n5UriOrPath );
+
+ N5Factory factory = new N5Factory().cacheAttributes( true );
+ try
+ {
+ return factory.openReader( rootPath );
+ }
+ catch ( final Exception e )
+ {}
+ // Use credentials
+ if ( AWSCredentialsManager.getInstance().getCredentials() == null )
+ AWSCredentialsManager.getInstance().setCredentials( AWSCredentialsTools.getBasicAWSCredentials() );
+ factory = factory.s3UseCredentials( AWSCredentialsManager.getInstance().getCredentials() );
+ try
+ {
+ return factory.openReader( rootPath );
+ }
+ catch ( final N5Exception e )
+ {
+ AWSCredentialsManager.getInstance().setCredentials( null );
+ IJ.handleException( e );
+ }
+ return null;
+ }
+ }
+
+ private static String upToLastExtension( final String path )
+ {
+
+ final int i = path.lastIndexOf( '.' );
+ if ( i >= 0 )
+ {
+ final int j = path.substring( i ).indexOf( '/' );
+ if ( j >= 0 )
+ return path.substring( 0, i + j );
+ else
+ return path;
+ }
+ else
+ return path;
+ }
+}
diff --git a/src/main/java/org/mastodon/mamut/launcher/NewMastodonProjectPanel.java b/src/main/java/org/mastodon/mamut/launcher/NewMastodonProjectPanel.java
index b4b4655dc..70598e5a6 100644
--- a/src/main/java/org/mastodon/mamut/launcher/NewMastodonProjectPanel.java
+++ b/src/main/java/org/mastodon/mamut/launcher/NewMastodonProjectPanel.java
@@ -82,7 +82,7 @@ public NewMastodonProjectPanel( final String panelTitle, final String buttonTitl
gbcLblNewMastodonProject.gridy = 0;
add( lblNewMastodonProject, gbcLblNewMastodonProject );
- rdbtBrowseToBDV = new JRadioButton( "Browse to a BDV file (xml/h5 pair):" );
+ rdbtBrowseToBDV = new JRadioButton( "Browse to a BDV file (xml + N5/OME-Zarr/HDF5 pair):" );
final GridBagConstraints gbc_rdbtBrowseToBDV = new GridBagConstraints();
gbc_rdbtBrowseToBDV.anchor = GridBagConstraints.SOUTHWEST;
gbc_rdbtBrowseToBDV.insets = new Insets( 5, 5, 5, 5 );
diff --git a/src/main/java/org/mastodon/ui/util/CombinedFileFilter.java b/src/main/java/org/mastodon/ui/util/CombinedFileFilter.java
new file mode 100644
index 000000000..a4f57aae0
--- /dev/null
+++ b/src/main/java/org/mastodon/ui/util/CombinedFileFilter.java
@@ -0,0 +1,44 @@
+package org.mastodon.ui.util;
+
+import java.io.File;
+import javax.swing.filechooser.FileFilter;
+
+public class CombinedFileFilter extends FileFilter
+{
+
+ private FileFilter[] fileFilters;
+
+ public CombinedFileFilter( FileFilter... fileFilters )
+ {
+ this.fileFilters = fileFilters;
+ }
+
+ @Override
+ public boolean accept( File f )
+ {
+ for ( FileFilter fileFilter : fileFilters )
+ {
+ if ( fileFilter.accept( f ) )
+ {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public String getDescription()
+ {
+ StringBuilder sb = new StringBuilder();
+ for ( FileFilter fileFilter : fileFilters )
+ {
+ if ( sb.length() > 0 )
+ {
+ sb.append( " or " );
+ }
+ sb.append( fileFilter.getDescription() );
+ }
+ return sb.toString();
+ }
+
+}
diff --git a/src/main/java/org/mastodon/ui/util/HDF5FileFilter.java b/src/main/java/org/mastodon/ui/util/HDF5FileFilter.java
new file mode 100644
index 000000000..ecb2f6392
--- /dev/null
+++ b/src/main/java/org/mastodon/ui/util/HDF5FileFilter.java
@@ -0,0 +1,37 @@
+/*-
+ * #%L
+ * Mastodon
+ * %%
+ * Copyright (C) 2014 - 2022 Tobias Pietzsch, Jean-Yves Tinevez
+ * %%
+ * 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 org.mastodon.ui.util;
+
+public class HDF5FileFilter extends CombinedFileFilter
+{
+ public HDF5FileFilter()
+ {
+ super( new ExtensionFileFilter( "h5" ), new ExtensionFileFilter( "hdf5" ) );
+ }
+}
diff --git a/src/main/java/org/mastodon/views/bdv/SharedBigDataViewerData.java b/src/main/java/org/mastodon/views/bdv/SharedBigDataViewerData.java
index ca6fb05bf..ec6a80bcf 100644
--- a/src/main/java/org/mastodon/views/bdv/SharedBigDataViewerData.java
+++ b/src/main/java/org/mastodon/views/bdv/SharedBigDataViewerData.java
@@ -473,7 +473,8 @@ private static SharedBigDataViewerData fromSpimData(
if ( !sbdv.tryLoadSettings( spimDataXmlFilename ) )
{
final BasicViewerState state = new BasicViewerState();
- state.addSource( sources.get( 0 ) );
+ for ( int i = 0; i < sources.size(); ++i )
+ state.addSource( sources.get( i ) );
state.setCurrentSource( sources.get( 0 ) );
InitializeViewerState.initBrightness( 0.001, 0.999, state, setups );
}
diff --git a/src/test/java/org/mastodon/StartMastodonLauncher.java b/src/test/java/org/mastodon/StartMastodonLauncher.java
index 6514ff5ac..61004adb2 100644
--- a/src/test/java/org/mastodon/StartMastodonLauncher.java
+++ b/src/test/java/org/mastodon/StartMastodonLauncher.java
@@ -28,11 +28,14 @@
*/
package org.mastodon;
+import org.mastodon.mamut.io.loader.XmlIoN5UniverseImgLoader;
import org.mastodon.mamut.launcher.MastodonLauncherCommand;
import org.scijava.Context;
import org.scijava.command.CommandService;
import org.scijava.ui.UIService;
+import mpicbg.spim.data.generic.sequence.ImgLoaders;
+
/**
* Shows the ImageJ main window and Mastodon launcher.
*
@@ -46,6 +49,7 @@ public static void main( final String... args )
@SuppressWarnings( "resource" )
final Context context = new Context();
final UIService uiService = context.service( UIService.class );
+ ImgLoaders.registerManually( XmlIoN5UniverseImgLoader.class );
uiService.showUI();
final CommandService commandService = context.service( CommandService.class );
commandService.run( MastodonLauncherCommand.class, true );
diff --git a/src/test/java/org/mastodon/mamut/io/loader/N5UniverseImgLoaderTest.java b/src/test/java/org/mastodon/mamut/io/loader/N5UniverseImgLoaderTest.java
new file mode 100644
index 000000000..7edaed2e4
--- /dev/null
+++ b/src/test/java/org/mastodon/mamut/io/loader/N5UniverseImgLoaderTest.java
@@ -0,0 +1,77 @@
+package org.mastodon.mamut.io.loader;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import uk.org.webcompere.systemstubs.rules.EnvironmentVariablesRule;
+
+import java.io.File;
+import java.io.IOException;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.mastodon.mamut.ProjectModel;
+import org.mastodon.mamut.io.ProjectLoader;
+import org.mastodon.mamut.io.project.MamutProject;
+import org.mastodon.mamut.io.project.MamutProjectIO;
+import org.scijava.Context;
+
+import mpicbg.spim.data.SpimDataException;
+
+public class N5UniverseImgLoaderTest
+{
+
+ @Rule
+ public EnvironmentVariablesRule environmentVariablesRuleId =
+ new EnvironmentVariablesRule( "AWS_ACCESS_KEY_ID", "admin" );
+
+ @Rule
+ public EnvironmentVariablesRule environmentVariablesRuleSecret =
+ new EnvironmentVariablesRule( "AWS_SECRET_ACCESS_KEY", "password" );
+
+ @Test
+ public void testN5HDF5Reader() throws IOException, SpimDataException
+ {
+ testN5UniverseImgLoader( "/org/mastodon/mamut/io/loader/mitosis_small_hdf5.xml" );
+ }
+
+ @Test
+ public void testN5KeyValueReader() throws IOException, SpimDataException
+ {
+ testN5UniverseImgLoader( "/org/mastodon/mamut/io/loader/mitosis_small_n5.xml" );
+ }
+
+ @Test
+ public void testZarrKeyValueReader() throws IOException, SpimDataException
+ {
+ testN5UniverseImgLoader( "/org/mastodon/mamut/io/loader/mitosis_small_omezarr.xml" );
+ }
+
+ @Test
+ public void testZarrKeyValueReaderMinIOPublic() throws IOException, SpimDataException
+ {
+ testN5UniverseImgLoader( "/org/mastodon/mamut/io/loader/mitosis_small_omezarr_s3.xml" );
+ }
+
+ @Test
+ public void variableCanBeRead()
+ {
+ assertEquals( System.getenv( "AWS_ACCESS_KEY_ID" ), "admin" );
+ assertEquals( System.getenv( "AWS_SECRET_ACCESS_KEY" ), "password" );
+ }
+
+ @Test
+ public void testZarrKeyValueReaderMinIOPrivate() throws IOException, SpimDataException
+ {
+ testN5UniverseImgLoader( "/org/mastodon/mamut/io/loader/mitosis_small_omezarr_s3_private.xml" );
+ }
+
+ private void testN5UniverseImgLoader( final String resource ) throws IOException, SpimDataException
+ {
+ final String filepath = N5UniverseImgLoaderTest.class.getResource( resource ).getPath();
+ final File file = new File( filepath );
+ final MamutProject project = MamutProjectIO.fromBdvFile( file );
+ final ProjectModel appModel = ProjectLoader.open( project, new Context() );
+ assertTrue( appModel.getProject().getDatasetXmlFile().exists() );
+ }
+
+}
diff --git a/src/test/resources/org/mastodon/mamut/io/loader/13457227_s3_omezarr_tczyx.xml b/src/test/resources/org/mastodon/mamut/io/loader/13457227_s3_omezarr_tczyx.xml
new file mode 100644
index 000000000..6bc54e2af
--- /dev/null
+++ b/src/test/resources/org/mastodon/mamut/io/loader/13457227_s3_omezarr_tczyx.xml
@@ -0,0 +1,543 @@
+
+