diff --git a/src/main/java/org/embl/mobie/viewer/MoBIE.java b/src/main/java/org/embl/mobie/viewer/MoBIE.java index f8c88599e..c09fe0495 100644 --- a/src/main/java/org/embl/mobie/viewer/MoBIE.java +++ b/src/main/java/org/embl/mobie/viewer/MoBIE.java @@ -1,6 +1,5 @@ package org.embl.mobie.viewer; -import bdv.util.volatiles.SharedQueue; import bdv.img.n5.N5ImageLoader; import bdv.viewer.Source; import bdv.viewer.SourceAndConverter; @@ -39,16 +38,13 @@ import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; +import java.util.concurrent.Future; public class MoBIE { static { net.imagej.patcher.LegacyInjector.preinit(); } - public static final int N_THREADS = Runtime.getRuntime().availableProcessors() - 1; - public static final SharedQueue sharedQueue = new SharedQueue( N_THREADS ); public static final String PROTOTYPE_DISPLAY_VALUE = "01234567890123456789"; - public static final ExecutorService executorService = Executors.newFixedThreadPool( N_THREADS ); private final String projectName; private MoBIESettings settings; @@ -181,17 +177,16 @@ private void setProjectImageAndTableRootLocations( ) Map< String, SourceAndConverter< ? > > sourceAndConverters = new ConcurrentHashMap< >(); - final ExecutorService executorService = Executors.newFixedThreadPool( N_THREADS ); + final ArrayList< Future< ? > > futures = ThreadUtils.getFutures(); for ( String sourceName : sources ) { - executorService.execute( () -> { - sourceAndConverters.put( sourceName, openSourceAndConverter( sourceName ) ); - } ); + futures.add( + ThreadUtils.ioExecutorService.submit( () -> { sourceAndConverters.put( sourceName, openSourceAndConverter( sourceName ) ); } + ) ); } + ThreadUtils.waitUntilFinished( futures ); - MoBIEUtils.waitUntilFinishedAndShutDown( executorService ); - - System.out.println( "Fetched " + sourceAndConverters.size() + " image source(s) in " + (System.currentTimeMillis() - start) + " ms, using " + N_THREADS + " thread(s)."); + System.out.println( "Fetched " + sourceAndConverters.size() + " image source(s) in " + (System.currentTimeMillis() - start) + " ms, using " + ThreadUtils.N_IO_THREADS + " thread(s)."); return sourceAndConverters; } @@ -291,7 +286,7 @@ public synchronized ImageSource getSource( String sourceName ) final String imagePath = getImagePath( imageSource ); IJ.log( "Opening image:\n" + imagePath ); final ImageDataFormat imageDataFormat = settings.values.getImageDataFormat(); - SpimData spimData = new SpimDataOpener().openSpimData( imagePath, imageDataFormat, sharedQueue ); + SpimData spimData = new SpimDataOpener().openSpimData( imagePath, imageDataFormat, ThreadUtils.sharedQueue ); sourceNameToImgLoader.put( sourceName, spimData.getSequenceDescription().getImgLoader() ); final SourceAndConverterFromSpimDataCreator creator = new SourceAndConverterFromSpimDataCreator( spimData ); @@ -381,20 +376,20 @@ private List< Map< String, List< String > > > loadAdditionalTables( List final List< Map< String, List< String > > > additionalTables = new CopyOnWriteArrayList<>(); final long start = System.currentTimeMillis(); - final ExecutorService executorService = Executors.newFixedThreadPool( N_THREADS ); - + final ExecutorService executorService = ThreadUtils.ioExecutorService; + final ArrayList< Future< ? > > futures = ThreadUtils.getFutures(); for ( String sourceName : sources ) { - executorService.execute( () -> { - Map< String, List< String > > columns = - loadAdditionalTable( sourceName, getTablePath( ( SegmentationSource ) getSource( sourceName ), table ) ); + futures.add( + executorService.submit( () -> { + Map< String, List< String > > columns = loadAdditionalTable( sourceName, getTablePath( ( SegmentationSource ) getSource( sourceName ), table ) ); additionalTables.add( columns ); - } ); + } ) + ); } + ThreadUtils.waitUntilFinished( futures ); - MoBIEUtils.waitUntilFinishedAndShutDown( executorService ); - - System.out.println( "Fetched " + sources.size() + " table(s) in " + (System.currentTimeMillis() - start) + " ms, using " + N_THREADS + " thread(s)."); + System.out.println( "Fetched " + sources.size() + " table(s) in " + (System.currentTimeMillis() - start) + " ms, using " + ThreadUtils.N_IO_THREADS + " thread(s)."); return additionalTables; } @@ -416,21 +411,23 @@ private ArrayList< List< TableRowImageSegment > > loadPrimarySegmentsTables( Seg sourceNameToRootSources.put( sourceName, rootSources ); } - // TODO: make parallel + final ArrayList< List< TableRowImageSegment > > primaryTables = new ArrayList<>(); + final ArrayList< Future< ? > > futures = ThreadUtils.getFutures(); for ( String displayedSourceName : segmentationDisplaySources ) { final Set< Source > rootSources = sourceNameToRootSources.get( displayedSourceName ); for ( Source rootSource : rootSources ) { - addPrimaryTable( table, primaryTables, rootSource.getName() ); + futures.add( ThreadUtils.ioExecutorService.submit( () -> loadAndAddPrimaryTable( table, primaryTables, rootSource.getName() ) ) ); } } + ThreadUtils.waitUntilFinished( futures ); return primaryTables; } - private void addPrimaryTable( String tableName, ArrayList< List< TableRowImageSegment > > primaryTables, String tableSourceName ) + private void loadAndAddPrimaryTable( String tableName, ArrayList< List< TableRowImageSegment > > primaryTables, String tableSourceName ) { final List< TableRowImageSegment > primaryTable = loadImageSegmentsTable( tableSourceName, tableName ); primaryTables.add( primaryTable ); diff --git a/src/main/java/org/embl/mobie/viewer/MoBIEUtils.java b/src/main/java/org/embl/mobie/viewer/MoBIEUtils.java index e98272fba..d04994ef0 100644 --- a/src/main/java/org/embl/mobie/viewer/MoBIEUtils.java +++ b/src/main/java/org/embl/mobie/viewer/MoBIEUtils.java @@ -1,56 +1,42 @@ package org.embl.mobie.viewer; -import bdv.SpimSource; -import bdv.tools.transformation.TransformedSource; import bdv.util.BdvHandle; -import bdv.util.ResampledSource; import bdv.viewer.Source; import bdv.viewer.SourceAndConverter; import de.embl.cba.bdv.utils.BdvUtils; import de.embl.cba.tables.FileAndUrlUtils; import de.embl.cba.tables.TableColumns; -import de.embl.cba.tables.imagesegment.ImageSegment; import de.embl.cba.tables.imagesegment.SegmentProperty; import de.embl.cba.tables.imagesegment.SegmentUtils; import de.embl.cba.tables.tablerow.TableRowImageSegment; import ij.IJ; import ij.gui.GenericDialog; -import net.imglib2.*; +import net.imglib2.FinalRealInterval; +import net.imglib2.RealPoint; import net.imglib2.realtransform.AffineTransform3D; import net.imglib2.realtransform.Scale3D; -import net.imglib2.util.Intervals; -import org.embl.mobie.viewer.transform.MergedGridSource; import javax.swing.*; import javax.swing.filechooser.FileNameExtensionFilter; -import java.io.*; +import java.io.File; import java.net.URI; -import java.util.*; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.HashMap; import java.util.List; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.TimeUnit; +import java.util.Map; import java.util.stream.Collectors; import static de.embl.cba.bdv.utils.BdvUtils.getBdvWindowCenter; -import static de.embl.cba.tables.Utils.*; -import static de.embl.cba.tables.Utils.getVoxelSpacings; -import static org.embl.mobie.viewer.ui.SwingHelper.selectionDialog; import static de.embl.cba.tables.imagesegment.SegmentUtils.BB_MAX_Z; import static de.embl.cba.tables.imagesegment.SegmentUtils.BB_MIN_Z; +import static org.embl.mobie.viewer.ui.SwingHelper.selectionDialog; public abstract class MoBIEUtils { static { net.imagej.patcher.LegacyInjector.preinit(); } - public static void waitUntilFinishedAndShutDown( ExecutorService executorService ) - { - executorService.shutdown(); - try { - executorService.awaitTermination(Long.MAX_VALUE, TimeUnit.NANOSECONDS); - } catch (InterruptedException e) { - } - } - public static int[] asInts( long[] longs) { int[] ints = new int[longs.length]; diff --git a/src/main/java/org/embl/mobie/viewer/SourceNameEncoder.java b/src/main/java/org/embl/mobie/viewer/SourceNameEncoder.java index 6094fd69a..606e3b9f3 100644 --- a/src/main/java/org/embl/mobie/viewer/SourceNameEncoder.java +++ b/src/main/java/org/embl/mobie/viewer/SourceNameEncoder.java @@ -42,27 +42,36 @@ public static String getName( final UnsignedIntType unsignedIntType ) public static String getName( final VolatileUnsignedIntType unsignedIntType ) { - final long imageIndex = unsignedIntType.get().get() >> valueBits; + return getName( unsignedIntType.get().get() ); + } + + public static String getName( final long l ) + { + final long imageIndex = l >> valueBits; final String name = longToName.get( imageIndex ); return name; } public static long getValue( final UnsignedIntType unsignedIntType ) { - final long value = unsignedIntType.get() & 0x0000FFFF; - return value; + return getValue( unsignedIntType.get() ); } public static long getValue( final VolatileUnsignedIntType unsignedIntType ) { - final long value = unsignedIntType.get().get() & 0x0000FFFF; + return getValue( unsignedIntType.get().get() ); + } + + public static long getValue( final long l ) + { + final long value = l & 0x0000FFFF; return value; } public static void encodeName( final UnsignedIntType value, final String name ) { - final long encoded = value.get() + ( nameToLong.get( name ) << valueBits ); + final long l = value.get(); + final long encoded = l + ( nameToLong.get( name ) << valueBits ); value.set( encoded ); } - } diff --git a/src/main/java/org/embl/mobie/viewer/ThreadUtils.java b/src/main/java/org/embl/mobie/viewer/ThreadUtils.java new file mode 100644 index 000000000..7cba8a84a --- /dev/null +++ b/src/main/java/org/embl/mobie/viewer/ThreadUtils.java @@ -0,0 +1,45 @@ +package org.embl.mobie.viewer; + +import bdv.util.volatiles.SharedQueue; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; +import java.util.concurrent.TimeUnit; + +public class ThreadUtils +{ + public static final int N_FETCHER_THREADS = Runtime.getRuntime().availableProcessors() - 1; + public static final SharedQueue sharedQueue = new SharedQueue( N_FETCHER_THREADS ); + + public static final int N_IO_THREADS = 8; + public static final ExecutorService ioExecutorService = Executors.newFixedThreadPool( N_IO_THREADS ); + + public static final int N_THREADS = Runtime.getRuntime().availableProcessors() - 1;; + public static final ExecutorService executorService = Executors.newFixedThreadPool( N_THREADS ); + + public static void waitUntilFinished( List< Future< ? > > futures ) + { + for ( Future< ? > future : futures ) + { + try + { + future.get(); + } catch ( InterruptedException e ) + { + e.printStackTrace(); + } catch ( ExecutionException e ) + { + e.printStackTrace(); + } + } + } + + public static ArrayList< Future< ? > > getFutures() + { + return new ArrayList<>(); + } +} diff --git a/src/main/java/org/embl/mobie/viewer/bdv/view/SegmentationSliceView.java b/src/main/java/org/embl/mobie/viewer/bdv/view/SegmentationSliceView.java index abc0f1f46..3762f748e 100644 --- a/src/main/java/org/embl/mobie/viewer/bdv/view/SegmentationSliceView.java +++ b/src/main/java/org/embl/mobie/viewer/bdv/view/SegmentationSliceView.java @@ -101,6 +101,7 @@ private LabelConverter< S > getLabelConverter( SourceAndConverter< ? > sourceAnd // image segments should be fetched. // Thus, the constructor where the source name // is determined from encoding in the label is chosen. + return new LabelConverter( display.segmentAdapter, display.coloringModel ); diff --git a/src/main/java/org/embl/mobie/viewer/color/LabelConverter.java b/src/main/java/org/embl/mobie/viewer/color/LabelConverter.java index 801ef45de..32d0f780f 100644 --- a/src/main/java/org/embl/mobie/viewer/color/LabelConverter.java +++ b/src/main/java/org/embl/mobie/viewer/color/LabelConverter.java @@ -48,13 +48,12 @@ public class LabelConverter< S extends ImageSegment > implements Converter< Real private int timePointIndex = 0; private double opacity = 1.0; - // No imageId given => decode from pixel value public LabelConverter( SegmentAdapter< S > segmentAdapter, MoBIEColoringModel< S > coloringModel ) { this.segmentAdapter = segmentAdapter; - this.imageId = null; + this.imageId = null; // No imageId given => decode from pixel value this.coloringModel = coloringModel; } @@ -80,48 +79,43 @@ public void convert( RealType label, ARGBType color ) } } - if ( label.getRealDouble() == 0 ) + if ( imageId == null ) { - color.set( 0 ); - return; - } + final long labelId = SourceNameEncoder.getValue( ( VolatileUnsignedIntType ) label ); - S imageSegment = getImageSegment( label ); + if ( labelId == 0 ) + { + color.set( 0 ); + return; + } - if ( imageSegment == null ) - { - color.set( 0 ); - return; + final String imageId = SourceNameEncoder.getName( ( VolatileUnsignedIntType ) label ); + S segment = segmentAdapter.getSegment( labelId, timePointIndex, imageId ); + setColorBySegment( color, segment ); } else { - coloringModel.convert( imageSegment, color ); - final int alpha = ARGBType.alpha( color.get() ); - color.mul( alpha / 255.0 ); - } - - color.mul( opacity ); - } + final double labelId = label.getRealDouble(); - // TODO: figure out how to make this work for more types - private S getImageSegment( RealType label ) - { - if ( imageId == null ) - { - final long value = SourceNameEncoder.getValue( ( VolatileUnsignedIntType ) label ); - if ( value == 0 ) + if ( labelId == 0 ) { - return null; // background + color.set( 0 ); + return; } - final String imageId = SourceNameEncoder.getName( ( VolatileUnsignedIntType ) label ); - return segmentAdapter.getSegmentCreateIfNotExist( value, timePointIndex, imageId ); - } - else - { - return segmentAdapter.getSegmentCreateIfNotExist( label.getRealDouble(), timePointIndex, imageId ); + + final S segment = segmentAdapter.getSegment( labelId, timePointIndex, imageId ); + setColorBySegment( color, segment ); } } + private void setColorBySegment( ARGBType color, S imageSegment ) + { + coloringModel.convert( imageSegment, color ); + final int alpha = ARGBType.alpha( color.get() ); + color.mul( alpha / 255.0 ); + color.mul( opacity ); + } + @Override public void timePointChanged( int timePointIndex ) { diff --git a/src/main/java/org/embl/mobie/viewer/project/PublishedProjectsCreator.java b/src/main/java/org/embl/mobie/viewer/project/PublishedProjectsCreator.java index e6b892f9f..95413aed8 100644 --- a/src/main/java/org/embl/mobie/viewer/project/PublishedProjectsCreator.java +++ b/src/main/java/org/embl/mobie/viewer/project/PublishedProjectsCreator.java @@ -14,23 +14,25 @@ public PublishedProjectsCreator() platybrowserProject.name = "Vergara et al. (2020) \"PlatyBrowser\", bioRxiv"; platybrowserProject.location = "https://github.com/mobie/platybrowser-datasets"; platybrowserProject.pulicationURL = "https://www.biorxiv.org/content/10.1101/2020.02.26.961037v1"; - publishedProjects.put( platybrowserProject.name, platybrowserProject ); final PublishedProject covidTomoProject = new PublishedProject(); - covidTomoProject.name = "Cortese et al. (2020) covid tomograms, Cell Host & Microbe"; + covidTomoProject.name = "Cortese et al. (2020) Covid tomograms, Cell Host & Microbe"; covidTomoProject.location = "https://github.com/mobie/covid-tomo-datasets"; covidTomoProject.pulicationURL = "https://www.sciencedirect.com/science/article/pii/S193131282030620X"; - publishedProjects.put( covidTomoProject.name, covidTomoProject ); final PublishedProject covidFIBProject = new PublishedProject(); - covidFIBProject.name = "Cortese et al. (2020) covid FIB-SEM, Cell Host & Microbe"; + covidFIBProject.name = "Cortese et al. (2020) Covid FIB-SEM, Cell Host & Microbe"; covidFIBProject.location = "https://github.com/mobie/covid-em-datasets"; covidFIBProject.pulicationURL = "https://www.sciencedirect.com/science/article/pii/S193131282030620X"; - publishedProjects.put( covidFIBProject.name, covidFIBProject ); + final PublishedProject spongeProject = new PublishedProject(); + spongeProject.name = "Musser et al. (2021) Sponge FIB-SEM, Science"; + spongeProject.location = "https://github.com/mobie/sponge-fibsem-project"; + spongeProject.pulicationURL = "https://www.science.org/doi/abs/10.1126/science.abj2949"; + publishedProjects.put( spongeProject.name, spongeProject ); } public HashMap< String, PublishedProject > getPublishedProjects() diff --git a/src/main/java/org/embl/mobie/viewer/segment/BdvSegmentSelector.java b/src/main/java/org/embl/mobie/viewer/segment/BdvSegmentSelector.java index f17487519..e1b9ce222 100644 --- a/src/main/java/org/embl/mobie/viewer/segment/BdvSegmentSelector.java +++ b/src/main/java/org/embl/mobie/viewer/segment/BdvSegmentSelector.java @@ -5,8 +5,10 @@ import bdv.viewer.Source; import bdv.viewer.SourceAndConverter; import ij.IJ; +import net.imglib2.type.volatiles.VolatileUnsignedIntType; import org.embl.mobie.io.n5.source.LabelSource; import org.embl.mobie.viewer.MoBIE; +import org.embl.mobie.viewer.SourceNameEncoder; import org.embl.mobie.viewer.bdv.BdvMousePositionProvider; import org.embl.mobie.viewer.display.SegmentationSourceDisplay; import de.embl.cba.tables.tablerow.TableRowImageSegment; @@ -25,8 +27,10 @@ public class BdvSegmentSelector implements Runnable { private BdvHandle bdvHandle; private boolean is2D; - private Supplier< Collection > segmentationDisplaySupplier; + private Supplier< Collection< SegmentationSourceDisplay > > segmentationDisplaySupplier; + // A segmentationDisplaySupplier is used such that the segmentation images can + // change during runtime. public BdvSegmentSelector( BdvHandle bdvHandle, boolean is2D, Supplier< Collection< SegmentationSourceDisplay > > segmentationDisplaySupplier ) { this.bdvHandle = bdvHandle; @@ -36,7 +40,7 @@ public BdvSegmentSelector( BdvHandle bdvHandle, boolean is2D, Supplier< Collecti public synchronized void clearSelection() { - final Collection< SegmentationSourceDisplay > segmentationDisplays = segmentationDisplaySupplier.get(); + final Collection< SegmentationSourceDisplay > segmentationDisplays = getCurrent(); for ( SegmentationSourceDisplay segmentationDisplay : segmentationDisplays ) { @@ -44,6 +48,11 @@ public synchronized void clearSelection() } } + private Collection< SegmentationSourceDisplay > getCurrent() + { + return segmentationDisplaySupplier.get(); + } + private synchronized void toggleSelectionAtMousePosition() { final BdvMousePositionProvider positionProvider = new BdvMousePositionProvider( bdvHandle ); @@ -54,52 +63,69 @@ private synchronized void toggleSelectionAtMousePosition() for ( SegmentationSourceDisplay segmentationDisplay : segmentationDisplays ) { - final Collection< SourceAndConverter< ? > > displayedSourceAndConverters = segmentationDisplay.sourceNameToSourceAndConverter.values(); - for ( SourceAndConverter< ? > displayedSourceAndConverter : displayedSourceAndConverters ) + final Collection< SourceAndConverter< ? > > segmentationSourceAndConverters = segmentationDisplay.sourceNameToSourceAndConverter.values(); + + for ( SourceAndConverter< ? > sourceAndConverter : segmentationSourceAndConverters ) { - final boolean sourceVisible = bdvHandle.getViewerPanel().state().isSourceVisible( displayedSourceAndConverter ); - if ( ! sourceVisible ) + if ( ! bdvHandle.getViewerPanel().state().isSourceVisible( sourceAndConverter ) ) { continue; } - // The source might be a MergedGridSource and thereby represent several sources that - // need to be inspected for whether they contain selectable image segments. - // TODO: Probably more efficient and consistent to simplify this, - // using the image name encoding instead. - final Collection< SourceAndConverter< ? > > sourceAndConverters = getContainedSourceAndConverters( displayedSourceAndConverter ); - - for ( SourceAndConverter< ? > sourceAndConverter : sourceAndConverters ) + if ( SourceAndConverterHelper.isPositionWithinSourceInterval( sourceAndConverter, position, timePoint, is2D ) ) { - if ( SourceAndConverterHelper.isPositionWithinSourceInterval( sourceAndConverter, position, timePoint, is2D ) ) - { - final Source< ? > source = sourceAndConverter.getSpimSource(); + final Source< ? > source = sourceAndConverter.getSpimSource(); + + final double pixelValue = getPixelValue( timePoint, position, source ); + final String sourceName = getSourceName( source, pixelValue ); + double labelIndex = getLabelIndex( source, pixelValue ); - final double labelIndex = getPixelValue( timePoint, position, source ); - if ( labelIndex == 0 ) continue; // background + if ( labelIndex == 0 ) continue; // image background - final String sourceName = source.getName(); + final boolean containsSegment = segmentationDisplay.segmentAdapter.containsSegment( labelIndex, timePoint, sourceName ); - final boolean containsSegment = segmentationDisplay.segmentAdapter.containsSegment( labelIndex, timePoint, sourceName ); + if ( ! containsSegment ) + { + // This happens when there is a segmentation without + // a segment table + continue; + } - if ( ! containsSegment ) - { - continue; - } + final TableRowImageSegment segment = segmentationDisplay.segmentAdapter.getSegment( labelIndex, timePoint, sourceName ); - final TableRowImageSegment segment = segmentationDisplay.segmentAdapter.getSegment( labelIndex, timePoint, sourceName ); + segmentationDisplay.selectionModel.toggle( segment ); - segmentationDisplay.selectionModel.toggle( segment ); - if ( segmentationDisplay.selectionModel.isSelected( segment ) ) - { - segmentationDisplay.selectionModel.focus( segment ); - } + if ( segmentationDisplay.selectionModel.isSelected( segment ) ) + { + segmentationDisplay.selectionModel.focus( segment ); } } } } } + private static double getLabelIndex( Source< ? > source, double pixelValue ) + { + if ( MergedGridSource.instanceOf( source ) ) + { + return SourceNameEncoder.getValue( Double.valueOf( pixelValue ).longValue() ); + } + else + { + return pixelValue; + } + } + + private static String getSourceName( Source< ? > source, double labelIndex ) + { + if ( MergedGridSource.instanceOf( source ) ) + { + return SourceNameEncoder.getName( Double.valueOf( labelIndex ).longValue() ); + } + final String sourceName = source.getName(); + return sourceName; + } + private Collection< SourceAndConverter< ? > > getContainedSourceAndConverters( SourceAndConverter< ? > sourceAndConverter ) { final Collection< SourceAndConverter< ? > > containedSourceAndConverters = new HashSet<>(); @@ -134,8 +160,7 @@ private static double getPixelValue( int timePoint, RealPoint position, Source< final RandomAccess< RealType > randomAccess = ( RandomAccess< RealType > ) source.getSource( timePoint, 0 ).randomAccess(); final long[] positionInSource = SourceAndConverterHelper.getVoxelPositionInSource( source, position, timePoint, 0 ); randomAccess.setPosition( positionInSource ); - final double labelIndex = randomAccess.get().getRealDouble(); - return labelIndex; + return randomAccess.get().getRealDouble(); } @Override diff --git a/src/main/java/org/embl/mobie/viewer/transform/MergedGridSource.java b/src/main/java/org/embl/mobie/viewer/transform/MergedGridSource.java index 30e6180a8..34181fe51 100644 --- a/src/main/java/org/embl/mobie/viewer/transform/MergedGridSource.java +++ b/src/main/java/org/embl/mobie/viewer/transform/MergedGridSource.java @@ -6,6 +6,7 @@ import bdv.viewer.Source; import bdv.viewer.SourceAndConverter; import net.imglib2.type.numeric.integer.UnsignedIntType; +import org.embl.mobie.io.n5.source.LabelSource; import org.embl.mobie.viewer.MoBIEUtils; import mpicbg.spim.data.sequence.VoxelDimensions; import net.imglib2.Cursor; @@ -67,7 +68,17 @@ public MergedGridSource( List< Source< T > > gridSources, List< int[] > position public static boolean instanceOf( SourceAndConverter< ? > sourceAndConverter ) { - return (( TransformedSource ) sourceAndConverter.getSpimSource() ).getWrappedSource() instanceof MergedGridSource || sourceAndConverter.getSpimSource() instanceof MergedGridSource; + final Source< ? > source = sourceAndConverter.getSpimSource(); + return instanceOf( source ); + } + + public static boolean instanceOf( Source< ? > source ) + { + if ( source instanceof LabelSource ) source = ( ( LabelSource ) source ).getWrappedSource(); + + final Source wrappedSource = ( ( TransformedSource ) source ).getWrappedSource(); + + return wrappedSource instanceof MergedGridSource || source instanceof MergedGridSource; } public List< Source< T > > getGridSources() diff --git a/src/main/java/org/embl/mobie/viewer/transform/MergedGridSourceTransformer.java b/src/main/java/org/embl/mobie/viewer/transform/MergedGridSourceTransformer.java index 1f27b7ca1..f67b6bef5 100644 --- a/src/main/java/org/embl/mobie/viewer/transform/MergedGridSourceTransformer.java +++ b/src/main/java/org/embl/mobie/viewer/transform/MergedGridSourceTransformer.java @@ -5,12 +5,11 @@ import bdv.viewer.Source; import bdv.viewer.SourceAndConverter; import de.embl.cba.tables.Logger; -import ij.IJ; -import org.embl.mobie.viewer.MoBIE; import org.embl.mobie.viewer.MoBIEUtils; import net.imglib2.FinalRealInterval; import net.imglib2.converter.Converter; import net.imglib2.type.numeric.ARGBType; +import org.embl.mobie.viewer.ThreadUtils; import java.util.ArrayList; import java.util.List; @@ -19,6 +18,7 @@ import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; +import java.util.concurrent.Future; import java.util.stream.Collectors; public class MergedGridSourceTransformer extends AbstractSourceTransformer @@ -59,34 +59,27 @@ public void transform( Map< String, SourceAndConverter< ? > > sourceNameToSource private void transformContainedSources( Map< String, SourceAndConverter< ? > > sourceNameToSourceAndConverter, List< SourceAndConverter< ? > > gridSources ) { - final long start = System.currentTimeMillis(); - final ArrayList< SourceAndConverter< ? > > referenceSources = new ArrayList<>(); referenceSources.add( gridSources.get( 0 ) ); final double[] gridCellRealDimensions = mergedGridSource.getCellRealDimensions(); - // due to margin... + // account for grid margin translationRealOffset = computeTranslationOffset( gridSources, gridCellRealDimensions ); - final int nThreads = MoBIE.N_THREADS; - final ExecutorService executorService = Executors.newFixedThreadPool( nThreads ); - final int numSources = gridSources.size(); + final ArrayList< Future< ? > > futures = ThreadUtils.getFutures(); for ( int positionIndex = 0; positionIndex < numSources; positionIndex++ ) { final int finalPositionIndex = positionIndex; final ArrayList< String > sourceNamesAtGridPosition = getSourcesAtGridPosition( gridSources, finalPositionIndex ); - executorService.execute( () -> { + futures.add( ThreadUtils.executorService.submit( () -> { recursivelyTransformSources( sourceNameToSourceAndConverter, gridCellRealDimensions, finalPositionIndex, sourceNamesAtGridPosition ); - } ); - + } ) ); } - MoBIEUtils.waitUntilFinishedAndShutDown( executorService ); - - IJ.log( "Transformed " + transformedSourceAndConverters.size() + " source(s) in " + (System.currentTimeMillis() - start) + " ms, using " + nThreads + " thread(s)." ); + ThreadUtils.waitUntilFinished( futures ); } private double[] computeTranslationOffset( List< SourceAndConverter< ? > > gridSources, double[] gridCellRealDimensions ) @@ -107,7 +100,10 @@ private double[] computeTranslationOffset( List< SourceAndConverter< ? > > gridS private void recursivelyTransformSources( Map< String, SourceAndConverter< ? > > sourceNameToSourceAndConverter, double[] gridCellRealDimensions, int finalPositionIndex, ArrayList< String > transformedSourceNames ) { // transform the sources - TransformedGridSourceTransformer.translate( sourceNameToSourceAndConverter, transformedSourceNames, null, centerAtOrigin, gridCellRealDimensions[ 0 ] * positions.get( finalPositionIndex )[ 0 ] + translationRealOffset[ 0 ], gridCellRealDimensions[ 1 ] * positions.get( finalPositionIndex )[ 1 ] + translationRealOffset[ 1 ]); + final double translationX = gridCellRealDimensions[ 0 ] * positions.get( finalPositionIndex )[ 0 ] + translationRealOffset[ 0 ]; + final double translationY = gridCellRealDimensions[ 1 ] * positions.get( finalPositionIndex )[ 1 ] + translationRealOffset[ 1 ]; + + TransformedGridSourceTransformer.translate( sourceNameToSourceAndConverter, transformedSourceNames, null, centerAtOrigin, translationX, translationY ); addTransformedSources( sourceNameToSourceAndConverter, transformedSourceNames ); // if there are any, also transform contained sources @@ -160,7 +156,7 @@ public List< String > getSources() { mergedGridSource = new MergedGridSource( gridSources, positions, mergedGridSourceName, TransformedGridSourceTransformer.RELATIVE_CELL_MARGIN, encodeSource ); - final VolatileSource< ?, ? > volatileMergedGridSource = new VolatileSource<>( mergedGridSource, MoBIE.sharedQueue ); + final VolatileSource< ?, ? > volatileMergedGridSource = new VolatileSource<>( mergedGridSource, ThreadUtils.sharedQueue ); final SourceAndConverter< ? > volatileSourceAndConverter = new SourceAndConverter( volatileMergedGridSource, volatileConverter ); diff --git a/src/main/java/org/embl/mobie/viewer/transform/TransformedGridSourceTransformer.java b/src/main/java/org/embl/mobie/viewer/transform/TransformedGridSourceTransformer.java index e32873ecd..1eaf7bb38 100644 --- a/src/main/java/org/embl/mobie/viewer/transform/TransformedGridSourceTransformer.java +++ b/src/main/java/org/embl/mobie/viewer/transform/TransformedGridSourceTransformer.java @@ -2,8 +2,7 @@ import bdv.viewer.SourceAndConverter; import de.embl.cba.tables.Logger; -import org.embl.mobie.viewer.MoBIE; -import org.embl.mobie.viewer.MoBIEUtils; +import org.embl.mobie.viewer.ThreadUtils; import org.embl.mobie.viewer.playground.SourceAffineTransformer; import net.imglib2.realtransform.AffineTransform3D; @@ -12,6 +11,7 @@ import java.util.Map; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; +import java.util.concurrent.Future; public class TransformedGridSourceTransformer extends AbstractSourceTransformer { @@ -47,27 +47,20 @@ public List< String > getSources() private void transform( Map< String, SourceAndConverter< ? > > sourceNameToSourceAndConverter, double[] cellRealDimensions ) { - final long start = System.currentTimeMillis(); - - final int nThreads = MoBIE.N_THREADS; - final ExecutorService executorService = Executors.newFixedThreadPool( nThreads ); - final int numGridPositions = sources.size(); + final ArrayList< Future< ? > > futures = ThreadUtils.getFutures(); for ( int gridIndex = 0; gridIndex < numGridPositions; gridIndex++ ) { int finalGridIndex = gridIndex; - executorService.execute( () -> { + futures.add( ThreadUtils.executorService.submit( () -> { if ( sourceNamesAfterTransform != null ) translate( sourceNameToSourceAndConverter, sources.get( finalGridIndex ), sourceNamesAfterTransform.get( finalGridIndex ), centerAtOrigin, cellRealDimensions[ 0 ] * positions.get( finalGridIndex )[ 0 ], cellRealDimensions[ 1 ] * positions.get( finalGridIndex )[ 1 ] ); else translate( sourceNameToSourceAndConverter, sources.get( finalGridIndex ), null, centerAtOrigin, cellRealDimensions[ 0 ] * positions.get( finalGridIndex )[ 0 ], cellRealDimensions[ 1 ] * positions.get( finalGridIndex )[ 1 ] ); - } ); + } ) ); } - - MoBIEUtils.waitUntilFinishedAndShutDown( executorService ); - - System.out.println( "Transformed " + sourceNameToSourceAndConverter.size() + " image source(s) in " + (System.currentTimeMillis() - start) + " ms, using " + nThreads + " thread(s)." ); + ThreadUtils.waitUntilFinished( futures ); } public static void translate( Map< String, SourceAndConverter< ? > > sourceNameToSourceAndConverter, List< String > sourceNames, List< String > sourceNamesAfterTransform, boolean centerAtOrigin, double translationX, double translationY ) diff --git a/src/test/java/projects/OpenRemoteCovidScreen.java b/src/test/java/projects/OpenRemoteCovidScreen.java index 93f52460d..5ad585e6b 100644 --- a/src/test/java/projects/OpenRemoteCovidScreen.java +++ b/src/test/java/projects/OpenRemoteCovidScreen.java @@ -16,7 +16,7 @@ public static void main( String[] args ) try { new MoBIE("https://github.com/mobie/covid-if-project", - MoBIESettings.settings().gitProjectBranch( "main" ).imageDataFormat( ImageDataFormat.OmeZarrS3 ) ); + MoBIESettings.settings().gitProjectBranch( "main" ).imageDataFormat( ImageDataFormat.OmeZarrS3 ).view( "single_image" ) ); } catch (IOException e) { e.printStackTrace(); } diff --git a/src/test/java/projects/OpenRemotePlankton.java b/src/test/java/projects/OpenRemotePlankton.java new file mode 100644 index 000000000..fd5b2ac65 --- /dev/null +++ b/src/test/java/projects/OpenRemotePlankton.java @@ -0,0 +1,21 @@ +package projects; + +import net.imagej.ImageJ; +import org.embl.mobie.viewer.MoBIE; +import org.embl.mobie.viewer.MoBIESettings; + +import java.io.IOException; + +public class OpenRemotePlankton +{ + public static void main( String[] args ) + { + final ImageJ imageJ = new ImageJ(); + imageJ.ui().showUI(); + try { + new MoBIE("https://s3.embl.de/plankton-fibsem", MoBIESettings.settings()); + } catch (IOException e) { + e.printStackTrace(); + } + } +} diff --git a/src/test/java/tests/TestRemotePlanktonS3.java b/src/test/java/tests/TestRemotePlanktonS3.java new file mode 100644 index 000000000..a3111de1c --- /dev/null +++ b/src/test/java/tests/TestRemotePlanktonS3.java @@ -0,0 +1,39 @@ +package tests; + +import net.imagej.ImageJ; +import org.embl.mobie.viewer.MoBIE; +import org.embl.mobie.viewer.MoBIESettings; +import org.embl.mobie.viewer.display.SegmentationSourceDisplay; +import org.embl.mobie.viewer.source.ImageDataFormat; +import org.embl.mobie.viewer.view.View; +import org.embl.mobie.viewer.view.additionalviews.AdditionalViewsLoader; +import org.junit.jupiter.api.Test; + +import java.io.IOException; +import java.util.Map; + +public class TestRemotePlanktonS3 +{ + public static void main( String[] args ) throws IOException + { + new TestRemotePlanktonS3().run(); + } + + @Test + public void run() throws IOException + { + final ImageJ imageJ = new ImageJ(); + imageJ.ui().showUI(); + + // This is special as it does not go via github but + // all information is on S3 + final MoBIE moBIE = new MoBIE("https://s3.embl.de/plankton-fibsem", MoBIESettings.settings()); + + // Check all views + final Map< String, View > views = moBIE.getViews(); + for ( View view : views.values() ) + { + moBIE.getViewManager().show( view ); + } + } +} diff --git a/src/test/java/tests/TestRemoteZebrafish.java b/src/test/java/tests/TestRemoteZebrafish.java index d9bb2d69c..69fc46aa5 100644 --- a/src/test/java/tests/TestRemoteZebrafish.java +++ b/src/test/java/tests/TestRemoteZebrafish.java @@ -1,17 +1,14 @@ package tests; import net.imagej.ImageJ; -import net.imagej.patcher.LegacyInjector; import org.embl.mobie.viewer.MoBIE; import org.embl.mobie.viewer.MoBIESettings; import org.embl.mobie.viewer.display.SegmentationSourceDisplay; import org.embl.mobie.viewer.source.ImageDataFormat; -import org.embl.mobie.viewer.view.View; import org.embl.mobie.viewer.view.additionalviews.AdditionalViewsLoader; import org.junit.jupiter.api.Test; import java.io.IOException; -import java.util.Map; public class TestRemoteZebrafish {