diff --git a/build.gradle.kts b/build.gradle.kts index b0ab7430..bf00d29f 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -4,7 +4,7 @@ import java.net.URL import sciview.* plugins { - val ktVersion = "1.8.20" + val ktVersion = "1.8.22" val dokkaVersion = "1.8.20" java @@ -25,43 +25,50 @@ java { } repositories { + if(project.properties["useMavenLocal"] == "true") { + logger.warn("Using local Maven repository as source") + mavenLocal() + } + mavenCentral() + maven("https://oss.sonatype.org/content/repositories/graphicsscenery-1222") maven("https://maven.scijava.org/content/groups/public") + mavenLocal() } dependencies { - val ktVersion = "1.8.20" - implementation(platform("org.scijava:pom-scijava:31.1.0")) + val ktVersion = "1.8.22" + implementation(platform("org.scijava:pom-scijava:35.1.1")) // Graphics dependencies - annotationProcessor("org.scijava:scijava-common:2.90.0") - kapt("org.scijava:scijava-common:2.90.0") { // MANUAL version increment + // Attention! Manual version increment necessary here! + val scijavaCommonVersion = "2.94.1" + annotationProcessor("org.scijava:scijava-common:$scijavaCommonVersion") + kapt("org.scijava:scijava-common:$scijavaCommonVersion") { exclude("org.lwjgl") } - val sceneryVersion = "0.8.0" + val sceneryVersion = "0.9.0" api("graphics.scenery:scenery:$sceneryVersion") { version { strictly(sceneryVersion) } exclude("org.biojava.thirdparty", "forester") exclude("null", "unspecified") } - implementation("com.fasterxml.jackson.core:jackson-databind:2.13.4.2") - implementation("com.fasterxml.jackson.module:jackson-module-kotlin:2.13.4") - implementation("com.fasterxml.jackson.dataformat:jackson-dataformat-yaml:2.13.4") - implementation("org.msgpack:jackson-dataformat-msgpack:0.9.3") implementation("net.java.dev.jna:jna-platform:5.11.0") - implementation("net.clearvolume:cleargl") implementation("org.janelia.saalfeldlab:n5") implementation("org.janelia.saalfeldlab:n5-imglib2") implementation("org.apache.logging.log4j:log4j-api:2.20.0") implementation("org.apache.logging.log4j:log4j-1.2-api:2.20.0") - implementation("com.formdev:flatlaf:2.6") + implementation("com.formdev:flatlaf") // SciJava dependencies + implementation("org.yaml:snakeyaml") { + version { strictly("1.33") } + } implementation("org.scijava:scijava-common") implementation("org.scijava:ui-behaviour") implementation("org.scijava:script-editor") @@ -84,8 +91,9 @@ dependencies { implementation("io.scif:scifio-bf-compat") // ImgLib2 dependencies - implementation("net.imglib2:imglib2") + implementation("net.imglib2:imglib2:6.2.0") implementation("net.imglib2:imglib2-roi") + implementation("net.imglib2:imglib2-algorithm:0.14.0") // XDG support implementation("dev.dirs:directories:26") @@ -110,6 +118,14 @@ dependencies { // implementation(n5.imglib2) implementation("org.janelia.saalfeldlab:n5") implementation("org.janelia.saalfeldlab:n5-hdf5") + implementation("org.janelia.saalfeldlab:n5-ij:3.2.4-SNAPSHOT") + implementation("org.janelia.saalfeldlab:n5-imglib2:5.0.0") + implementation("org.janelia.saalfeldlab:n5-viewer_fiji:5.3.0") + //implementation("com.github.saalfeldlab:n5-viewer:ec0b177") + implementation("org.janelia.saalfeldlab:n5-aws-s3") + implementation("org.janelia.saalfeldlab:n5-google-cloud") + implementation("org.janelia.saalfeldlab:n5-blosc") + implementation("org.janelia.saalfeldlab:n5-zarr") implementation("sc.fiji:spim_data") implementation(platform(kotlin("bom"))) @@ -117,8 +133,8 @@ dependencies { testImplementation(kotlin("test-junit")) testImplementation("org.slf4j:slf4j-simple:1.7.36") - implementation("sc.fiji:bigdataviewer-core") - implementation("sc.fiji:bigdataviewer-vistools") + implementation("sc.fiji:bigdataviewer-core:10.4.7") + implementation("sc.fiji:bigdataviewer-vistools:1.0.0-beta-32") // OME implementation("ome:formats-bsd") @@ -435,5 +451,7 @@ artifacts { archives(dokkaHtmlJar) } + + java.withSourcesJar() diff --git a/src/main/java/sc/iview/commands/demo/advanced/OpenOrganelleDemo.java b/src/main/java/sc/iview/commands/demo/advanced/OpenOrganelleDemo.java new file mode 100644 index 00000000..70e12a85 --- /dev/null +++ b/src/main/java/sc/iview/commands/demo/advanced/OpenOrganelleDemo.java @@ -0,0 +1,505 @@ +/*- + * #%L + * Scenery-backed 3D visualization package for ImageJ. + * %% + * Copyright (C) 2016 - 2021 SciView 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 sc.iview.commands.demo.advanced; + +import bdv.BigDataViewer; +import bdv.cache.SharedQueue; +import bdv.tools.brightness.ConverterSetup; +import bdv.util.volatiles.VolatileTypeMatcher; +import bdv.util.volatiles.VolatileViews; +import bdv.viewer.Source; +import bdv.viewer.SourceAndConverter; +import com.intellij.util.Consumer; +import graphics.scenery.attribute.material.Material; +import graphics.scenery.volumes.Volume; +import io.scif.services.DatasetIOService; +import net.imagej.mesh.Mesh; +import net.imagej.mesh.Meshes; +import net.imagej.ops.OpService; +import net.imglib2.Cursor; +import net.imglib2.RandomAccessibleInterval; +import net.imglib2.RealPoint; +import net.imglib2.Volatile; +import net.imglib2.algorithm.blocks.BlockAlgoUtils; +import net.imglib2.algorithm.blocks.convert.Convert; +import net.imglib2.algorithm.labeling.ConnectedComponents; +import net.imglib2.blocks.PrimitiveBlocks; +import net.imglib2.cache.Cache; +import net.imglib2.cache.CacheLoader; +import net.imglib2.cache.LoaderCache; +import net.imglib2.cache.img.CachedCellImg; +import net.imglib2.cache.img.LoadedCellCacheLoader; +import net.imglib2.cache.img.RandomAccessibleCacheLoader; +import net.imglib2.cache.ref.SoftRefLoaderCache; +import net.imglib2.cache.volatiles.CacheHints; +import net.imglib2.cache.volatiles.LoadingStrategy; +import net.imglib2.converter.Converters; +import net.imglib2.img.array.ArrayImgs; +import net.imglib2.img.basictypeaccess.AccessFlags; +import net.imglib2.img.basictypeaccess.ArrayDataAccessFactory; +import net.imglib2.img.basictypeaccess.volatiles.VolatileByteAccess; +import net.imglib2.img.basictypeaccess.volatiles.array.VolatileByteArray; +import net.imglib2.img.basictypeaccess.volatiles.array.VolatileShortArray; +import net.imglib2.img.cell.Cell; +import net.imglib2.realtransform.AffineTransform3D; +import net.imglib2.roi.labeling.ImgLabeling; +import net.imglib2.roi.labeling.LabelRegion; +import net.imglib2.roi.labeling.LabelRegions; +import net.imglib2.type.NativeType; +import net.imglib2.type.numeric.ARGBType; +import net.imglib2.type.numeric.NumericType; +import net.imglib2.type.numeric.RealType; +import net.imglib2.type.numeric.integer.IntType; +import net.imglib2.type.numeric.integer.UnsignedByteType; +import net.imglib2.type.numeric.integer.UnsignedShortType; +import net.imglib2.type.volatiles.VolatileARGBType; +import net.imglib2.type.volatiles.VolatileUnsignedShortType; +import net.imglib2.util.Util; +import net.imglib2.view.Views; +import org.janelia.saalfeldlab.n5.DataType; +import org.janelia.saalfeldlab.n5.DatasetAttributes; +import org.janelia.saalfeldlab.n5.N5Reader; +import org.janelia.saalfeldlab.n5.bdv.MultiscaleDatasets; +import org.janelia.saalfeldlab.n5.bdv.N5Source; +import org.janelia.saalfeldlab.n5.bdv.N5ViewerTreeCellRenderer; +import org.janelia.saalfeldlab.n5.ij.N5Factory; +import org.janelia.saalfeldlab.n5.ij.N5Importer; +import org.janelia.saalfeldlab.n5.imglib2.N5CacheLoader; +import org.janelia.saalfeldlab.n5.imglib2.N5Utils; +import org.janelia.saalfeldlab.n5.metadata.*; +import org.janelia.saalfeldlab.n5.metadata.canonical.CanonicalMultichannelMetadata; +import org.janelia.saalfeldlab.n5.metadata.canonical.CanonicalMultiscaleMetadata; +import org.janelia.saalfeldlab.n5.metadata.canonical.CanonicalSpatialMetadata; +import org.janelia.saalfeldlab.n5.ui.DataSelection; +import org.janelia.saalfeldlab.n5.ui.DatasetSelectorDialog; +import org.joml.Vector3f; +import org.scijava.command.Command; +import org.scijava.command.CommandService; +import org.scijava.log.LogService; +import org.scijava.plugin.Menu; +import org.scijava.plugin.Parameter; +import org.scijava.plugin.Plugin; +import sc.iview.SciView; +import sc.iview.process.MeshConverter; + +import java.io.IOException; +import java.util.*; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.function.Function; +import java.util.function.Supplier; + +import static bdv.BigDataViewer.createConverterToARGB; +import static bdv.BigDataViewer.wrapWithTransformedSource; +import static org.janelia.saalfeldlab.n5.bdv.N5ViewerCreator.n5vGroupParsers; +import static org.janelia.saalfeldlab.n5.bdv.N5ViewerCreator.n5vParsers; +import static sc.iview.commands.MenuWeights.*; + +/** + * OpenOrganelle demo + * + * This borrows heavily from n5-viewer and BVV code. + * + * @author Kyle Harrington + */ +@Plugin(type = Command.class, label = "Open Organelle", menuRoot = "SciView", // + menu = { @Menu(label = "Demo", weight = DEMO), // + @Menu(label = "Advanced", weight = DEMO_ADVANCED), // + @Menu(label = "Open Organelle (Java)", weight = DEMO_ADVANCED_SEGMENTATION) }) +public class OpenOrganelleDemo implements Command { + + @Parameter + private DatasetIOService datasetIO; + + @Parameter + private LogService log; + + @Parameter + private OpService ops; + + @Parameter + private SciView sciView; + + @Parameter + private int numSegments = 20; + + private Random rng = new Random(); + + private static String containerName = "s3://janelia-cosem-datasets/jrc_mus-kidney/jrc_mus-kidney.n5"; + + @Override + public void run() { + openN5(sciView); + } + + static public void openN5(SciView sciView) { + ExecutorService exec = Executors.newFixedThreadPool( ij.Prefs.getThreads() ); + final DatasetSelectorDialog dialog = new DatasetSelectorDialog( + new N5Importer.N5ViewerReaderFun(), + x -> "", + containerName, + n5vGroupParsers, + n5vParsers); + + dialog.setLoaderExecutor( exec ); + +// dialog.setRecursiveFilterCallback( new N5ViewerDatasetFilter() ); + dialog.setContainerPathUpdateCallback( x -> containerName = x ); + dialog.setTreeRenderer( new N5ViewerTreeCellRenderer( false ) ); + + dialog.run( selection -> { + addDataSelection(selection, sciView); + } ); + } + + /** + * Thanks Tobi! + * @param rai + * @return + */ + static CachedCellImg< UnsignedShortType, ? > convert( + RandomAccessibleInterval< UnsignedByteType > rai ) + { + final int[] cellDimensions = { 64, 64, 64 }; + final PrimitiveBlocks< UnsignedByteType > blocks = PrimitiveBlocks.of( rai ); + final CachedCellImg< UnsignedShortType, ? > img = BlockAlgoUtils.cellImg( + blocks, + Convert.convert( new UnsignedByteType(), new UnsignedShortType() ), + new UnsignedShortType(), + rai.dimensionsAsLongArray(), + cellDimensions + ); + return img; + // return VolatileViews.wrapAsVolatile( img ); + } + + static public < T extends NumericType< T > & NativeType< T >, + V extends Volatile< T > & NumericType< V >> void buildN5Sources( + final N5Reader n5, + final List selectedMetadata, + final SharedQueue sharedQueue, + final List< ConverterSetup > converterSetups, + final List< SourceAndConverter< T > > sourcesAndConverters, + final List> sources, + final List> volatileSources) throws IOException + { + final ArrayList> additionalSources = new ArrayList<>(); + + int i; + for ( i = 0; i < selectedMetadata.size(); ++i ) + { + String[] datasetsToOpen = null; + AffineTransform3D[] transforms = null; + + final N5Metadata metadata = selectedMetadata.get( i ); + final String srcName = metadata.getName(); + if (metadata instanceof N5SingleScaleMetadata) { + final N5SingleScaleMetadata singleScaleDataset = (N5SingleScaleMetadata) metadata; + final String[] tmpDatasets= new String[]{ singleScaleDataset.getPath() }; + final AffineTransform3D[] tmpTransforms = new AffineTransform3D[]{ singleScaleDataset.spatialTransform3d() }; + + final MultiscaleDatasets msd = MultiscaleDatasets.sort( tmpDatasets, tmpTransforms ); + datasetsToOpen = msd.getPaths(); + transforms = msd.getTransforms(); + } else if (metadata instanceof N5MultiScaleMetadata) { + final N5MultiScaleMetadata multiScaleDataset = (N5MultiScaleMetadata) metadata; + datasetsToOpen = multiScaleDataset.getPaths(); + transforms = multiScaleDataset.spatialTransforms3d(); + } else if (metadata instanceof N5CosemMetadata ) { + final N5CosemMetadata singleScaleCosemDataset = (N5CosemMetadata) metadata; + datasetsToOpen = new String[]{ singleScaleCosemDataset.getPath() }; + transforms = new AffineTransform3D[]{ singleScaleCosemDataset.spatialTransform3d() }; + } else if (metadata instanceof CanonicalSpatialMetadata) { + final CanonicalSpatialMetadata canonicalDataset = (CanonicalSpatialMetadata) metadata; + datasetsToOpen = new String[]{ canonicalDataset.getPath() }; + transforms = new AffineTransform3D[]{ canonicalDataset.getSpatialTransform().spatialTransform3d() }; + } else if (metadata instanceof N5CosemMultiScaleMetadata ) { + final N5CosemMultiScaleMetadata multiScaleDataset = (N5CosemMultiScaleMetadata) metadata; + final MultiscaleDatasets msd = MultiscaleDatasets.sort( multiScaleDataset.getPaths(), multiScaleDataset.spatialTransforms3d() ); + datasetsToOpen = msd.getPaths(); + transforms = msd.getTransforms(); + } else if (metadata instanceof CanonicalMultiscaleMetadata) { + final CanonicalMultiscaleMetadata multiScaleDataset = (CanonicalMultiscaleMetadata) metadata; + final MultiscaleDatasets msd = MultiscaleDatasets.sort( multiScaleDataset.getPaths(), multiScaleDataset.spatialTransforms3d() ); + datasetsToOpen = msd.getPaths(); + transforms = msd.getTransforms(); + } + else if( metadata instanceof N5DatasetMetadata ) { + final List> addTheseSources = MetadataSource.buildMetadataSources(n5, (N5DatasetMetadata)metadata); + if( addTheseSources != null ) + additionalSources.addAll(addTheseSources); + } + else { + datasetsToOpen = new String[]{ metadata.getPath() }; + transforms = new AffineTransform3D[] { new AffineTransform3D() }; + } + + if( datasetsToOpen == null || datasetsToOpen.length == 0 ) + continue; + + @SuppressWarnings( "rawtypes" ) + final RandomAccessibleInterval[] images = new RandomAccessibleInterval[datasetsToOpen.length]; + for ( int s = 0; s < images.length; ++s ) + { + CachedCellImg vimg = convert(N5Utils.openVolatile(n5, datasetsToOpen[s])); + //final CachedCellImg unsignedShortVimg = Converters.convert(vimg, (i, o) -> o.set(i.getRealDouble())); + + if( vimg.numDimensions() == 2 ) + { + images[ s ] = Views.addDimension(vimg, 0, 0); + } + else + { + images[ s ] = vimg; + } + } + + final RandomAccessibleInterval[] vimages = new RandomAccessibleInterval[images.length]; + for (int s = 0; s < images.length; ++s) { + final CacheHints cacheHints = new CacheHints(LoadingStrategy.VOLATILE, 0, true); + vimages[s] = VolatileViews.wrapAsVolatile(images[s], sharedQueue, cacheHints); + } + // TODO: Ideally, the volatile views should use a caching strategy + // where blocks are enqueued with reverse resolution level as + // priority. However, this would require to predetermine the number + // of resolution levels, which would man a lot of duplicated code + // for analyzing selectedMetadata. Instead, wait until SharedQueue + // supports growing numPriorities, then revisit. + // See https://github.com/imglib/imglib2-cache/issues/18. + // Probably it should look like this: +// sharedQueue.ensureNumPriorities(images.length); +// for (int s = 0; s < images.length; ++s) { +// final int priority = images.length - 1 - s; +// final CacheHints cacheHints = new CacheHints(LoadingStrategy.BUDGETED, priority, false); +// vimages[s] = VolatileViews.wrapAsVolatile(images[s], sharedQueue, cacheHints); +// } + + @SuppressWarnings("unchecked") + final T type = (T) Util.getTypeFromInterval(images[0]); + final N5Source source = new N5Source<>( + type, + srcName, + images, + transforms); + + @SuppressWarnings("unchecked") + final V volatileType = (V) VolatileTypeMatcher.getVolatileTypeForType(type); + final N5Source volatileSource = new N5Source<>( + volatileType, + srcName, + vimages, + transforms); + + sources.add(source); + volatileSources.add(volatileSource); + + addSourceToListsGenericType(source, volatileSource, i + 1, converterSetups, sourcesAndConverters); + } + + for( final MetadataSource src : additionalSources ) { +// if( src.numTimePoints() > numTimepoints ) +// numTimepoints = src.numTimePoints(); + + addSourceToListsGenericType( src, i + 1, converterSetups, sourcesAndConverters ); + } + } + + /** + * Add the given {@code source} to the lists of {@code converterSetups} + * (using specified {@code setupId}) and {@code sources}. For this, the + * {@code source} is wrapped with an appropriate Converter to + * {@link ARGBType} and into a TransformedSource. + * + * @param source + * source to add. + * @param setupId + * id of the new source for use in {@code SetupAssignments}. + * @param converterSetups + * list of {@link ConverterSetup}s to which the source should be + * added. + * @param sources + * list of {@link SourceAndConverter}s to which the source should + * be added. + */ + @SuppressWarnings( { "rawtypes", "unchecked" } ) + private static < T > void addSourceToListsGenericType( + final Source< T > source, + final int setupId, + final List< ConverterSetup > converterSetups, + final List< SourceAndConverter< T > > sources ) + { + addSourceToListsGenericType( source, null, setupId, converterSetups, sources ); + } + + /** + * Add the given {@code source} to the lists of {@code converterSetups} + * (using specified {@code setupId}) and {@code sources}. For this, the + * {@code source} is wrapped with an appropriate Converter to + * {@link ARGBType} and into a TransformedSource. + * + * @param source + * source to add. + * @param setupId + * id of the new source for use in {@code SetupAssignments}. + * @param converterSetups + * list of {@link ConverterSetup}s to which the source should be + * added. + * @param sources + * list of {@link SourceAndConverter}s to which the source should + * be added. + */ + @SuppressWarnings( { "rawtypes", "unchecked" } ) + private static < T, V extends Volatile< T > > void addSourceToListsGenericType( + final Source< T > source, + final Source< V > volatileSource, + final int setupId, + final List< ConverterSetup > converterSetups, + final List< SourceAndConverter< T > > sources ) + { + final T type = source.getType(); + if ( type instanceof RealType || type instanceof ARGBType || type instanceof VolatileARGBType) + addSourceToListsNumericType( ( Source ) source, ( Source ) volatileSource, setupId, converterSetups, ( List ) sources ); + else + throw new IllegalArgumentException( "Unknown source type. Expected RealType, ARGBType, or VolatileARGBType" ); + } + + /** + * Add the given {@code source} to the lists of {@code converterSetups} + * (using specified {@code setupId}) and {@code sources}. For this, the + * {@code source} is wrapped with an appropriate Converter to + * {@link ARGBType} and into a TransformedSource. + * + * @param source + * source to add. + * @param volatileSource + * corresponding volatile source. + * @param setupId + * id of the new source for use in {@code SetupAssignments}. + * @param converterSetups + * list of {@link ConverterSetup}s to which the source should be + * added. + * @param sources + * list of {@link SourceAndConverter}s to which the source should + * be added. + */ + private static < T extends NumericType< T >, V extends Volatile< T > & NumericType< V > > void addSourceToListsNumericType( + final Source< T > source, + final Source< V > volatileSource, + final int setupId, + final List< ConverterSetup > converterSetups, + final List< SourceAndConverter< T > > sources ) + { + final SourceAndConverter< V > vsoc = ( volatileSource == null ) + ? null + : new SourceAndConverter<>( volatileSource, createConverterToARGB( volatileSource.getType() ) ); + final SourceAndConverter< T > soc = new SourceAndConverter<>( source, createConverterToARGB( source.getType() ), vsoc ); + final SourceAndConverter< T > tsoc = wrapWithTransformedSource( soc ); + + converterSetups.add( BigDataViewer.createConverterSetup( tsoc, setupId ) ); + sources.add( tsoc ); + } + + static private < T extends NumericType< T > & NativeType< T >, + V extends Volatile< T > & NumericType< V >> void addDataSelection(DataSelection dataSelection, SciView sciView) { + // This function should do the same type of job as N5Viewer() + SharedQueue sharedQueue = new SharedQueue(Math.max(1, Runtime.getRuntime().availableProcessors() / 2)); + + final List converterSetups = new ArrayList<>(); + + final List> sourcesAndConverters = new ArrayList<>(); + + final List selected = new ArrayList<>(); + for( final N5Metadata meta : dataSelection.metadata ) + { + if( meta instanceof N5ViewerMultichannelMetadata) + { + final N5ViewerMultichannelMetadata mc = (N5ViewerMultichannelMetadata)meta; + for( final MultiscaleMetadata m : mc.getChildrenMetadata() ) + selected.add( m ); + } + else if ( meta instanceof CanonicalMultichannelMetadata) + { + final CanonicalMultichannelMetadata mc = (CanonicalMultichannelMetadata)meta; + for( final N5Metadata m : mc.getChildrenMetadata() ) + selected.add( m ); + } + else + selected.add( meta ); + } + + final List> sources = new ArrayList<>(); + final List> volatileSources = new ArrayList<>(); + + try { + buildN5Sources(dataSelection.n5, selected, sharedQueue, converterSetups, sourcesAndConverters, sources, volatileSources); + } catch (IOException e) { + throw new RuntimeException(e); + } + + /** + * Notes: + * + * MultiResolutionStack3DImp + * https://github.com/bigdataviewer/bigvolumeviewer-core/blob/9f6a7d66dc4d92a7d9e011a680fdc5facd4706a0/src/main/java/bvv/core/multires/SourceStacks.java#L101 + * + * TileAccess + * https://github.com/bigdataviewer/bigvolumeviewer-core/blob/9f6a7d66dc4d92a7d9e011a680fdc5facd4706a0/src/main/java/bvv/core/blocks/TileAccess.java#L133 + * + * Only UnsignedShortType is supported + * + * https://javadoc.scijava.org/ImgLib2/net/imglib2/cache/img/LoadedCellCacheLoader.html + * + */ + + + + // use scenery's Volume.fromSpimData to open + Volume v = sciView.addVolume( + sourcesAndConverters, + converterSetups, + 1, + containerName); + + } + + + public static void main(String... args) throws Exception { + SciView sv = SciView.create(); + + // Many functions were made static here out of concern for issue sciview#494 + openN5(sv); + // TODO pickup here, exit 134 this might be an OOM + +// CommandService command = sv.getScijavaContext().getService(CommandService.class); +// +// HashMap argmap = new HashMap(); +// +// command.run(OpenOrganelleDemo.class, true, argmap); + } +} diff --git a/src/main/java/sc/iview/commands/demo/animation/SceneRiggingDemo.java b/src/main/java/sc/iview/commands/demo/animation/SceneRiggingDemo.java index 853a17e1..bfc506f9 100644 --- a/src/main/java/sc/iview/commands/demo/animation/SceneRiggingDemo.java +++ b/src/main/java/sc/iview/commands/demo/animation/SceneRiggingDemo.java @@ -113,7 +113,7 @@ public void run() { sciView.centerOnNode( sciView.getActiveNode() ); for( PointLight light : sciView.getLights() ) { - Icosphere s = new Icosphere(1f, 1); + Icosphere s = new Icosphere(1f, 1, false); s.getMaterial().setDiffuse(light.getEmissionColor()); s.getMaterial().setAmbient(light.getEmissionColor()); s.getMaterial().setSpecular(light.getEmissionColor()); diff --git a/src/main/java/sc/iview/commands/edit/add/AddOrientationCompass.java b/src/main/java/sc/iview/commands/edit/add/AddOrientationCompass.java index ad8ddb81..74d524d0 100644 --- a/src/main/java/sc/iview/commands/edit/add/AddOrientationCompass.java +++ b/src/main/java/sc/iview/commands/edit/add/AddOrientationCompass.java @@ -86,7 +86,7 @@ private Node makeAxis( float axisLength, float angleX, float angleY, float angle return null; }); - Icosphere axisCap = new Icosphere(AXESBARRADIUS, 2); + Icosphere axisCap = new Icosphere(AXESBARRADIUS, 2, false); axisCap.ifSpatial(spatial -> { spatial.setPosition(new Vector3f(0, axisLength, 0)); return null; diff --git a/src/main/java/sc/iview/io/N5IO.java b/src/main/java/sc/iview/io/N5IO.java index e7f44569..1c8407c2 100644 --- a/src/main/java/sc/iview/io/N5IO.java +++ b/src/main/java/sc/iview/io/N5IO.java @@ -29,6 +29,7 @@ package sc.iview.io; import bdv.util.AxisOrder; +import bvv.core.VolumeViewerOptions; import graphics.scenery.Group; import graphics.scenery.Node; import graphics.scenery.primitives.PointCloud; @@ -46,7 +47,6 @@ import sc.iview.SciView; import sc.iview.SciViewService; import sc.iview.process.MeshConverter; -import tpietzsch.example2.VolumeViewerOptions; import java.io.File; import java.io.IOException; diff --git a/src/main/java/sc/iview/ui/REPLPane.java b/src/main/java/sc/iview/ui/REPLPane.java index 76376259..ae617426 100644 --- a/src/main/java/sc/iview/ui/REPLPane.java +++ b/src/main/java/sc/iview/ui/REPLPane.java @@ -75,7 +75,7 @@ public REPLPane(final Context context) { final JScrollPane outputScroll = new JScrollPane(output); outputScroll.setPreferredSize(new Dimension(440, 400)); - repl = new ScriptREPL(context, output.getOutputStream()); + repl = new ScriptREPL(context, "Python (Jython)", output.getOutputStream()); repl.initialize(); final Writer writer = output.getOutputWriter(); @@ -89,7 +89,7 @@ public REPLPane(final Context context) { //prompt = new REPLEditor(repl, vars, output); prompt = new REPLEditor(repl, null, output); context.inject(prompt); - prompt.setREPLLanguage("Python"); + final JScrollPane promptScroll = new JScrollPane(prompt); final JPanel bottomPane = new JPanel(); diff --git a/src/main/kotlin/sc/iview/SciView.kt b/src/main/kotlin/sc/iview/SciView.kt index 3c404ebf..c27ad3ce 100644 --- a/src/main/kotlin/sc/iview/SciView.kt +++ b/src/main/kotlin/sc/iview/SciView.kt @@ -37,11 +37,11 @@ import bdv.util.RandomAccessibleIntervalSource4D import bdv.util.volatiles.VolatileView import bdv.viewer.Source import bdv.viewer.SourceAndConverter +import bvv.core.VolumeViewerOptions import dev.dirs.ProjectDirectories import graphics.scenery.* import graphics.scenery.Scene.RaycastResult import graphics.scenery.backends.Renderer -import graphics.scenery.backends.opengl.OpenGLRenderer import graphics.scenery.backends.vulkan.VulkanRenderer import graphics.scenery.controls.InputHandler import graphics.scenery.controls.OpenVRHMD @@ -110,7 +110,6 @@ import sc.iview.ui.CustomPropertyUI import sc.iview.ui.MainWindow import sc.iview.ui.SwingMainWindow import sc.iview.ui.TaskManager -import tpietzsch.example2.VolumeViewerOptions import java.awt.event.WindowListener import java.io.IOException import java.net.URL @@ -388,10 +387,9 @@ class SciView : SceneryBase, CalibratedRealInterval { versionString = versionString.substring(0, 5) val launcherVersion = Version(versionString) val nonWorkingVersion = Version("4.0.5") - if (launcherVersion.compareTo(nonWorkingVersion) <= 0 + if (launcherVersion <= nonWorkingVersion && !java.lang.Boolean.parseBoolean(System.getProperty("sciview.DisableLauncherVersionCheck", "false"))) { - logger.info("imagej-launcher version smaller or equal to non-working version ($versionString vs. 4.0.5), disabling Vulkan as rendering backend. Disable check by setting 'scenery.DisableLauncherVersionCheck' system property to 'true'.") - System.setProperty("scenery.Renderer", "OpenGLRenderer") + throw IllegalStateException("imagej-launcher version is outdated, please update your Fiji installation.") } else { logger.info("imagej-launcher version bigger that non-working version ($versionString vs. 4.0.5), all good.") } @@ -1412,6 +1410,26 @@ fun deleteNode(node: Node?, activePublish: Boolean = true) { return v } + /** + * Adds a SourceAndConverter to the scene. + * + * This method actually instantiates the volume. + * + * @param sources The list of SourceAndConverter to add + * @param name Name of the dataset + * @param voxelDimensions Array with voxel dimensions. + * @param Type of the dataset. + * @return THe node corresponding to the volume just added. + */ + @JvmOverloads + @Suppress("UNCHECKED_CAST") + fun > addVolume(sources: List>, + converterSetups: List, + numTimepoints: Int, + name: String = "Volume"): Volume { + return addVolume(sources, ArrayList(converterSetups), numTimepoints, name, *floatArrayOf(1.0f, 1.0f, 1.0f), block={}, colormapName = "Fire.lut") + } + /** * Adds a SourceAndConverter to the scene. * diff --git a/src/main/kotlin/sc/iview/commands/MenuWeights.kt b/src/main/kotlin/sc/iview/commands/MenuWeights.kt index 5e224bdf..c3d9eaef 100644 --- a/src/main/kotlin/sc/iview/commands/MenuWeights.kt +++ b/src/main/kotlin/sc/iview/commands/MenuWeights.kt @@ -115,6 +115,7 @@ object MenuWeights { const val DEMO_ADVANCED_CREMI = 1.0 const val DEMO_ADVANCED_BDVSLICING = 2.0 const val DEMO_ADVANCED_MESHTEXTURE = 3.0 + const val DEMO_ADVANCED_OPENORGANELLE = 4.0 // Help const val HELP_HELP = 0.0 const val HELP_ABOUT = 200.0 diff --git a/src/main/kotlin/sc/iview/commands/demo/advanced/OpenOrganelle.kt b/src/main/kotlin/sc/iview/commands/demo/advanced/OpenOrganelle.kt new file mode 100644 index 00000000..bf542833 --- /dev/null +++ b/src/main/kotlin/sc/iview/commands/demo/advanced/OpenOrganelle.kt @@ -0,0 +1,289 @@ +package sc.iview.commands.demo.advanced + +import bdv.cache.SharedQueue +import bdv.util.volatiles.VolatileTypeMatcher +import bdv.util.volatiles.VolatileViews +import net.imagej.lut.LUTService +import net.imagej.ops.OpService +import net.imglib2.RandomAccessibleInterval +import net.imglib2.Volatile +import net.imglib2.cache.img.CachedCellImg +import net.imglib2.cache.volatiles.CacheHints +import net.imglib2.cache.volatiles.LoadingStrategy +import net.imglib2.type.NativeType +import net.imglib2.type.numeric.ARGBType +import net.imglib2.type.numeric.integer.UnsignedByteType +import net.imglib2.view.Views +import org.janelia.saalfeldlab.n5.N5DatasetDiscoverer +import org.janelia.saalfeldlab.n5.N5Reader +import org.janelia.saalfeldlab.n5.bdv.MultiscaleDatasets +import org.janelia.saalfeldlab.n5.bdv.N5Source +import org.janelia.saalfeldlab.n5.ij.N5Factory +import org.janelia.saalfeldlab.n5.ij.N5Importer +import org.janelia.saalfeldlab.n5.imglib2.N5Utils +import org.janelia.saalfeldlab.n5.metadata.* +import org.joml.Vector4f +import org.scijava.command.Command +import org.scijava.command.CommandService +import org.scijava.log.LogService +import org.scijava.plugin.Menu +import org.scijava.plugin.Parameter +import org.scijava.plugin.Plugin +import org.scijava.ui.UIService +import sc.iview.SciView +import sc.iview.commands.MenuWeights +import java.io.File +import java.util.* +import java.util.concurrent.Executors + + +@Plugin(type = Command::class, + label = "OpenOrganelle demo", + menuRoot = "SciView", + menu = [Menu(label = "Demo", weight = MenuWeights.DEMO), + Menu(label = "Advanced", weight = MenuWeights.DEMO_ADVANCED), + Menu(label = "OpenOrganelle Demo", weight = MenuWeights.DEMO_ADVANCED_OPENORGANELLE)]) +class OpenOrganelle : Command { + @Parameter + private lateinit var ui: UIService + + @Parameter + private lateinit var log: LogService + + @Parameter + private lateinit var ops: OpService + + @Parameter + private lateinit var sciview: SciView + + @Parameter + private lateinit var lut: LUTService + + /* + * TODO + * + * https://openorganelle.janelia.org/datasets/jrc_mus-kidney + * s3://janelia-cosem-datasets/jrc_mus-kidney/jrc_mus-kidney.n5 + * + */ + + internal class DefaultLabelIterator : MutableIterator { + private var i = 0L + override fun hasNext(): Boolean { + return i < Long.MAX_VALUE + } + + override fun next(): Long { + return i++ + } + + override fun remove() { + throw UnsupportedOperationException() + } + } + + /** + * When an object implementing interface `Runnable` is used + * to create a thread, starting the thread causes the object's + * `run` method to be called in that separately executing + * thread. + * + * + * The general contract of the method `run` is that it may + * take any action whatsoever. + * + * @see Thread.run + */ + + override fun run() { + val task = sciview.taskManager.newTask("OpenOrganelle", "Loading dataset") + + val file = File("s3://janelia-cosem-datasets/jrc_mus-kidney/jrc_mus-kidney.n5") + + // Read the EM volume + task.status = "Reading image volume" + //val nai = readMultiscaleN5(file.canonicalPath, "em/fibsem-uint8") + log.info("Start Reading") + val nai = readMultiscaleN5() + + log.info("Done Reading") + + if (nai == null) { + log.error("Could not read data") + return + } +// val raiVolume = nai.third +// val cursor = Views.iterable(raiVolume).localizingCursor() +// while (cursor.hasNext() && cursor.getIntPosition(2) < 50) { +// cursor.fwd() +// cursor.get().set(0) +// } +// + val colormapVolume = lut.loadLUT(lut.findLUTs().get("Grays.lut")) +// val colormapNeurons = lut.loadLUT(lut.findLUTs().get("Fire.lut")) + +// sciview.addVolume(nai) { +// origin = Origin.FrontBottomLeft +// this.spatialOrNull()?.scale = Vector3f(0.08f, 0.08f, 5.0f) +// transferFunction = TransferFunction.ramp(0.3f, 0.1f, 0.1f) +// // min 20, max 180, color map fire +// +// transferFunction.addControlPoint(0.3f, 0.5f) +// transferFunction.addControlPoint(0.8f, 0.01f) +// converterSetups.get(0).setDisplayRange(20.0, 220.0) +// colormap = Colormap.fromColorTable(colormapVolume) +// } + + // Read the labels volume + task.status = "Reading labels volume" + val labels = readMultiscaleN5() + + task.status = "Creating labeling" + task.completion = 10.0f +// val rai = nai.second +// log.info("Got ${nai.first.size} labels") + + // let's extract some neurons here + log.info("Creating labeling ...") + + //val labels = (0..(nai.first.keys.maxOrNull()?.toInt() ?: 1)).toList() +// val labeling = ImgLabeling.fromImageAndLabels(rai, labels) +// log.info("Creating regions...") +// val regions = LabelRegions(labeling) +// log.info("Created ${regions.count()} regions") +// +// val largestNeuronLabels = nai.first.entries.sortedByDescending { p -> p.value }.take(50).shuffled().take(10).map { kv -> kv.key } +// +// log.info("Largest neuron labels are ${largestNeuronLabels.joinToString(",")}") +// +// regions.filter { largestNeuronLabels.contains(it.label.toLong() + 1L) }.forEachIndexed { i, region -> +// log.info("Meshing neuron ${i + 1}/${largestNeuronLabels.size} with label ${region.label}...") +// task.status = "Meshing neuron ${i + 1}/${largestNeuronLabels.size}" +// +// // ui.show(region) +// // Generate the mesh with imagej-ops +// val m: Mesh = Meshes.marchingCubes(region); +// +// log.info("Converting neuron ${i + 1}/${largestNeuronLabels.size} to scenery format...") +// // Convert the mesh into a scenery mesh for visualization +// val mesh = MeshConverter.toScenery(m, false, flipWindingOrder = true) +// sciview.addNode(mesh) { +// spatial().scale = Vector3f(0.01f, 0.01f, 0.06f) +// ifMaterial { +// diffuse = +// colormapNeurons.lookupARGB(0.0, 255.0, kotlin.random.Random.nextDouble(0.0, 255.0)).toRGBColor() +// .xyz() +// roughness = 0.0f +// } +// name = "Neuron $i" +// } +// val completion = 10.0f + ((i + 1) / largestNeuronLabels.size.toFloat()) * 90.0f +// task.completion = completion +// } +// +// task.completion = 100.0f + } + + + + fun readMultiscaleN5(n5root: String = "s3://janelia-cosem-datasets/jrc_mus-kidney/jrc_mus-kidney.n5", + multiscaleBaseDataset: String = "/labels/empanada-mito_seg", + scale: Double = 1.0): CachedCellImg, *>? { + // set up the n5 reader + + log.info("Opening reader") + var n5: N5Reader = N5Factory().openReader(n5root) + // parse the metadata so bdv knows how to display the scales + val importers = Arrays.asList>(*N5Importer.PARSERS) + val groupParsers = Arrays.asList>(*N5Importer.GROUP_PARSERS) + log.info("Dataset discoverer") + val parsers = N5DatasetDiscoverer( + n5, + Executors.newSingleThreadExecutor(), + importers, + groupParsers + ) + + log.info("Dataset discoverer done") + + // get the particular metadata we care above + var root = parsers.discoverAndParseRecursive("") + val multiscaleMetadata = root.childrenList()[0].childrenList()[0].metadata + + var datasetToOpen = multiscaleMetadata.path + + log.info("Opening image") + + val multiScaleDataset = multiscaleMetadata as N5CosemMultiScaleMetadata + val msd = MultiscaleDatasets.sort(multiScaleDataset.paths, multiScaleDataset.spatialTransforms3d()) + var datasetsToOpen = msd.paths + var transforms = msd.transforms + + var sharedQueue = SharedQueue(Math.max(1, Runtime.getRuntime().availableProcessors() / 2)) + + // Open the images + val images: Array?> = arrayOfNulls>(multiScaleDataset.paths.size) + for (s in images.indices) { + val vimg = N5Utils.openVolatile(n5, datasetsToOpen[s]) as RandomAccessibleInterval + if (vimg.numDimensions() == 2) { + images[s] = Views.addDimension(vimg, 0, 0) + } else { + images[s] = vimg + } + } + + // Open the images as volatiles + val vimages = arrayOfNulls>>(images.size) + for (s in 0 until images.size) { + val cacheHints = CacheHints(LoadingStrategy.VOLATILE, 0, true) + vimages[s] = VolatileViews.wrapAsVolatile>(images[s], sharedQueue as bdv.util.volatiles.SharedQueue?, cacheHints) + } + + // Make a source + val source: N5Source = N5Source( + UnsignedByteType(), + "OpenOrganelle", + images, + transforms) + + // Make a volatile source +// var volatileType = VolatileTypeMatcher.getVolatileTypeForType(UnsignedByteType()) +// val volatileSource = N5Source( +// volatileType, +// "Volatile OpenOrganelle", +// vimages, +// transforms) + + //var img = N5Utils.openVolatile(n5, datasetToOpen) + + log.info("Image opened") + + // run n5 viewer + //N5Viewer.exec(new DataSelection( n5, Collections.singletonList( multiscaleMetadata ))); + + + return null + } + + + private fun Int.toRGBColor(): Vector4f { + val a = ARGBType.alpha(this) / 255.0f + val r = ARGBType.red(this) / 255.0f + val g = ARGBType.green(this) / 255.0f + val b = ARGBType.blue(this) / 255.0f + + return Vector4f(r, g, b, a) + } + + + companion object { + @Throws(Exception::class) + @JvmStatic + fun main(args: Array) { + val sv = SciView.create() + val command = sv.scijavaContext!!.getService(CommandService::class.java) + val argmap = HashMap() + command.run(OpenOrganelle::class.java, true, argmap) + } + } +} \ No newline at end of file diff --git a/src/main/kotlin/sc/iview/ui/SwingMainWindow.kt b/src/main/kotlin/sc/iview/ui/SwingMainWindow.kt index 1404b555..2faf5037 100644 --- a/src/main/kotlin/sc/iview/ui/SwingMainWindow.kt +++ b/src/main/kotlin/sc/iview/ui/SwingMainWindow.kt @@ -46,6 +46,7 @@ import java.awt.event.* import java.util.* import javax.script.ScriptException import javax.swing.* +import kotlin.concurrent.thread import kotlin.math.roundToInt /** @@ -58,228 +59,239 @@ class SwingMainWindow(val sciview: SciView) : MainWindow { private val logger by lazyLogger() private var previousSidebarPosition = 0 - var sceneryJPanel: SceneryJPanel + lateinit var sceneryJPanel: SceneryJPanel private set - var mainSplitPane: JSplitPane + lateinit var mainSplitPane: JSplitPane protected set - var inspector: JSplitPane + lateinit var inspector: JSplitPane protected set - var interpreterPane: REPLPane + lateinit var interpreterPane: REPLPane protected set - var nodePropertyEditor: SwingNodePropertyEditor + lateinit var nodePropertyEditor: SwingNodePropertyEditor protected set - var frame: JFrame + lateinit var frame: JFrame protected set var sidebarOpen = false protected set - private var splashLabel: SplashLabel + private lateinit var splashLabel: SplashLabel private var defaultSidebarAction: (() -> Any)? = null init { - FlatLightLaf.setup() - try { - UIManager.setLookAndFeel(FlatLightLaf()) - } catch (ex: Exception) { - System.err.println("Failed to initialize Flat Light LaF, falling back to Swing default.") - } + val initializer = Runnable { + FlatLightLaf.setup() + try { + UIManager.setLookAndFeel(FlatLightLaf()) + } catch (ex: Exception) { + System.err.println("Failed to initialize Flat Light LaF, falling back to Swing default.") + } - var x: Int - var y: Int - try { - val screenSize = Toolkit.getDefaultToolkit().screenSize - x = screenSize.width / 2 - sciview.windowWidth / 2 - y = screenSize.height / 2 - sciview.windowHeight / 2 - } catch (e: HeadlessException) { - x = 10 - y = 10 - } + var x: Int + var y: Int + try { + val screenSize = Toolkit.getDefaultToolkit().screenSize + x = screenSize.width / 2 - sciview.windowWidth / 2 + y = screenSize.height / 2 - sciview.windowHeight / 2 + } catch (e: HeadlessException) { + x = 10 + y = 10 + } - frame = JFrame("sciview") - frame.layout = BorderLayout(0, 0) - frame.setSize(sciview.windowWidth, sciview.windowHeight) - frame.setLocation(x, y) - - splashLabel = SplashLabel() - val glassPane = frame.glassPane as JPanel - glassPane.isVisible = true - glassPane.layout = BorderLayout() - glassPane.isOpaque = false - glassPane.add(splashLabel, BorderLayout.CENTER) - glassPane.requestFocusInWindow() - glassPane.revalidate() - - frame.defaultCloseOperation = JFrame.DISPOSE_ON_CLOSE - nodePropertyEditor = SwingNodePropertyEditor(sciview) - - sceneryJPanel = SceneryJPanel() - JPopupMenu.setDefaultLightWeightPopupEnabled(false) - val swingMenuBar = JMenuBar() - val menus = sciview.scijavaContext?.getService(MenuService::class.java) ?: throw IllegalStateException("MenuService not available") - SwingJMenuBarCreator().createMenus(menus.getMenu("SciView"), swingMenuBar) - - val bar = ProgressPie() - bar.value = 0.0 - bar.minimumSize = Dimension(30, 30) - bar.maximumSize = Dimension(30, 30) - bar.preferredSize = Dimension(30, 30) - val progressLabel = JLabel("") - progressLabel.horizontalAlignment = SwingConstants.RIGHT - swingMenuBar.add(Box.createHorizontalGlue()) - swingMenuBar.add(progressLabel) - swingMenuBar.add(bar) - frame.jMenuBar = swingMenuBar - - sciview.taskManager.update = { current -> - if(current != null) { - progressLabel.text = "${current.source}: ${current.status} " - bar.value = current.completion.toDouble() - } else { - progressLabel.text = "" - bar.value = 0.0 + frame = JFrame("sciview") + frame.layout = BorderLayout(0, 0) + frame.setSize(sciview.windowWidth, sciview.windowHeight) + frame.setLocation(x, y) + + splashLabel = SplashLabel() + val glassPane = frame.glassPane as JPanel + glassPane.isVisible = true + glassPane.layout = BorderLayout() + glassPane.isOpaque = false + glassPane.add(splashLabel, BorderLayout.CENTER) + glassPane.requestFocusInWindow() + glassPane.revalidate() + + frame.defaultCloseOperation = JFrame.DISPOSE_ON_CLOSE + nodePropertyEditor = SwingNodePropertyEditor(sciview) + + sceneryJPanel = SceneryJPanel() + JPopupMenu.setDefaultLightWeightPopupEnabled(false) + val swingMenuBar = JMenuBar() + val menus = sciview.scijavaContext?.getService(MenuService::class.java) + ?: throw IllegalStateException("MenuService not available") + SwingJMenuBarCreator().createMenus(menus.getMenu("SciView"), swingMenuBar) + + val bar = ProgressPie() + bar.value = 0.0 + bar.minimumSize = Dimension(30, 30) + bar.maximumSize = Dimension(30, 30) + bar.preferredSize = Dimension(30, 30) + val progressLabel = JLabel("") + progressLabel.horizontalAlignment = SwingConstants.RIGHT + swingMenuBar.add(Box.createHorizontalGlue()) + swingMenuBar.add(progressLabel) + swingMenuBar.add(bar) + frame.jMenuBar = swingMenuBar + + sciview.taskManager.update = { current -> + if (current != null) { + progressLabel.text = "${current.source}: ${current.status} " + bar.value = current.completion.toDouble() + } else { + progressLabel.text = "" + bar.value = 0.0 + } + + bar.repaint() + } + sceneryJPanel.isVisible = true + nodePropertyEditor.component // Initialize node property panel + + val inspectorTree = nodePropertyEditor.tree + inspectorTree.toggleClickCount = 0 // This disables expanding menus on double click + val inspectorProperties = nodePropertyEditor.props + + val container = JPanel(CardLayout()) + + var propsPane = JScrollPane(inspectorProperties) + var treePane = JScrollPane(inspectorTree) + propsPane.verticalScrollBar.unitIncrement = 16 + treePane.verticalScrollBar.unitIncrement = 16 + inspector = JSplitPane(JSplitPane.VERTICAL_SPLIT, // + treePane, + propsPane) + inspector.dividerLocation = sciview.windowHeight / 3 + inspector.isContinuousLayout = true + inspector.border = BorderFactory.createEmptyBorder() + inspector.dividerSize = 4 + inspector.name = "Inspector" + container.add(inspector, "Inspector") + + // We need to get the surface scale here before initialising scenery's renderer, as + // the information is needed already at initialisation time. + val dt = frame.graphicsConfiguration.defaultTransform + val surfaceScale = Vector2f(dt.scaleX.toFloat(), dt.scaleY.toFloat()) + sciview.getScenerySettings().set("Renderer.SurfaceScale", surfaceScale) + interpreterPane = REPLPane(sciview.scijavaContext) + interpreterPane.component.border = BorderFactory.createEmptyBorder() + interpreterPane.component.name = "REPL" + container.add(interpreterPane.component, "REPL") + thread { + initializeInterpreter() } + mainSplitPane = JSplitPane(JSplitPane.HORIZONTAL_SPLIT, + sceneryJPanel, + container + ) + mainSplitPane.dividerLocation = frame.width + mainSplitPane.border = BorderFactory.createEmptyBorder() + mainSplitPane.dividerSize = 4 + mainSplitPane.resizeWeight = 1.0 + mainSplitPane.rightComponent = null - bar.repaint() - } - sceneryJPanel.isVisible = true - nodePropertyEditor.component // Initialize node property panel - - val inspectorTree = nodePropertyEditor.tree - inspectorTree.toggleClickCount = 0 // This disables expanding menus on double click - val inspectorProperties = nodePropertyEditor.props - - val container = JPanel(CardLayout()) - - var propsPane = JScrollPane(inspectorProperties) - var treePane = JScrollPane(inspectorTree) - propsPane.verticalScrollBar.unitIncrement = 16 - treePane.verticalScrollBar.unitIncrement = 16 - inspector = JSplitPane(JSplitPane.VERTICAL_SPLIT, // - treePane, - propsPane) - inspector.dividerLocation = sciview.windowHeight / 3 - inspector.isContinuousLayout = true - inspector.border = BorderFactory.createEmptyBorder() - inspector.dividerSize = 1 - inspector.name = "Inspector" - container.add(inspector, "Inspector") - - // We need to get the surface scale here before initialising scenery's renderer, as - // the information is needed already at initialisation time. - val dt = frame.graphicsConfiguration.defaultTransform - val surfaceScale = Vector2f(dt.scaleX.toFloat(), dt.scaleY.toFloat()) - sciview.getScenerySettings().set("Renderer.SurfaceScale", surfaceScale) - interpreterPane = REPLPane(sciview.scijavaContext) - interpreterPane.component.border = BorderFactory.createEmptyBorder() - interpreterPane.component.name = "REPL" - container.add(interpreterPane.component, "REPL") - initializeInterpreter() - mainSplitPane = JSplitPane(JSplitPane.HORIZONTAL_SPLIT, - sceneryJPanel, - container - ) - mainSplitPane.dividerLocation = frame.width - mainSplitPane.border = BorderFactory.createEmptyBorder() - mainSplitPane.dividerSize = 1 - mainSplitPane.resizeWeight = 0.5 - mainSplitPane.rightComponent = null - - val toolbar = JToolBar() - toolbar.orientation = SwingConstants.VERTICAL - - val inspectorIcon = Utils.getScaledImageIcon(SciView::class.java.getResource("toolbox.png"), 16, 16) - val inspectorButton = JToggleButton() - inspectorButton.toolTipText = "Inspector" - - val inspectorAction = object: AbstractAction("Inspector", inspectorIcon) { - override fun actionPerformed(e: ActionEvent) { - container.toggleSidebarComponent(inspector, toolbar, inspectorButton, e) + val toolbar = JToolBar() + toolbar.orientation = SwingConstants.VERTICAL + + val inspectorIcon = Utils.getScaledImageIcon(SciView::class.java.getResource("toolbox.png"), 16, 16) + val inspectorButton = JToggleButton() + inspectorButton.toolTipText = "Inspector" + + val inspectorAction = object : AbstractAction("Inspector", inspectorIcon) { + override fun actionPerformed(e: ActionEvent) { + container.toggleSidebarComponent(inspector, toolbar, inspectorButton, e) + } } - } - defaultSidebarAction = { container.toggleSidebarComponent(inspector, toolbar, inspectorButton, null) } + defaultSidebarAction = { container.toggleSidebarComponent(inspector, toolbar, inspectorButton, null) } - inspectorButton.action = inspectorAction - inspectorButton.icon = inspectorIcon - inspectorButton.hideActionText = true + inspectorButton.action = inspectorAction + inspectorButton.icon = inspectorIcon + inspectorButton.hideActionText = true - val replIcon = Utils.getScaledImageIcon(SciView::class.java.getResource("terminal.png"), 16, 16) - val replButton = JToggleButton() - replButton.toolTipText = "Script Interpreter" + val replIcon = Utils.getScaledImageIcon(SciView::class.java.getResource("terminal.png"), 16, 16) + val replButton = JToggleButton() + replButton.toolTipText = "Script Interpreter" - val replAction = object: AbstractAction("REPL", replIcon) { - override fun actionPerformed(e: ActionEvent) { - container.toggleSidebarComponent(interpreterPane.component, toolbar, replButton, e) + val replAction = object : AbstractAction("REPL", replIcon) { + override fun actionPerformed(e: ActionEvent) { + container.toggleSidebarComponent(interpreterPane.component, toolbar, replButton, e) + } } - } - replButton.action = replAction - replButton.icon = replIcon - replButton.hideActionText = true - - toolbar.add(inspectorButton) - toolbar.add(replButton) - - //frame.add(mainSplitPane, BorderLayout.CENTER); - frame.add(mainSplitPane, BorderLayout.CENTER) - frame.add(toolbar, BorderLayout.EAST) - frame.defaultCloseOperation = JFrame.DO_NOTHING_ON_CLOSE - frame.addWindowListener(object : WindowAdapter() { - override fun windowClosing(e: WindowEvent) { - logger.debug("Closing SciView window.") - close() - sciview.scijavaContext?.service(SciViewService::class.java)?.close(sciview) - sciview.isClosed = true - } - }) - - frame.isVisible = true - glassPane.repaint() - - sciview.sceneryPanel[0] = sceneryJPanel - val renderer = Renderer.createRenderer( - sciview.hub, - sciview.applicationName, - sciview.currentScene, - sciview.windowWidth, - sciview.windowHeight, - sciview.sceneryPanel[0] - ) - - sciview.setRenderer(renderer) - sciview.hub.add(SceneryElement.Renderer, renderer) - sciview.reset() - - SwingUtilities.invokeLater { - try { - while (!sciview.getSceneryRenderer()!!.firstImageReady) { - logger.debug("Waiting for renderer initialisation") - Thread.sleep(300) + replButton.action = replAction + replButton.icon = replIcon + replButton.hideActionText = true + + toolbar.add(inspectorButton) + toolbar.add(replButton) + + //frame.add(mainSplitPane, BorderLayout.CENTER); + frame.add(mainSplitPane, BorderLayout.CENTER) + frame.add(toolbar, BorderLayout.EAST) + frame.defaultCloseOperation = JFrame.DO_NOTHING_ON_CLOSE + frame.addWindowListener(object : WindowAdapter() { + override fun windowClosing(e: WindowEvent) { + logger.debug("Closing SciView window.") + close() + sciview.scijavaContext?.service(SciViewService::class.java)?.close(sciview) + sciview.isClosed = true } - Thread.sleep(200) - } catch (e: InterruptedException) { - logger.error("Renderer construction interrupted.") - } - nodePropertyEditor.rebuildTree() - logger.info("Done initializing SciView") + }) + + frame.isVisible = true + glassPane.repaint() + + sciview.sceneryPanel[0] = sceneryJPanel + val renderer = Renderer.createRenderer( + sciview.hub, + sciview.applicationName, + sciview.currentScene, + sciview.windowWidth, + sciview.windowHeight, + sciview.sceneryPanel[0] + ) + + sciview.setRenderer(renderer) + sciview.hub.add(SceneryElement.Renderer, renderer) + sciview.reset() + + SwingUtilities.invokeLater { + try { + while (!sciview.getSceneryRenderer()!!.firstImageReady) { + logger.debug("Waiting for renderer initialisation") + Thread.sleep(300) + } + Thread.sleep(200) + } catch (e: InterruptedException) { + logger.error("Renderer construction interrupted.") + } + nodePropertyEditor.rebuildTree() + logger.info("Done initializing SciView") - // subscribe to Node{Added, Removed, Changed} events, happens automatically + // subscribe to Node{Added, Removed, Changed} events, happens automatically // eventService!!.subscribe(this) - sceneryJPanel.isVisible = true + sceneryJPanel.isVisible = true - // install hook to keep inspector updated on external changes (scripting, etc) - sciview.currentScene.onNodePropertiesChanged["updateInspector"] = { node: Node -> - if (node === nodePropertyEditor.currentNode) { - nodePropertyEditor.updateProperties(node) + // install hook to keep inspector updated on external changes (scripting, etc) + sciview.currentScene.onNodePropertiesChanged["updateInspector"] = { node: Node -> + if (node === nodePropertyEditor.currentNode) { + nodePropertyEditor.updateProperties(node) + } } - } - // Enable push rendering by default - renderer.pushMode = true - sciview.camera!!.setPosition(1.65, 1) - glassPane.isVisible = false + // Enable push rendering by default + renderer.pushMode = true + sciview.camera!!.setPosition(1.65, 1) + glassPane.isVisible = false + + sceneryJPanel.minimumSize = Dimension(256, 256) + } + } - sceneryJPanel.minimumSize = Dimension(256, 256) + if(SwingUtilities.isEventDispatchThread()) { + initializer.run() + } else { + SwingUtilities.invokeAndWait(initializer) } } diff --git a/src/main/kotlin/sc/iview/ui/SwingNodePropertyEditor.kt b/src/main/kotlin/sc/iview/ui/SwingNodePropertyEditor.kt index 83f517e4..4e4a995a 100644 --- a/src/main/kotlin/sc/iview/ui/SwingNodePropertyEditor.kt +++ b/src/main/kotlin/sc/iview/ui/SwingNodePropertyEditor.kt @@ -32,6 +32,8 @@ import com.intellij.ui.components.JBPanel import graphics.scenery.Camera import graphics.scenery.Node import graphics.scenery.Scene +import graphics.scenery.volumes.TransferFunctionEditor +import graphics.scenery.volumes.Volume import net.miginfocom.swing.MigLayout import org.joml.Quaternionf import org.joml.Vector3f @@ -340,6 +342,13 @@ class SwingNodePropertyEditor(private val sciView: SciView) : UIComponent { material.setAmbient( new Vector3f( 1.0f, 0.0f, 0.0f ) ); material.setDiffuse( new Vector3f( 1.0f, 0.0f, 0.0f ) );