diff --git a/pom.xml b/pom.xml index fd6f1c07..11b9f280 100644 --- a/pom.xml +++ b/pom.xml @@ -118,11 +118,11 @@ BigWarp plugin for Fiji. **/resources/*.xml - 7.1.0 + 7.1.1 4.0.3 0.15.0 1.0.0-beta-18 - 0.15.3 + 0.16.0 10.6.0 1.0.0-beta-36 @@ -131,15 +131,11 @@ 3.0.4 3.3.0 - 7.0.1 + 7.0.2 4.2.1 - 1.1.1 - 2.2.0 4.1.1 - 1.3.5 1.6.0 - 4.2.1 - 6.1.1 + 4.2.3 0.41 0.41 diff --git a/scripts/BigWarp_N5.groovy b/scripts/BigWarp_N5.groovy index 1816e94a..07737986 100644 --- a/scripts/BigWarp_N5.groovy +++ b/scripts/BigWarp_N5.groovy @@ -154,7 +154,7 @@ def makeSources( String n5Base, String[] datasets ) source = new RandomAccessibleIntervalMipmapSource( images, - Util.getTypeFromInterval(images[0]), + images[0].getType(), scales, new mpicbg.spim.data.sequence.FinalVoxelDimensions( "pix", 1, 1, 1), "source"); diff --git a/src/main/java/bdv/ij/ApplyBigwarpPlugin.java b/src/main/java/bdv/ij/ApplyBigwarpPlugin.java index e23b13a9..7052c03a 100644 --- a/src/main/java/bdv/ij/ApplyBigwarpPlugin.java +++ b/src/main/java/bdv/ij/ApplyBigwarpPlugin.java @@ -85,7 +85,7 @@ import net.imglib2.view.Views; /** - * Apply a bigwarp transform to a 2d or 3d ImagePlus + * Apply a Bigwarp transform to a 2d or 3d ImagePlus */ public class ApplyBigwarpPlugin implements PlugIn { @@ -97,7 +97,11 @@ public class ApplyBigwarpPlugin implements PlugIn public static final String SPECIFIED_PHYSICAL = "Specified (physical units)"; public static final String SPECIFIED_PIXEL = "Specified (pixel units)"; public static final String LANDMARK_POINTS = "Landmark points"; + + @Deprecated public static final String LANDMARK_POINT_CUBE_PHYSICAL = "Landmark point cube (physical units)"; + + @Deprecated public static final String LANDMARK_POINT_CUBE_PIXEL = "Landmark point cube (pixel units)"; public static void main( final String[] args ) @@ -135,32 +139,6 @@ public static boolean validateInput( return true; } - public static double[] getResolution( - final Source< ? > source, - final String resolutionOption, - final double[] resolutionSpec ) - { - if( ( source == null ) ) - { - System.err.println("Requested target resolution but target image is missing."); - return null; - } - - final double[] res = resolutionFromSource( source ); - - /* - * Maybe the below will work in the future, but it does not right now - * ( April 2021) in part because theres no way to set the VoxelDimensions for a - * bdv.util.RandomAccessibleIntervalSource - */ -// VoxelDimensions voxdims = source.getVoxelDimensions(); -// if( voxdims == null ) -// Arrays.fill( res, 1.0 ); -// else -// voxdims.dimensions( res ); - - return res; - } public static String getUnit( final BigWarpData bwData, final String resolutionOption ) @@ -390,6 +368,19 @@ else if( fieldOfViewOption.equals( UNION_TARGET_MOVING )) return null; } + public static double[] getResolution( + final Source< ? > source, + final String resolutionOption, + final double[] resolutionSpec ) + { + if( ( source == null ) ) + { + System.err.println("Requested target resolution but target image is missing."); + return null; + } + return resolutionFromSource( source ); + } + public static double[] resolutionFromSource( final Source< ? > src ) { final double[] res = new double[ 3 ]; @@ -503,7 +494,7 @@ public static List getPixelInterval( out.add(new FinalInterval(min, max)); return out; } else { - System.out.println("Invalid fov spec, length : " + fovSpec.length); + System.err.println("Invalid fov spec, length : " + fovSpec.length); return null; } } else if (fieldOfViewOption.equals(SPECIFIED_PHYSICAL)) { @@ -527,14 +518,14 @@ public static List getPixelInterval( final long[] max = new long[]{ (long)Math.floor((offsetSpec[0] + fovSpec[0]) / outputResolution[0]), - (long)Math.floor((offsetSpec[0] + fovSpec[1]) / outputResolution[1]), + (long)Math.floor((offsetSpec[1] + fovSpec[1]) / outputResolution[1]), (long)Math.floor((offsetSpec[2] + fovSpec[2]) / outputResolution[2])}; final ArrayList out = new ArrayList<>(); - out.add(new FinalInterval(min, max)); + out.add(Intervals.zeroMin(new FinalInterval(min, max))); return out; } else { - System.out.println("Invalid fov spec, length : " + fovSpec.length); + System.err.println("Invalid fov spec, length : " + fovSpec.length); return null; } } else if (fieldOfViewOption.equals(LANDMARK_POINTS)) { @@ -564,7 +555,6 @@ public static List getPixelInterval( } System.out.println("Estimated field of view using " + numPoints + " landmarks."); - // Make sure something naughty didn't happen for (int d = 0; d < min.length; d++) { if (min[d] == Long.MAX_VALUE) { @@ -579,7 +569,7 @@ public static List getPixelInterval( } final ArrayList out = new ArrayList<>(); - out.add(new FinalInterval(min, max)); + out.add(Intervals.zeroMin(new FinalInterval(min, max))); return out; } else if (fieldOfViewOption.equals(LANDMARK_POINT_CUBE_PHYSICAL) || fieldOfViewOption.equals(LANDMARK_POINT_CUBE_PIXEL)) { @@ -667,14 +657,69 @@ public static List getMatchedPoints( } ptList.add( pt ); - - System.out.println( "Using point with name : " - + landmarks.getNames().get( i ) ); } return ptList; } + /** + * Get the offset in pixels given the output resolution and interval + * + * @param fieldOfViewOption the field of view option + * @param offsetSpec the offset specification + * @param ltm the {@link LandmarkTableModel} + * @param fieldOfViewPointFilter the landmark name filter for FOV estimation + * @param outputResolution the resolution of the output image + * @param targetSource the target source + * @return the offset in physical units + */ + public static double[] getPhysicalOffset( + final String fieldOfViewOption, + final double[] offsetSpec, + final LandmarkTableModel ltm, + final String fieldOfViewPointFilter, + final double[] outputResolution, + final Source targetSource ) + { + final int nd = 3; // this okay even for 2D + final double[] offset = new double[ nd ]; + if( fieldOfViewOption.equals( SPECIFIED_PIXEL ) ) + { + System.arraycopy( offsetSpec, 0, offset, 0, offset.length ); + return offset; + } + else if( fieldOfViewOption.equals( SPECIFIED_PHYSICAL ) ) + { + for( int d = 0; d < nd; d++ ) { + offset[ d ] = offsetSpec[ d ] / outputResolution[ d ]; + } + return offset; + } + else if( fieldOfViewOption.equals( LANDMARK_POINTS ) ) + { + Arrays.fill(offset, Double.MAX_VALUE); + List pts = getMatchedPoints(ltm, fieldOfViewPointFilter); + for( int i = 0; i < pts.size(); i++ ) { + for( int d = 0; d < nd; d++ ) { + final double v = pts.get(i)[d]; + offset[d] = v < offset[d] ? v : offset[d]; + } + } + return offset; + } + else + { + final Interval outputInterval = targetSource.getSource(0, 0); + final AffineTransform3D tform = new AffineTransform3D(); + targetSource.getSourceTransform(0, 0, tform); + + // is using interval min overkill? + // or can I count on it always be at the origin? + tform.apply(outputInterval.minAsDoubleArray(), offset); + return offset; + } + } + /** * Get the offset in pixels given the output resolution and interval * @@ -824,14 +869,87 @@ public static List apply( final boolean wait, final WriteDestinationOptions writeOpts) { - final int numChannels = bwData.numMovingSources(); - final InvertibleRealTransform invXfm = new BigWarpTransform( landmarks, tranformTypeOption ).getTransformation(); + return apply( + bwData, + landmarks, + invXfm, + tranformTypeOption, + fieldOfViewOption, + fieldOfViewPointFilter, + bboxEst, + resolutionOption, + resolutionSpec, + fovSpec, + offsetSpec, + interp, + isVirtual, + nThreads, + wait, + writeOpts); + } + + public static List apply( + final BigWarpData bwData, + final LandmarkTableModel landmarks, + final InvertibleRealTransform invXfm, + final String tranformTypeOption, + final String fieldOfViewOption, + final String fieldOfViewPointFilter, + final BoundingBoxEstimation bboxEst, + final String resolutionOption, + final double[] resolutionSpec, + final double[] fovSpec, + final double[] offsetSpec, + final Interpolation interp, + final boolean isVirtual, + final int nThreads, + final boolean wait, + final WriteDestinationOptions writeOpts) { + + final boolean show = ( writeOpts == null || writeOpts.pathOrN5Root == null || writeOpts.pathOrN5Root.isEmpty()); + return apply(bwData, landmarks, invXfm, + tranformTypeOption, fieldOfViewOption, fieldOfViewPointFilter, bboxEst, + resolutionOption, resolutionSpec, fovSpec, offsetSpec, + interp, isVirtual, nThreads, wait, writeOpts, show); + } + + public static List apply( + final BigWarpData bwData, + final LandmarkTableModel landmarks, + final InvertibleRealTransform invXfm, + final String tranformTypeOption, + final String fieldOfViewOption, + final String fieldOfViewPointFilter, + final BoundingBoxEstimation bboxEst, + final String resolutionOption, + final double[] resolutionSpec, + final double[] fovSpec, + final double[] offsetSpec, + final Interpolation interp, + final boolean isVirtual, + final int nThreads, + final boolean wait, + final WriteDestinationOptions writeOpts, + final boolean show) { + + final int numChannels = bwData.numMovingSources(); for ( int i = 0; i < numChannels; i++ ) { final SourceAndConverter< T > movingSource = bwData.getMovingSource( i ); - ((WarpedSource)(movingSource.getSpimSource())).updateTransform(invXfm); - ((WarpedSource)(movingSource.getSpimSource())).setIsTransformed(true); + final WarpedSource ws = ((WarpedSource)(movingSource.getSpimSource())); + if (ws.getTransform() == null) { + ws.updateTransform(invXfm); + ws.setIsTransformed(true); + } + } + + Source tgtSrc = null; + if (fieldOfViewOption.equals(TARGET) || resolutionOption.equals(TARGET)) { + if (bwData.numTargetSources() == 0) + throw new RuntimeException("Requested target field of view, but no target source exists."); + else + tgtSrc = bwData.getTargetSource(0).getSpimSource(); } final ProgressWriter progressWriter = new ProgressWriterIJ(); @@ -839,7 +957,6 @@ public static List apply( // Generate the properties needed to generate the transform from output pixel space // to physical space final double[] res = getResolution( bwData, resolutionOption, resolutionSpec ); - final List outputIntervalList = getPixelInterval(bwData, landmarks, invXfm, fieldOfViewOption, fieldOfViewPointFilter, bboxEst, fovSpec, offsetSpec, res); @@ -847,7 +964,8 @@ public static List apply( if( outputIntervalList.size() > 1 ) ApplyBigwarpPlugin.fillMatchedPointNames( matchedPtNames, landmarks, fieldOfViewPointFilter ); - final double[] offset = getPixelOffset( fieldOfViewOption, offsetSpec, res, outputIntervalList.get( 0 ) ); + final double[] offset = getPhysicalOffset( fieldOfViewOption, offsetSpec, landmarks, + fieldOfViewPointFilter, res, tgtSrc ); if( writeOpts != null && writeOpts.n5Dataset != null && !writeOpts.n5Dataset.isEmpty()) { @@ -858,15 +976,12 @@ public static List apply( offset, res, unit, progressWriter, writeOpts, Executors.newFixedThreadPool(nThreads)); + return null; } else { - final boolean show; - if( writeOpts == null || writeOpts.pathOrN5Root == null || writeOpts.pathOrN5Root.isEmpty()) - show = true; - else - show = false; + return runExport( bwData, bwData.sources, fieldOfViewOption, outputIntervalList, matchedPtNames, interp, @@ -896,14 +1011,12 @@ public static List runExport( int i = 0; for( final Interval outputInterval : outputIntervalList ) { - final double[] offset = ApplyBigwarpPlugin.getPixelOffset( fieldOfViewOption, offsetIn, resolution, outputIntervalList.get( i ) ); - // need to declare the exporter in the loop since the actual work // is done asynchronously, and changing variables in the loop would mess it up final BigWarpExporter exporter = BigWarpExporter.getExporter( data, sources, interp, progressWriter ); exporter.setOutputList( ipList ); exporter.setRenderResolution( resolution ); - exporter.setOffset( offset ); + exporter.setOffset( offsetIn ); exporter.setVirtual( isVirtual ); exporter.setNumThreads( nThreads ); @@ -973,10 +1086,6 @@ public static & NumericType> void runN5Export( final double[] offset = ApplyBigwarpPlugin.getPixelOffset( fieldOfViewOption, offsetArg, resolution, outputInterval); - System.out.println("resolution: " + Arrays.toString(resolution)); - System.out.println("offset : " + Arrays.toString(offset)); - System.out.println("interval : " + Intervals.toString(outputInterval)); - // setup n5 parameters final String dataset = writeOpts.n5Dataset; final int[] blockSize = writeOpts.blockSize; @@ -1087,11 +1196,11 @@ public static & NumericType> void runN5Export( final ExecutorService exec ) { - final int nd = BigWarp.detectNumDims( data.sources ); - final double[] resolution = limit(nd,resolutionArg); + final int nd = BigWarp.detectNumDims(data.sources); + final double[] resolution = limit(nd, resolutionArg); - // pixel offset - final double[] offsetPixel = ApplyBigwarpPlugin.getPixelOffset( fieldOfViewOption, offsetArg, resolution, + // physical offset + final double[] offsetPhysical = ApplyBigwarpPlugin.getPixelOffset(fieldOfViewOption, offsetArg, resolution, outputInterval); // setup n5 parameters @@ -1130,13 +1239,6 @@ public static & NumericType> void runN5Export( if( resolution.length > 2 ) resolutionTransform.set( resolution[ 2 ], 2, 2 ); - final double[] offsetPhysical = new double[resolution.length]; - offsetPhysical[0] = resolution[0] * offsetPixel[0]; - offsetPhysical[1] = resolution[1] * offsetPixel[1]; - - if( resolution.length > 2 ) - offsetPhysical[2] = resolution[2] * offsetPixel[2]; - final AffineTransform3D offsetTransform = new AffineTransform3D(); offsetTransform.set( offsetPhysical[ 0 ], 0, 3 ); offsetTransform.set( offsetPhysical[ 1 ], 1, 3 ); @@ -1153,7 +1255,7 @@ public static & NumericType> void runN5Export( final BigWarpExporter exporter = BigWarpExporter.getExporter( data, sourceAndConverter, interp, progressWriter ); exporter.setRenderResolution( resolution ); - exporter.setOffset( offsetPixel ); + exporter.setOffset( offsetPhysical ); exporter.setInterval(Intervals.zeroMin(outputInterval)); exporter.setSingleChannelNoStack(true); final RandomAccessibleInterval imgExp = (RandomAccessibleInterval)exporter.exportRai((Source)sourceAndConverter.getSpimSource()); @@ -1166,7 +1268,6 @@ public static & NumericType> void runN5Export( imgToWrite = img; final String destDataset = dataset; - final OmeNgffMetadata metadata = OmeNgffMetadata.buildForWriting(nd, srcName, axes, new String[]{"s0"}, new double[][]{resolution}, new double[][]{offsetPhysical}); @@ -1185,7 +1286,6 @@ public static & NumericType> void runN5Export( progressWriter.setProgress( 1.0 ); - System.out.println("done"); } @Override diff --git a/src/main/java/bigwarp/BigWarp.java b/src/main/java/bigwarp/BigWarp.java index a547e6ca..8942eb9f 100755 --- a/src/main/java/bigwarp/BigWarp.java +++ b/src/main/java/bigwarp/BigWarp.java @@ -1431,8 +1431,6 @@ public void exportAsImagePlus( boolean virtual, String path ) ApplyBigwarpPlugin.MOVING_WARPED, ApplyBigwarpPlugin.UNION_TARGET_MOVING, ApplyBigwarpPlugin.LANDMARK_POINTS, - ApplyBigwarpPlugin.LANDMARK_POINT_CUBE_PIXEL, - ApplyBigwarpPlugin.LANDMARK_POINT_CUBE_PHYSICAL, ApplyBigwarpPlugin.SPECIFIED_PIXEL, ApplyBigwarpPlugin.SPECIFIED_PHYSICAL }, @@ -1521,69 +1519,23 @@ public void exportAsImagePlus( boolean virtual, String path ) else interp = Interpolation.NLINEAR; - final double[] res = ApplyBigwarpPlugin.getResolution( this.data, resolutionOption, resolutionSpec ); - - final List outputIntervalList = ApplyBigwarpPlugin.getPixelInterval( this.data, - this.landmarkModel, this.currentTransform, - fieldOfViewOption, fieldOfViewPointFilter, bboxOptions, fovSpec, offsetSpec, res ); - - final List matchedPtNames = new ArrayList<>(); - if( outputIntervalList.size() > 1 ) - ApplyBigwarpPlugin.fillMatchedPointNames( matchedPtNames, getLandmarkPanel().getTableModel(), fieldOfViewPointFilter ); - - - // export has to be treated differently if we're doing fov's around - // landmark centers (because multiple images can be exported this way ) - if( matchedPtNames.size() > 0 ) - { - final BigwarpLandmarkSelectionPanel selection = new BigwarpLandmarkSelectionPanel<>( - data, data.sources, fieldOfViewOption, - outputIntervalList, matchedPtNames, interp, - offsetSpec, res, isVirtual, nThreads, - progressWriter ); - } - else - { - if( writeOpts.n5Dataset != null && !writeOpts.n5Dataset.isEmpty()) - { - @SuppressWarnings("rawtypes") - final SourceAndConverter activeSource = getCurrentSourceInActiveViewer(); - - final String unit = ApplyBigwarpPlugin.getUnit( data, resolutionOption ); - new Thread() - { - @SuppressWarnings("unchecked") - @Override - public void run() - { - progressWriter.setProgress( 0.01 ); - ApplyBigwarpPlugin.runN5Export( data, activeSource, - fieldOfViewOption, - outputIntervalList.get(0), interp, - offsetSpec, res, unit, - progressWriter, writeOpts, - Executors.newFixedThreadPool( nThreads )); - - progressWriter.setProgress( 1.00 ); - } - }.start(); - } - else - { - // export - final boolean show = ( writeOpts.pathOrN5Root == null || writeOpts.pathOrN5Root.isEmpty() ); - ApplyBigwarpPlugin.runExport( data, data.sources, fieldOfViewOption, - outputIntervalList, matchedPtNames, interp, - offsetSpec, res, isVirtual, nThreads, - progressWriter, show, false, writeOpts ); - } - } - } - - private SourceAndConverter getCurrentSourceInActiveViewer() { - - BigWarpViewerFrame activeFrame = viewerFrameP.isActive() ? viewerFrameP : viewerFrameQ; - return activeFrame.getViewerPanel().state().getCurrentSource(); + // TODO run n5 export in a separate thread? + ApplyBigwarpPlugin.apply(data, + landmarkModel, + this.currentTransform, + compressionString, + fieldOfViewOption, + fieldOfViewPointFilter, + bboxOptions, + resolutionOption, + resolutionSpec, + fovSpec, + offsetSpec, + interp, + isVirtual, + nThreads, + isVirtual, + writeOpts); } public void exportWarpField() diff --git a/src/main/java/bigwarp/BigWarpARGBExporter.java b/src/main/java/bigwarp/BigWarpARGBExporter.java index 2e623e7a..def7ecb2 100644 --- a/src/main/java/bigwarp/BigWarpARGBExporter.java +++ b/src/main/java/bigwarp/BigWarpARGBExporter.java @@ -101,7 +101,7 @@ public RandomAccessibleInterval< ARGBType > exportRai() final int numChannels = bwData.numMovingSources(); for ( int i = 0; i < numChannels; i++ ) { - final Source src = bwData.getMovingSource( i ).getSpimSource(); + final Source src = bwData.getMovingSourceForExport( i ).getSpimSource(); raiList.add( (RandomAccessibleInterval)exportRai(src)); } final RandomAccessibleInterval< ARGBType > raiStack = Views.stack( raiList ); @@ -117,6 +117,7 @@ public RandomAccessibleInterval exportRai(Source src) { src.getSourceTransform(0, 0, srcXfm); // in pixel space + @SuppressWarnings("unchecked") final RealRandomAccessible raiRaw = (RealRandomAccessible)src.getInterpolatedSource(0, 0, interp); // the transform from world to new pixel coordinates diff --git a/src/main/java/bigwarp/BigWarpData.java b/src/main/java/bigwarp/BigWarpData.java index 580d9767..a9aa1966 100644 --- a/src/main/java/bigwarp/BigWarpData.java +++ b/src/main/java/bigwarp/BigWarpData.java @@ -1,8 +1,5 @@ package bigwarp; -import bigwarp.source.SourceInfo; -import bigwarp.util.BigWarpUtils; - import java.util.ArrayList; import java.util.LinkedHashMap; import java.util.List; @@ -12,25 +9,26 @@ import java.util.function.Supplier; import bdv.cache.CacheControl; +import bdv.cache.SharedQueue; import bdv.gui.BigWarpViewerFrame; import bdv.img.WarpedSource; import bdv.tools.InitializeViewerState; import bdv.tools.brightness.ConverterSetup; -import bdv.tools.brightness.SetupAssignments; import bdv.tools.transformation.TransformedSource; import bdv.util.Bounds; import bdv.viewer.ConverterSetups; import bdv.viewer.Source; import bdv.viewer.SourceAndConverter; import bdv.viewer.SynchronizedViewerState; -import bdv.viewer.VisibilityAndGrouping; +import bigwarp.source.SourceInfo; +import bigwarp.util.BigWarpUtils; import net.imglib2.realtransform.AffineGet; import net.imglib2.realtransform.AffineTransform3D; import net.imglib2.realtransform.InvertibleRealTransform; import net.imglib2.realtransform.InvertibleWrapped2DTransformAs3D; import net.imglib2.realtransform.RealTransform; import net.imglib2.realtransform.Wrapped2DTransformAs3D; -import net.imglib2.util.Intervals; +import net.imglib2.realtransform.inverse.WrappedIterativeInvertibleRealTransform; public class BigWarpData< T > { @@ -40,6 +38,10 @@ public class BigWarpData< T > public final List< ConverterSetup > converterSetups; public final CacheControl cache; + + public boolean cacheFixedTransform = true; + + private static SharedQueue sharedQueue; public BigWarpData() { @@ -98,6 +100,14 @@ public BigWarpData( final List< SourceAndConverter< T > > sources, final List< C else this.cache = cache; } + + public static SharedQueue getSharedQueue() { + + if (sharedQueue == null) + sharedQueue = new SharedQueue(Math.max(1, Runtime.getRuntime().availableProcessors() / 2)); + + return sharedQueue; + } private static ArrayList listOf( int[] x ) { @@ -132,6 +142,22 @@ public SourceAndConverter< T > getMovingSource( int i ) return null; } + @SuppressWarnings("unchecked") + public SourceAndConverter getMovingSourceForExport(int i) { + + int curIdx = 0; + for (final Map.Entry idToInfo : sourceInfos.entrySet()) { + final SourceInfo info = idToInfo.getValue(); + if (info.isMoving()) { + if (curIdx == i) { + return info.sourceForExport() != null ? (SourceAndConverter)info.sourceForExport() : (SourceAndConverter)info.getSourceAndConverter(); + } + curIdx++; + } + } + return null; + } + public int numTargetSources() { return sourceInfos.size() - numMovingSources(); @@ -150,6 +176,7 @@ public List< Integer > getMovingSourceIndices() { return indices; } + @SuppressWarnings("unchecked") public SourceAndConverter< T > getTargetSource( int i ) { int curIdx = 0; @@ -325,7 +352,6 @@ public List> wrapSourcesAsTransformed() { final List< SourceAndConverter> wrappedSource = new ArrayList<>(); - int i = 0; for ( final SourceInfo sourceInfo : sourceInfos.values() ) { if ( sourceInfo.isMoving() ) @@ -337,7 +363,6 @@ public List> wrapSourcesAsTransformed() { wrappedSource.add( ( SourceAndConverter< T > ) sourceInfo.getSourceAndConverter() ); } - i++; } return wrappedSource; } @@ -366,17 +391,30 @@ public void setTransformation(SourceInfo srcInfo, final RealTransform transform, wrapMovingSources(srcInfo); } + @SuppressWarnings({"unchecked", "rawtypes"}) public void applyTransformations() { int i = 0; - for (final SourceAndConverter sac : sources) { + for (final SourceAndConverter sac : sources) { final SourceInfo info = getSourceInfo(sac); final RealTransform transform = info.getTransform(); if (transform != null) { - final SourceAndConverter newSac = inheritConverter( + + SourceAndConverter sourceForExport = null; + SourceAndConverter newSac = inheritConverter( applyFixedTransform(sac.getSpimSource(), transform), sac); + if (cacheFixedTransform) { + + sourceForExport = newSac; + final InvertibleRealTransform invTransform = transform instanceof InvertibleRealTransform ? (InvertibleRealTransform)transform + : new WrappedIterativeInvertibleRealTransform(transform); + newSac = BigWarpInit.cacheTransformedSource(sac, invTransform, getSharedQueue()); + } + + // will need to reload transformation on export if the transform is cached + info.setSourceForExport(sourceForExport); info.setSourceAndConverter(newSac); sources.set(i, newSac); } @@ -384,14 +422,30 @@ public void applyTransformations() { } } + @SuppressWarnings({"unchecked", "rawtypes"}) public void applyTransformation(final SourceInfo info, Source unWrappedSource) { final RealTransform transform = info.getTransform(); - @SuppressWarnings("unchecked") - final SourceAndConverter sac = (SourceAndConverter)info.getSourceAndConverter(); - final SourceAndConverter newSac = inheritConverter( - transform != null ? applyFixedTransform(unWrappedSource, transform) : unWrappedSource, + if (transform == null) + return; + + final SourceAndConverter sac = info.getSourceAndConverter(); + SourceAndConverter sourceForExport = null; + SourceAndConverter newSac = inheritConverter( + applyFixedTransform(sac.getSpimSource(), transform), sac); + + if (cacheFixedTransform) { + + sourceForExport = newSac; + final InvertibleRealTransform invTransform = transform instanceof InvertibleRealTransform ? (InvertibleRealTransform)transform + : new WrappedIterativeInvertibleRealTransform(transform); + newSac = BigWarpInit.cacheTransformedSource(sac, invTransform, getSharedQueue()); + } + + // will need to reload transformation on export if the transform is + // cached + info.setSourceForExport(sourceForExport); info.setSourceAndConverter(newSac); sources.set(sources.indexOf(sac), newSac); } @@ -432,7 +486,7 @@ public void updateFixedTransform( final Source src, final RealTransform trans if( !(src instanceof WarpedSource )) return; - WarpedSource wsrc = (WarpedSource)src; + final WarpedSource wsrc = (WarpedSource)src; wsrc.updateTransform(tform); } @@ -461,43 +515,36 @@ public Source applyFixedTransform( final Source src, final RealTransfo // TODO using a TransformedSource like the below wasn't working correctly // investigate later -// if( transform instanceof AffineGet ) -// { + // if (transform instanceof AffineGet) { // // if transform is a 2D affine, turn it into a 3D transform // // // can use TransformedSource // final AffineTransform3D affine3d; -// if( transform instanceof AffineTransform3D ) -// affine3d = ( AffineTransform3D ) transform; -// else -// { + // if (transform instanceof AffineTransform3D) + // affine3d = (AffineTransform3D)transform; + // else { // affine3d = new AffineTransform3D(); // final AffineGet transformTo3D = BigWarpUtils.toAffine3D((AffineGet)transform); -// System.out.println( transformTo3D ); -// affine3d.preConcatenate( transformTo3D ); -// System.out.println( affine3d ); + // System.out.println(transformTo3D); + // affine3d.preConcatenate(transformTo3D); + // System.out.println(affine3d); // } // // // could perhaps try to be clever if its a warped source (?), maybe later // TransformedSource tsrc; -// if ( src instanceof TransformedSource ) -// { -// tsrc = ( TransformedSource ) ( src ); -// } -// else -// { -// tsrc = new TransformedSource( src ); + // if (src instanceof TransformedSource) { + // tsrc = (TransformedSource)(src); + // } else { + // tsrc = new TransformedSource(src); // } -// tsrc.setFixedTransform( affine3d ); -// return ( Source< T > ) tsrc; -// } -// else -// { + // tsrc.setFixedTransform(affine3d); + // return (Source)tsrc; + // } else { // // need to use WarpedSource -// final WarpedSource wsrc = new WarpedSource( src, src.getName() ); -// wsrc.updateTransform( tform ); -// wsrc.setIsTransformed( true ); -// return ( Source< T > ) wsrc; + // final WarpedSource wsrc = new WarpedSource(src, src.getName()); + // wsrc.updateTransform(tform); + // wsrc.setIsTransformed(true); + // return (Source)wsrc; // } @@ -521,6 +568,7 @@ public Source applyFixedTransform( final Source src, final RealTransfo final WarpedSource wsrc = new WarpedSource( src, null ); wsrc.updateTransform( tform ); wsrc.setIsTransformed( true ); + return ( Source< T > ) wsrc; } diff --git a/src/main/java/bigwarp/BigWarpExporter.java b/src/main/java/bigwarp/BigWarpExporter.java index 640024e8..2e55b60e 100644 --- a/src/main/java/bigwarp/BigWarpExporter.java +++ b/src/main/java/bigwarp/BigWarpExporter.java @@ -116,7 +116,6 @@ public enum ParallelizationPolicy { private String exportPath; - public BigWarpExporter( BigWarpData bwData, final List< ConverterSetup > convSetups, @@ -232,7 +231,7 @@ public void setSingleChannelNoStack( boolean singleChannelNoStack ) } /** - * Set the offset of the output field of view in pixels. + * Set the offset of the output field of view in physical units. * * @param offset the offset in pixel units. */ @@ -250,8 +249,8 @@ public void setOffset( double... offset ) public void buildTotalRenderTransform() { pixelRenderToPhysical.identity(); - pixelRenderToPhysical.concatenate( resolutionTransform ); pixelRenderToPhysical.concatenate( offsetTransform ); + pixelRenderToPhysical.concatenate( resolutionTransform ); } public void setInterval( final Interval outputInterval ) @@ -259,6 +258,7 @@ public void setInterval( final Interval outputInterval ) this.outputInterval = outputInterval; } + @SuppressWarnings("hiding") public RandomAccessibleInterval exportSource( SourceAndConverter src ) { final RealRandomAccessible< T > raiRaw = src.getSpimSource().getInterpolatedSource( 0, 0, interp ); @@ -582,13 +582,9 @@ public static FinalInterval transformRealInterval( RealTransform xfm, RealInterv xfm.apply( pt, ptxfm ); copyToLongFloor( ptxfm, min ); - // transform max - for( int d = 0; d < nd; d++ ) - { pt[ d ] = interval.realMax( d ); - } xfm.apply( pt, ptxfm ); copyToLongCeil( ptxfm, max ); diff --git a/src/main/java/bigwarp/BigWarpInit.java b/src/main/java/bigwarp/BigWarpInit.java index fa3db9b8..f03063f9 100644 --- a/src/main/java/bigwarp/BigWarpInit.java +++ b/src/main/java/bigwarp/BigWarpInit.java @@ -26,6 +26,7 @@ import java.net.URI; import java.net.URISyntaxException; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collections; import java.util.LinkedHashMap; import java.util.List; @@ -72,6 +73,8 @@ import bdv.util.BdvOptions; import bdv.util.RandomAccessibleIntervalMipmapSource; import bdv.util.RandomAccessibleIntervalSource; +import bdv.util.VolatileSource; +import bdv.util.volatiles.VolatileTypeMatcher; import bdv.util.volatiles.VolatileViews; import bdv.viewer.Source; import bdv.viewer.SourceAndConverter; @@ -95,16 +98,31 @@ import net.imagej.Dataset; import net.imagej.axis.Axes; import net.imagej.axis.CalibratedAxis; +import net.imglib2.Interval; import net.imglib2.RandomAccessibleInterval; +import net.imglib2.RealInterval; +import net.imglib2.Volatile; import net.imglib2.cache.img.CachedCellImg; +import net.imglib2.cache.img.CellLoader; +import net.imglib2.cache.img.ReadOnlyCachedCellImgFactory; +import net.imglib2.cache.img.ReadOnlyCachedCellImgOptions; +import net.imglib2.cache.img.SingleCellArrayImg; import net.imglib2.cache.volatiles.CacheHints; import net.imglib2.cache.volatiles.LoadingStrategy; import net.imglib2.converter.Converter; import net.imglib2.converter.Converters; import net.imglib2.display.RealARGBColorConverter; import net.imglib2.display.ScaledARGBConverter; +import net.imglib2.img.cell.AbstractCellImg; +import net.imglib2.interpolation.randomaccess.ClampingNLinearInterpolatorFactory; +import net.imglib2.outofbounds.OutOfBoundsConstantValueFactory; import net.imglib2.realtransform.AffineTransform3D; +import net.imglib2.realtransform.BoundingBoxEstimation; +import net.imglib2.realtransform.InvertibleRealTransform; +import net.imglib2.realtransform.InvertibleRealTransformSequence; import net.imglib2.realtransform.RealTransform; +import net.imglib2.realtransform.RealTransformRandomAccessible; +import net.imglib2.realtransform.Translation; import net.imglib2.type.NativeType; import net.imglib2.type.numeric.ARGBType; import net.imglib2.type.numeric.NumericType; @@ -112,12 +130,11 @@ import net.imglib2.type.numeric.integer.UnsignedIntType; import net.imglib2.type.volatiles.VolatileARGBType; import net.imglib2.util.Util; +import net.imglib2.view.ExtendedRandomAccessibleInterval; import net.imglib2.view.IntervalView; import net.imglib2.view.Views; -public class BigWarpInit -{ - +public class BigWarpInit { public static final N5MetadataParser[] PARSERS = new N5MetadataParser[]{ new N5CosemMetadataParser(), @@ -262,8 +279,8 @@ public static < T extends RealType< T > > void initSourceReal( final Source< T > /** * Add images from an {@link ImagePlus} as sources for BigWarp. Each channel will be added as its own source. - * - * @param the type + * + * @param the type * @param bwdata the bigwarp data * @param ip an ImagePlus * @param setupId id @@ -326,7 +343,17 @@ public static < T > LinkedHashMap< Source< T >, SourceInfo > createSources( BigW /** * Initialize BigWarp. - * + * + * @param bwdata + * a BigWarpData instance + * @param src + * the Source to add + * @param setupId + * the id to assign to the source + * @param numTimepoints + * number of time points this source has + * @param isMoving + * true if this is a moving source * @return a {@link BigWarpData} instance * * @deprecated Use the output from one of the @@ -360,11 +387,23 @@ public static < T > BigWarpData< T > add( BigWarpData< T > bwdata, LinkedHashMap /** * Initialize BigWarp. * + * @param bwdata + * a BigWarpData instance + * @param src + * the Source to add + * @param setupId + * the id to assign to the source + * @param numTimepoints + * number of time points this source has + * @param isMoving + * true if this is a moving source + * @param transform + * a fixed transform to apply to this source * @return a {@link BigWarpData} instance * * @deprecated Use the output from one of the - * {{@code createSources(BigWarpData, String, int, boolean)}} - * to call {{@code add(BigWarpData, LinkedHashMap, RealTransform)}} + * {{@code createSources(BigWarpData, String, int, boolean)}} to + * call {{@code add(BigWarpData, LinkedHashMap, RealTransform)}} * instead */ @SuppressWarnings( { "unchecked", "rawtypes" } ) @@ -430,7 +469,7 @@ public static < T > LinkedHashMap< Source< T >, SourceInfo > createSources( BigW final IntervalView> channel = hasZ ? channelRaw : Views.addDimension( channelRaw, 0, 0 ); @SuppressWarnings( "unchecked" ) - final RandomAccessibleIntervalSource source = new RandomAccessibleIntervalSource( channel, Util.getTypeFromInterval( data ), res, data.getName() ); + final RandomAccessibleIntervalSource source = new RandomAccessibleIntervalSource( channel, data.getType(), res, data.getName() ); final SourceInfo info = new SourceInfo( baseId + c, isMoving, data.getName(), () -> data.getSource() ); info.setSerializable( first ); @@ -445,7 +484,7 @@ public static < T > LinkedHashMap< Source< T >, SourceInfo > createSources( BigW final RandomAccessibleInterval> img = hasZ ? data : Views.addDimension( data, 0, 0 ); @SuppressWarnings( "unchecked" ) - final RandomAccessibleIntervalSource source = new RandomAccessibleIntervalSource( img, Util.getTypeFromInterval( data ), res, data.getName() ); + final RandomAccessibleIntervalSource source = new RandomAccessibleIntervalSource( img, data.getType(), res, data.getName() ); final SourceInfo info = new SourceInfo( baseId, isMoving, data.getName(), () -> data.getSource() ); info.setSerializable( true ); @@ -505,7 +544,7 @@ private static String schemeSpecificPartWithoutQuery( URI uri ) public static < T > LinkedHashMap< Source< T >, SourceInfo > createSources( final BigWarpData< T > bwData, String uri, int setupId, boolean isMoving ) throws URISyntaxException, IOException, SpimDataException { - final SharedQueue sharedQueue = new SharedQueue(Math.max(1, Runtime.getRuntime().availableProcessors() / 2)); + final SharedQueue sharedQueue = BigWarpData.getSharedQueue(); final URI encodedUri = N5URI.encodeAsUri( uri.trim() ); final LinkedHashMap< Source< T >, SourceInfo > sourceStateMap = new LinkedHashMap<>(); if ( encodedUri.isOpaque() ) @@ -595,11 +634,17 @@ else if ( new File( uri ).isDirectory() ) } /** - * Initialize BigWarp. + * Add a source to thet given {@link BigWarpData} instance using the N5 api * + * @param the type + * @param bwdata the BigWarpData instance + * @param isMoving true if this source is moving + * @param setupId the id to assign to this source + * @param rootPath the root path to the container + * @param dataset the path to the dataset relative to the container root * @return a {@link SpimData} instance - * - * @deprecated Use output from + * + * * @deprecated Use output from * {@code createSources(BigWarpData, boolean, int, String, String)} and add with * {@code add(BigWarpData, LinkedHashMap, RealTransform)} instead. */ @@ -727,7 +772,7 @@ public static < T extends NativeType, M extends N5Metadata > Source< T > open else imageRaw = to3d( N5Utils.open( n5, meta.getPath() ) ); - if ( meta instanceof N5ImagePlusMetadata && ( ( N5ImagePlusMetadata ) meta ).getType() == ImagePlus.COLOR_RGB && Util.getTypeFromInterval( imageRaw ) instanceof UnsignedIntType ) + if ( meta instanceof N5ImagePlusMetadata && ( ( N5ImagePlusMetadata ) meta ).getType() == ImagePlus.COLOR_RGB && imageRaw.getType() instanceof UnsignedIntType ) { image = toColor( imageRaw ); } @@ -740,10 +785,10 @@ public static < T extends NativeType, M extends N5Metadata > Source< T > open final AffineTransform3D srcXfm = ( ( SpatialMetadata ) meta ).spatialTransform3d(); final FinalVoxelDimensions voxelDims = new FinalVoxelDimensions( unit, new double[] { srcXfm.get( 0, 0 ), srcXfm.get( 1, 1 ), srcXfm.get( 2, 2 ) } ); - return new BwRandomAccessibleIntervalSource( image, ( NumericType ) Util.getTypeFromInterval( image ), srcXfm, meta.getPath(), voxelDims ); + return new BwRandomAccessibleIntervalSource( image, ( NumericType ) image.getType(), srcXfm, meta.getPath(), voxelDims ); } else - return new BwRandomAccessibleIntervalSource( image, ( NumericType ) Util.getTypeFromInterval( image ), new AffineTransform3D(), meta.getPath() ); + return new BwRandomAccessibleIntervalSource( image, ( NumericType ) image.getType(), new AffineTransform3D(), meta.getPath() ); } catch ( final RuntimeException e ) { @@ -755,18 +800,19 @@ public static < T extends NativeType, M extends N5Metadata > Source< T > open public static < T extends NativeType & NumericType> Source< T > openN5V( final N5Reader n5, final MultiscaleMetadata< ? > multiMeta, final SharedQueue sharedQueue ) { - List> sources = new ArrayList<>(); - List converterSetups = new ArrayList<>(); + final List> sources = new ArrayList<>(); + final List converterSetups = new ArrayList<>(); try { N5Viewer.buildN5Sources(n5, new DataSelection(n5, Collections.singletonList(multiMeta)), sharedQueue, converterSetups, sources, BdvOptions.options()); if( sources.size() > 0 ) return (Source)sources.get(0).getSpimSource(); - } catch (IOException e) { } + } catch (final IOException e) { } return null; } - public static < T extends NativeType > Source< T > openAsSourceMulti( final N5Reader n5, final MultiscaleMetadata< ? > multiMeta, final SharedQueue sharedQueue, final boolean isVolatile ) + @SuppressWarnings("unchecked") + public static < T extends NumericType & NativeType > Source< T > openAsSourceMulti( final N5Reader n5, final MultiscaleMetadata< ? > multiMeta, final SharedQueue sharedQueue, final boolean isVolatile ) { final String[] paths = multiMeta.getPaths(); final AffineTransform3D[] transforms = multiMeta.spatialTransforms3d(); @@ -798,10 +844,7 @@ public static < T extends NativeType > Source< T > openAsSourceMulti( final N mipmapScales[ s ][ 2 ] = transforms[ s ].get( 2, 2 ); } - @SuppressWarnings( { "unchecked", "rawtypes" } ) - final RandomAccessibleIntervalMipmapSource source = new RandomAccessibleIntervalMipmapSource( images, ( NumericType ) Util.getTypeFromInterval( images[ 0 ] ), mipmapScales, new mpicbg.spim.data.sequence.FinalVoxelDimensions( unit, mipmapScales[ 0 ] ), new AffineTransform3D(), multiMeta.getPaths()[ 0 ] + "_group" ); - - return source; + return new RandomAccessibleIntervalMipmapSource( images, (T)images[ 0 ].getType(), mipmapScales, new mpicbg.spim.data.sequence.FinalVoxelDimensions( unit, mipmapScales[ 0 ] ), new AffineTransform3D(), multiMeta.getPaths()[ 0 ] + "_group" ); } private static RandomAccessibleInterval< ? > to3d( RandomAccessibleInterval< ? > img ) @@ -824,12 +867,9 @@ public void convert( UnsignedIntType input, ARGBType output ) }, new ARGBType() ); } - @SuppressWarnings( { "rawtypes", "unchecked" } ) - public static < T > BigWarpData< T > initData() - { -// final ArrayList< ConverterSetup > converterSetups = new ArrayList< ConverterSetup >(); -// final ArrayList< SourceAndConverter< T > > sources = new ArrayList< SourceAndConverter< T > >(); -// return new BigWarpData( sources, converterSetups, null ); + @SuppressWarnings({"rawtypes", "unchecked"}) + public static BigWarpData initData() { + return new BigWarpData(); } @@ -929,36 +969,153 @@ private static < T extends NumericType< T > > void addSourceToListsNumericType( } } - public static ArrayList< SourceAndConverter< ? > > wrapSourcesAsRenamable( final List< SourceAndConverter< ? > > sources, final String[] names ) - { - final ArrayList< SourceAndConverter< ? > > wrappedSource = new ArrayList< SourceAndConverter< ? > >(); + public static ArrayList> wrapSourcesAsRenamable(final List> sources, + final String[] names) { + + final ArrayList> wrappedSource = new ArrayList>(); int i = 0; - for ( final SourceAndConverter< ? > sac : sources ) - { - final SourceAndConverter< ? > renamableSource = wrapSourceAsRenamable( sac ); - if ( names != null ) - { - ( ( RenamableSource< ? > ) renamableSource.getSpimSource() ).setName( names[ i ] ); + for (final SourceAndConverter sac : sources) { + final SourceAndConverter renamableSource = wrapSourceAsRenamable(sac); + if (names != null) { + ((RenamableSource)renamableSource.getSpimSource()).setName(names[i]); } - wrappedSource.add( renamableSource ); + wrappedSource.add(renamableSource); i++; } return wrappedSource; } - private static < T > SourceAndConverter< T > wrapSourceAsRenamable( final SourceAndConverter< T > src ) - { - if ( src.asVolatile() == null ) - { - return new SourceAndConverter< T >( new RenamableSource< T >( src.getSpimSource() ), src.getConverter(), null ); + private static SourceAndConverter wrapSourceAsRenamable(final SourceAndConverter src) { + + if (src.asVolatile() == null) { + return new SourceAndConverter(new RenamableSource(src.getSpimSource()), src.getConverter(), null); + } else { + return new SourceAndConverter(new RenamableSource(src.getSpimSource()), src.getConverter(), + src.asVolatile()); } - else - { - return new SourceAndConverter< T >( new RenamableSource< T >( src.getSpimSource() ), src.getConverter(), src.asVolatile() ); + } + + @SuppressWarnings("unchecked") + public static & NativeType, V extends Volatile & NumericType> SourceAndConverter cacheTransformedSource( + final SourceAndConverter sac, + final InvertibleRealTransform transform, + SharedQueue sharedQueue) { + + final Source src = sac.getSpimSource(); + final int nd = src.getSource(0, 0).numDimensions(); + final int N = src.getNumMipmapLevels(); + + final T type = src.getType(); + final V vtype = (V)VolatileTypeMatcher.getVolatileTypeForType(type); + + final int[] defaultCellDimensions = new int[nd]; + Arrays.fill(defaultCellDimensions, 64); + + // TODO handle time? + final RandomAccessibleInterval[] mipmaps = new RandomAccessibleInterval[N]; + final RandomAccessibleInterval>[] vmipmaps = new RandomAccessibleInterval[N]; + final AffineTransform3D[] sourceTransforms = new AffineTransform3D[N]; + for (int i = 0; i < src.getNumMipmapLevels(); i++) { + + final RandomAccessibleInterval img = src.getSource(0, i); + + final AffineTransform3D origScaleTform = new AffineTransform3D(); + src.getSourceTransform(0, i, origScaleTform); + + // build the inverse transform: from transformed pixel coordinates + // to original pixel coordinates "through" physical coordinates + InvertibleRealTransformSequence tformToPhysicalSpace = new InvertibleRealTransformSequence(); + tformToPhysicalSpace.add(origScaleTform); + tformToPhysicalSpace.add(transform.copy()); + tformToPhysicalSpace.add(origScaleTform.inverse()); + + // compute the bounding box in physical coordinates + final BoundingBoxEstimation bbox = new BoundingBoxEstimation(); + final RealInterval targetInterval = bbox.estimateInterval(tformToPhysicalSpace.inverse(), img); + final Interval pixelInterval = BoundingBoxEstimation.containingInterval(targetInterval); + + // update the source transform to take into account any change in bounding box + final AffineTransform3D newSourceTform = origScaleTform.copy(); + final Translation tlation = new Translation(targetInterval.minAsDoubleArray()); + newSourceTform.concatenate(tlation.inverse()); + sourceTransforms[i] = newSourceTform; + + InvertibleRealTransformSequence tformToPixelSpace = new InvertibleRealTransformSequence(); + tformToPixelSpace.add(origScaleTform); + tformToPixelSpace.add(transform.copy()); + tformToPixelSpace.add(newSourceTform.inverse()); + + final RandomAccessibleInterval raiTform = transform( img, pixelInterval, tformToPixelSpace); + final ReadOnlyCachedCellImgFactory cacheFactory = new ReadOnlyCachedCellImgFactory( + new ReadOnlyCachedCellImgOptions() + .volatileAccesses(true) + .cellDimensions(getCellDimensionsOrDefault(img, defaultCellDimensions))); + + final CellLoader copier = new CellLoader() { + @Override + public void load(SingleCellArrayImg cell) throws Exception { + + Views.flatIterable(Views.interval(Views.pair(raiTform, cell), cell)).forEach( + pair -> pair.getB().set(pair.getA())); + } + }; + + final RandomAccessibleInterval cachedTransformedMipmap = cacheFactory.create( + raiTform.dimensionsAsLongArray(), + type.copy(), + copier); + mipmaps[i] = cachedTransformedMipmap; + + final int priority = N - 1 - i; + final CacheHints cacheHints = new CacheHints(LoadingStrategy.BUDGETED, priority, false); + vmipmaps[i] = VolatileViews.wrapAsVolatile(cachedTransformedMipmap, sharedQueue, cacheHints); + } + + final RandomAccessibleIntervalMipmapSource cachedTransformedSource = + new RandomAccessibleIntervalMipmapSource( + mipmaps, + type, + sourceTransforms, + src.getVoxelDimensions(), + src.getName(), + src.doBoundingBoxCulling()); + + final Source vsrc = new VolatileSource(cachedTransformedSource, vtype, sharedQueue); + final SourceAndConverter vsac = new SourceAndConverter(vsrc, BigDataViewer.createConverterToARGB(vtype)); + return new SourceAndConverter(cachedTransformedSource, BigDataViewer.createConverterToARGB(type), vsac); + } + + @SuppressWarnings("rawtypes") + private static int[] getCellDimensionsOrDefault(final RandomAccessibleInterval img, + final int[] defaultDimensions) { + + // TODO need to unwrap views + if (img instanceof AbstractCellImg) { + return ((AbstractCellImg)img).getCellGrid().getCellDimensions(); + } else { + return defaultDimensions; } } + private static & NativeType> RandomAccessibleInterval transform( + final RandomAccessibleInterval img, + final Interval targetInterval, + final RealTransform transform) { + + final T background = img.getType().copy(); + background.setZero(); + + return Views.interval( + new RealTransformRandomAccessible<>( + Views.interpolate( + new ExtendedRandomAccessibleInterval<>(img, + new OutOfBoundsConstantValueFactory<>(background)), + new ClampingNLinearInterpolatorFactory<>()), + transform), + targetInterval); + } + /** * Create {@link BigWarpData} from two {@link AbstractSpimData}. * @@ -1086,16 +1243,16 @@ private static < T > SourceAndConverter< T > wrapSourceAsRenamable( final Source /** * Create {@link BigWarpData} from two XML files. - * + * + * @param the type * @param xmlFilenameP * moving source XML * @param xmlFilenameQ * fixed source XML - * @return BigWarpData + * @return a BigWarpData instance */ public static < T extends NativeType > BigWarpData< T > createBigWarpDataFromXML( final String xmlFilenameP, final String xmlFilenameQ ) { -// return createBigWarpData( new XMLLoader( xmlFilenameP ), new XMLLoader( xmlFilenameQ ), null ); final BigWarpData< T > bwdata = BigWarpInit.initData(); try { @@ -1125,6 +1282,7 @@ public static < T extends NativeType > BigWarpData< T > createBigWarpDataFrom /** * Create {@link BigWarpData} from two {@link ImagePlus ImagePluses}. * + * @param the type * @param impP * moving source ImagePlus * @param impQ @@ -1138,7 +1296,9 @@ public static < T > BigWarpData< T > createBigWarpDataFromImages( final ImagePlu final LinkedHashMap< Source< T >, SourceInfo > mvgSrcs = BigWarpInit.createSources( bwdata, impP, id, 0, true ); id += mvgSrcs.size(); BigWarpInit.add( bwdata, mvgSrcs ); - BigWarpInit.add( bwdata, BigWarpInit.createSources( bwdata, impQ, id, 0, false ) ); + + if (impQ != null) + BigWarpInit.add(bwdata, BigWarpInit.createSources(bwdata, impQ, id, 0, false)); return bwdata; } @@ -1190,6 +1350,7 @@ public static < T > BigWarpData< T > createBigWarpDataFromImages( final ImagePlu /** * Create {@link BigWarpData} from an xml file and an {@link ImagePlus}. * + * @param the type * @param xmlFilenameP * movingSource XML * @param impQ @@ -1242,6 +1403,7 @@ public static < T extends NativeType > BigWarpData< T > createBigWarpDataFrom /** * Create {@link BigWarpData} from an {@link ImagePlus} and an XML file. * + * @param the type * @param impP * moving source ImagePlus * @param xmlFilenameQ @@ -1250,7 +1412,6 @@ public static < T extends NativeType > BigWarpData< T > createBigWarpDataFrom */ public static < T extends NativeType > BigWarpData< T > createBigWarpDataFromImagePlusXML( final ImagePlus impP, final String xmlFilenameQ ) { -// return createBigWarpData( new ImagePlusLoader( impP ), new XMLLoader( xmlFilenameQ ) ); final BigWarpData< T > bwdata = BigWarpInit.initData(); try { diff --git a/src/main/java/bigwarp/BigWarpRealExporter.java b/src/main/java/bigwarp/BigWarpRealExporter.java index d7f40e5b..59880743 100644 --- a/src/main/java/bigwarp/BigWarpRealExporter.java +++ b/src/main/java/bigwarp/BigWarpRealExporter.java @@ -143,7 +143,7 @@ public RandomAccessibleInterval< T > exportRai() numChannels = bwData.numMovingSources(); for ( int i = 0; i < numChannels; i++ ) - raiList.add( (RandomAccessibleInterval)exportRai( bwData.getMovingSource( i ).getSpimSource())); + raiList.add( (RandomAccessibleInterval)exportRai( bwData.getMovingSourceForExport( i ).getSpimSource())); if( singleChannelNoStack ) return raiList.get(0); diff --git a/src/main/java/bigwarp/loader/ImagePlusLoader.java b/src/main/java/bigwarp/loader/ImagePlusLoader.java index 4f58d54b..c3dc3bea 100644 --- a/src/main/java/bigwarp/loader/ImagePlusLoader.java +++ b/src/main/java/bigwarp/loader/ImagePlusLoader.java @@ -156,7 +156,6 @@ public HashMap getSetupSettings() { public void update(final BigWarpData data) { for (Integer key : settingsMap.keySet()) { - SourceAndConverter sac = data.sources.get(key.intValue()); data.getSourceInfo(key).setColorSettings(settingsMap.get(key)); } } @@ -196,13 +195,13 @@ public static double sanitizeCalibration(double in, String dim) { public SpimDataMinimal load(final int setupIdOffset, ImagePlus imp) { // get calibration and image size - final double pw; - final double ph; - final double pd; + final double pw = sanitizeCalibration(imp.getCalibration().pixelWidth, "x"); + final double ph = sanitizeCalibration(imp.getCalibration().pixelHeight, "y"); + final double pd = sanitizeCalibration(imp.getCalibration().pixelDepth, "z"); - pw = sanitizeCalibration(imp.getCalibration().pixelWidth, "x"); - ph = sanitizeCalibration(imp.getCalibration().pixelHeight, "y"); - pd = sanitizeCalibration(imp.getCalibration().pixelDepth, "z"); + final double ox = imp.getCalibration().xOrigin; + final double oy = imp.getCalibration().yOrigin; + final double oz = imp.getCalibration().zOrigin; String punit = imp.getCalibration().getUnit(); if (punit == null || punit.isEmpty()) @@ -274,7 +273,7 @@ public SpimDataMinimal load(final int setupIdOffset, ImagePlus imp) { // create ViewRegistrations from the images calibration final AffineTransform3D sourceTransform = new AffineTransform3D(); - sourceTransform.set(pw, 0, 0, 0, 0, ph, 0, 0, 0, 0, pd, 0); + sourceTransform.set(pw, 0, 0, ox, 0, ph, 0, oy, 0, 0, pd, oz); final ArrayList registrations = new ArrayList(); for (int t = 0; t < numTimepoints; ++t) for (int s = 0; s < numSetups; ++s) @@ -321,7 +320,10 @@ public ColorSettings(int converterSetupIndex, double min, double max, ARGBType c } /** - * @deprecated + * + * @param setups the SetupAssignments + * + * @deprecated use updateSetup( ConverterSetup) */ public void updateSetup(final SetupAssignments setups) { diff --git a/src/main/java/bigwarp/source/SourceInfo.java b/src/main/java/bigwarp/source/SourceInfo.java index ebc63408..7df64584 100644 --- a/src/main/java/bigwarp/source/SourceInfo.java +++ b/src/main/java/bigwarp/source/SourceInfo.java @@ -22,6 +22,14 @@ public class SourceInfo private Supplier transformUriSupplier; + /* + * When using an imported fixed transform, we'll want to apply all the + * transforms in sequence, interpolating only once. This source should be + * used for export, in contrast to the source stored in BigWarpData, which + * should be used for visualization. + */ + private SourceAndConverter sourceForExport; + boolean serializable = false; public SourceInfo( final int id, final boolean moving ) @@ -55,26 +63,36 @@ public SourceInfo( final int id, final boolean moving, final String name, final * * @param serializable whether to setSerialize this source or not. */ - public void setSerializable( final boolean serializable ) - { + public void setSerializable(final boolean serializable) { + this.serializable = serializable; } - public boolean isSerializable() - { + public boolean isSerializable() { + return serializable; } - public String getUri() - { + public String getUri() { + return uriSupplier.get(); } - public void setUriSupplier( final Supplier< String > getUri ) - { + public void setUriSupplier(final Supplier getUri) { + this.uriSupplier = getUri; } + public void setSourceForExport(SourceAndConverter sourceForExport) { + + this.sourceForExport = sourceForExport; + } + + public SourceAndConverter sourceForExport() { + + return sourceForExport; + } + public int getId() { return id; diff --git a/src/main/java/bigwarp/transforms/BigWarpTransform.java b/src/main/java/bigwarp/transforms/BigWarpTransform.java index 71c089b3..dae2ab53 100644 --- a/src/main/java/bigwarp/transforms/BigWarpTransform.java +++ b/src/main/java/bigwarp/transforms/BigWarpTransform.java @@ -24,7 +24,6 @@ import java.util.Arrays; import java.util.function.Supplier; -import bdv.gui.TransformTypeSelectDialog; import bdv.util.BoundedRange; import bdv.viewer.SourceAndConverter; import bdv.viewer.animate.SimilarityModel3D; @@ -54,10 +53,10 @@ import net.imglib2.realtransform.AffineTransform3D; import net.imglib2.realtransform.InverseRealTransform; import net.imglib2.realtransform.InvertibleRealTransform; +import net.imglib2.realtransform.InvertibleWrapped2DTransformAs3D; import net.imglib2.realtransform.MaskedSimilarityTransform.Interpolators; import net.imglib2.realtransform.ThinplateSplineTransform; import net.imglib2.realtransform.Translation2D; -import net.imglib2.realtransform.InvertibleWrapped2DTransformAs3D; import net.imglib2.realtransform.inverse.WrappedIterativeInvertibleRealTransform; import net.imglib2.type.numeric.RealType; import net.imglib2.type.numeric.real.DoubleType; diff --git a/src/main/java/bigwarp/transforms/NgffTransformations.java b/src/main/java/bigwarp/transforms/NgffTransformations.java index f8626b70..58c9475b 100644 --- a/src/main/java/bigwarp/transforms/NgffTransformations.java +++ b/src/main/java/bigwarp/transforms/NgffTransformations.java @@ -562,11 +562,10 @@ public static CoordinateSystem createVectorFieldCoordinateSystem(final String na /** * returns null if no permutation needed * - * @param cs + * @param cs a coordinate system * @return a permutation if needed - * @throws Exception */ - public static final int[] vectorAxisLastNgff( final CoordinateSystem cs ) throws Exception { + public static final int[] vectorAxisLastNgff( final CoordinateSystem cs ) { final Axis[] axes = cs.getAxes(); final int n = axes.length; @@ -576,15 +575,6 @@ public static final int[] vectorAxisLastNgff( final CoordinateSystem cs ) throws else { int vecDim = -1; -// for( int i = 0; i < n; i++ ) -// { -// vecDim = i; -// break; -// } -// -// if( vecDim < 0 ) -// return null; - final int[] permutation = new int[ n ]; int k = 0; @@ -605,9 +595,6 @@ public static final int[] vectorAxisLastNgff( final CoordinateSystem cs ) throws } } - /** - * @throws Exception the exception - */ public static final int[] vectorAxisLastNgff( final N5Reader n5, final String dataset ) throws Exception { diff --git a/src/main/java/bigwarp/util/BigWarpUtils.java b/src/main/java/bigwarp/util/BigWarpUtils.java index 3cffd7d1..6de90454 100644 --- a/src/main/java/bigwarp/util/BigWarpUtils.java +++ b/src/main/java/bigwarp/util/BigWarpUtils.java @@ -66,9 +66,6 @@ public static void ensurePositiveZ( final AffineTransform3D xfm ) public static void ensurePositiveDeterminant( final AffineTransform3D xfm ) { -// if( det( xfm ) < 0 ) -// permuteXY( xfm ); - if( det( xfm ) < 0 ) flipX( xfm ); } @@ -119,6 +116,8 @@ public static void permuteXY( final AffineTransform3D xfm ) * width of the viewer display * @param viewerHeight * height of the viewer display + * @param zoomedIn + * true if tighter, more "zoomedIn" bounds are desired * @param state * the {@link ViewerState} containing at least one source. * @return proposed initial viewer transform. @@ -179,7 +178,6 @@ public static AffineTransform3D initTransform( final int viewerWidth, final int else scale = Math.min( scaleX, scaleY ); viewerTransform.scale( scale ); -// viewerTransform.set( 1.0, 2, 2 ); // window center offset viewerTransform.set( viewerTransform.get( 0, 3 ) + cX, 0, 3 ); @@ -204,20 +202,6 @@ public static double quaternionAngle( double[] q1, double[] q2 ) return Math.acos( 2 * dot * dot - 1); } -// public static double angleBetween( final AffineTransform3D xfm1, final AffineTransform3D xfm2 ) -// { -// double[][] tmpMat = new double[ 3 ][ 4 ]; -// double[] q1 = new double[ 4 ]; -// double[] q2 = new double[ 4 ]; -// -// xfm1.toMatrix( tmpMat ); -// LinAlgHelpers.qu -// -// normalize( q1 ); -// normalize( q2 ); -// -// } - public static void normalize( double[] x ) { double magSqr = 0; diff --git a/src/main/java/net/imglib2/realtransform/BoundingBoxEstimation.java b/src/main/java/net/imglib2/realtransform/BoundingBoxEstimation.java index 90d3dd5f..c46dce4b 100644 --- a/src/main/java/net/imglib2/realtransform/BoundingBoxEstimation.java +++ b/src/main/java/net/imglib2/realtransform/BoundingBoxEstimation.java @@ -94,6 +94,21 @@ public static double[] samplesPerDim( final RealInterval itvl, final int maxSamp return steps; } + public RealInterval estimateInterval( RealTransform xfm, RealInterval interval ) + { + steps = samplesPerDim( interval, samplesPerDim ); + + switch( method ) + { + case CORNERS: + return cornersReal(xfm, interval); + case VOLUME: + return volumeReal(xfm, interval, steps ); + default: + return facesReal( xfm, interval, steps ); + } + } + public Interval estimatePixelInterval( RealTransform xfm, Interval interval ) { steps = samplesPerDim( interval, samplesPerDim ); @@ -139,7 +154,7 @@ public static FinalInterval faces( RealTransform xfm, Interval interval, double[ return containingInterval(facesReal( xfm, interval, steps )); } - public static FinalRealInterval facesReal( RealTransform xfm, Interval interval, double[] stepsIn ) + public static FinalRealInterval facesReal( RealTransform xfm, RealInterval interval, double[] stepsIn ) { if( xfm == null ) return new FinalRealInterval( interval ); @@ -187,8 +202,8 @@ public static void minMaxInterval( final RealTransform xfm, final RealInterval i for( int d = 0; d < nd; d++ ) { - long lo = (long)Math.floor( ptxfm.getDoublePosition(d) ); - long hi = (long)Math.ceil( ptxfm.getDoublePosition(d) ); + double lo = ptxfm.getDoublePosition(d); + double hi = ptxfm.getDoublePosition(d); if( lo < min[ d ]) min[ d ] = lo; @@ -209,7 +224,7 @@ public static void subInterval( final RealInterval interval, final double pos, f max[dim] = pos; } - public static FinalRealInterval volumeReal( RealTransform xfm, Interval interval, double[] steps ) + public static FinalRealInterval volumeReal( RealTransform xfm, RealInterval interval, double[] steps ) { if( xfm == null ) return new FinalRealInterval( interval ); @@ -228,11 +243,12 @@ public static FinalInterval volume( RealTransform xfm, Interval interval, double { return containingInterval( volumeReal( xfm, interval, steps )); } + - public static FinalInterval corners( RealTransform xfm, Interval interval ) + public static RealInterval cornersReal( RealTransform xfm, RealInterval interval ) { if( xfm == null ) - return new FinalInterval( interval ); + return new FinalRealInterval( interval ); int nd = interval.numDimensions(); double[] pt = new double[ nd ]; @@ -253,9 +269,9 @@ public static FinalInterval corners( RealTransform xfm, Interval interval ) for( int d = 0; d < nd; d++ ) { if( it.getLongPosition( d ) == 0 ) - pt[ d ] = interval.min( d ); + pt[ d ] = interval.realMin( d ); else - pt[ d ] = interval.max( d ); + pt[ d ] = interval.realMax( d ); } xfm.apply( pt, ptxfm ); @@ -275,4 +291,10 @@ public static FinalInterval corners( RealTransform xfm, Interval interval ) return new FinalInterval( min, max ); } + public static FinalInterval corners( RealTransform xfm, Interval interval ) + { + + return containingInterval(cornersReal(xfm, interval)); + } + } diff --git a/src/test/java/bigwarp/BigWarpTestUtils.java b/src/test/java/bigwarp/BigWarpTestUtils.java index 49e45de6..4ba66bc8 100644 --- a/src/test/java/bigwarp/BigWarpTestUtils.java +++ b/src/test/java/bigwarp/BigWarpTestUtils.java @@ -1,8 +1,21 @@ package bigwarp; -import bdv.gui.BigWarpViewerOptions; -import bdv.viewer.Source; -import bigwarp.source.SourceInfo; +import java.io.IOException; +import java.io.StringWriter; +import java.lang.reflect.Type; +import java.net.URISyntaxException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.stream.DoubleStream; +import java.util.stream.Stream; + +import org.bouncycastle.util.Arrays; +import org.junit.Assert; + import com.google.common.collect.MapDifference; import com.google.common.collect.Maps; import com.google.common.reflect.TypeToken; @@ -11,31 +24,47 @@ import com.google.gson.JsonElement; import com.google.gson.JsonObject; import com.google.gson.JsonParser; + +import bdv.gui.BigWarpViewerOptions; +import bdv.util.RandomAccessibleIntervalMipmapSource; +import bdv.util.RandomAccessibleIntervalSource; +import bdv.viewer.Source; +import bigwarp.landmarks.LandmarkTableModel; +import bigwarp.source.SourceInfo; import ij.IJ; import ij.ImagePlus; import ij.gui.NewImage; import ij.plugin.StackWriter; -import java.io.IOException; -import java.io.StringWriter; -import java.lang.reflect.Type; -import java.net.URISyntaxException; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.Paths; -import java.util.ArrayList; -import java.util.LinkedHashMap; -import java.util.Map; import mpicbg.spim.data.SpimDataException; +import mpicbg.spim.data.sequence.FinalVoxelDimensions; import net.imglib2.FinalInterval; +import net.imglib2.Localizable; +import net.imglib2.Point; +import net.imglib2.RandomAccess; +import net.imglib2.RandomAccessibleInterval; +import net.imglib2.RealPoint; +import net.imglib2.algorithm.blocks.BlockAlgoUtils; +import net.imglib2.algorithm.blocks.BlockSupplier; +import net.imglib2.algorithm.blocks.downsample.Downsample; +import net.imglib2.img.array.ArrayImg; +import net.imglib2.img.array.ArrayImgs; +import net.imglib2.img.basictypeaccess.array.ByteArray; import net.imglib2.img.display.imagej.ImageJFunctions; +import net.imglib2.iterator.IntervalIterator; +import net.imglib2.iterator.RealIntervalIterator; import net.imglib2.position.FunctionRandomAccessible; +import net.imglib2.realtransform.AffineTransform3D; +import net.imglib2.realtransform.RealTransform; +import net.imglib2.realtransform.Scale; +import net.imglib2.realtransform.ScaleAndTranslation; import net.imglib2.type.NativeType; +import net.imglib2.type.numeric.NumericType; +import net.imglib2.type.numeric.integer.AbstractIntegerType; import net.imglib2.type.numeric.integer.UnsignedByteType; import net.imglib2.view.Views; -import org.junit.Assert; +import net.imglib2.view.fluent.RandomAccessibleIntervalView.Extension; -public class BigWarpTestUtils -{ +public class BigWarpTestUtils{ /** * Create a 3D image file which is deleted on exit. @@ -51,7 +80,6 @@ public static String createTemp3DImage( String title, String format ) try { tmpImgPath = Files.createTempFile( title, "." + format ); - //noinspection ResultOfMethodCallIgnored tmpImgPath.toFile().delete(); } catch ( final IOException e ) @@ -65,7 +93,6 @@ public static String createTemp3DImage( String title, String format ) private static String create3DImage( final String format, final Path tmpImgPath ) throws IOException { final ImagePlus img3d = NewImage.createByteImage( tmpImgPath.getFileName().toString(), 8, 8, 4, NewImage.FILL_RAMP ); - System.out.println( tmpImgPath.toString()); IJ.saveAs(img3d, format, tmpImgPath.toString()); tmpImgPath.toFile().deleteOnExit(); @@ -95,7 +122,6 @@ public static String createTemp3DImage( final String format, Path imagePath ) private static String create2DImage( final String format, final Path tmpImgPath ) throws IOException { final ImagePlus img2d = NewImage.createByteImage( tmpImgPath.getFileName().toString(), 8, 8, 1, NewImage.FILL_RAMP ); - System.out.println( tmpImgPath.toString()); IJ.saveAs(img2d, format, tmpImgPath.toString()); tmpImgPath.toFile().deleteOnExit(); return tmpImgPath.toString(); @@ -115,14 +141,12 @@ public static String createTemp2DImage( String title, String format ) try { tmpImg = Files.createTempFile( title, "." + format ); - //noinspection ResultOfMethodCallIgnored tmpImg.toFile().delete(); return create2DImage( format, tmpImg ); } catch ( final Exception e ) { if (tmpImg != null) { - //noinspection ResultOfMethodCallIgnored tmpImg.toFile().delete(); } throw new RuntimeException( e ); @@ -259,7 +283,7 @@ static < T extends NativeType > BigWarp< T > createBigWarp(String sourcePath, return new BigWarp<>( data, opts, null ); } - static ImagePlus generateImagePlus( final String title ) + public static ImagePlus generateImagePlus( final String title ) { final FunctionRandomAccessible< UnsignedByteType > fimg = new FunctionRandomAccessible<>( 3, @@ -268,8 +292,250 @@ static ImagePlus generateImagePlus( final String title ) return ImageJFunctions.wrap( Views.interval( fimg, new FinalInterval( 32, 32, 1 ) ), title ); } - public static void main( String[] args ) throws SpimDataException, URISyntaxException, IOException - { - createBigWarp( true, true, false, false); + public static ImagePlus generateImagePlus3d(final String title, + final long[] size, + final long[] pos, + final int value, + final double[] resolution, + final double[] offset) { + + final ArrayImg img = ArrayImgs.unsignedBytes(size); + img.getAt(pos).set(value); + + final ImagePlus imp = ImageJFunctions.wrap(img, title); + imp.setDimensions(1, (int)size[2], 1); + + imp.getCalibration().pixelWidth = resolution[0]; + imp.getCalibration().pixelHeight = resolution[1]; + imp.getCalibration().pixelDepth = resolution[2]; + + imp.getCalibration().xOrigin = offset[0]; + imp.getCalibration().yOrigin = offset[1]; + imp.getCalibration().zOrigin = offset[2]; + + return imp; + } + + public static Source generateSource( + final String name, + final long[] size, + final long[] pos, + final double[] resolution, + final double[] offset) { + + return generateSource(name, size, Stream.of(new Point(pos)), resolution, offset); + } + + public static Source generateSource( + final String name, + final long[] size, + final Stream positions, + final double[] resolution, + final double[] offset) { + + final ArrayImg img = ArrayImgs.unsignedBytes(size); + setAll(img, positions, 255); + + AffineTransform3D tform = new AffineTransform3D(); + tform.set( + resolution[0], 0, 0, offset[0], + 0, resolution[1], 0, offset[1], + 0, 0, resolution[2], offset[2]); + + return new RandomAccessibleIntervalSource(img, img.getType(), tform, name); + } + + public static Source generateMultiscaleSource( + final int numScales, + final String name, + final long[] size, + final long[] pos, + final double[] resolution, + final double[] offset) { + + return generateMultiscaleSource(numScales, name, size, Stream.of(new Point(pos)), resolution, offset); + } + + public static > void setAll(final RandomAccessibleInterval img, final Stream positions, final int value) { + + final RandomAccess ra = img.randomAccess(); + positions.forEach(x -> { + ra.setPosition(x); + ra.get().setInteger(value); + }); + } + + public static Source generateMultiscaleSource( + final int numScales, + final String name, + final long[] size, + Stream positions, + final double[] resolution, + final double[] offset) { + + final ArrayImg img = ArrayImgs.unsignedBytes(size); + setAll(img, positions, 255); + + final int nd = 3; + final double[] scale = new double[nd]; + final double[] translation = new double[nd]; + for (int i = 0; i < nd; i++) { + scale[i] = 0.5; + translation[i] = -0.25; + } + + final ScaleAndTranslation downsampleTform = new ScaleAndTranslation(scale, translation); + final int[] downsampleFactors = new int[]{2, 2, 2}; + + AffineTransform3D tform = new AffineTransform3D(); + tform.set( + resolution[0], 0, 0, offset[0], + 0, resolution[1], 0, offset[1], + 0, 0, resolution[2], offset[2]); + + final RandomAccessibleInterval[] imgs = new RandomAccessibleInterval[numScales]; + final AffineTransform3D[] tforms = new AffineTransform3D[numScales]; + + imgs[0] = img; + tforms[0] = tform.copy(); + for (int s = 1; s < numScales; s++) { + + imgs[s] = downsampleAvgBy2NativeType(imgs[s - 1], downsampleFactors, + downsampledSize( imgs[s-1].dimensionsAsLongArray(), downsampleFactors)); + tform.preConcatenate(downsampleTform); + tforms[s] = tform.copy(); + } + + return new RandomAccessibleIntervalMipmapSource( + imgs, + new UnsignedByteType(), + tforms, + new FinalVoxelDimensions("arb", resolution), + name, + true); } + + public static > Source levelToSource( Source src, int level ) { + + final AffineTransform3D tf = new AffineTransform3D(); + src.getSourceTransform(0, level, tf); + return new RandomAccessibleIntervalSource( + src.getSource(0, level), + src.getType(), + tf, + src.getName() + " " + level); + } + + private static long[] downsampledSize(long[] dimensions, int[] factors) { + + long[] dsSize = new long[dimensions.length]; + for (int i = 0; i < dimensions.length; i++) + dsSize[i] = (long)Math.ceil((double)dimensions[i] / factors[i]); + + return dsSize; + } + + private static > RandomAccessibleInterval downsampleAvgBy2NativeType( + final RandomAccessibleInterval img, final int[] downsampleFactors, final long[] dimensions) { + + final int[] cellDimensions = new int[]{32}; + final BlockSupplier blocks = BlockSupplier + .of(img.view().extend(Extension.border())) + .andThen(Downsample.downsample(downsampleFactors)); + return BlockAlgoUtils.cellImg(blocks, dimensions, cellDimensions); + } + + public static class TestImagePlusBuilder { + + String title = "img"; + long[] size = new long[]{32, 16, 8}; + long[] pos = new long[]{16, 8, 4}; + int value = 1; + double[] resolution = new double[]{4, 3, 2}; + double[] offset = new double[]{0, 0, 0}; + + public ImagePlus build() { + + return generateImagePlus3d(title, size, pos, value, resolution, offset); + } + + public TestImagePlusBuilder title(String title) { + + this.title = title; + return this; + } + + public TestImagePlusBuilder size(long[] size) { + + this.size = size; + return this; + } + + public TestImagePlusBuilder position(long[] pos) { + + this.pos = pos; + return this; + } + + public TestImagePlusBuilder position(int value) { + + this.value = value; + return this; + } + + public TestImagePlusBuilder resolution(double[] resolution) { + + this.resolution = resolution; + return this; + } + + public TestImagePlusBuilder offset(double[] offset) { + + this.offset = offset; + return this; + } + + } + + public static LandmarkTableModel identityLandmarks(int nd) { + + final long[] min = new long[nd]; + Arrays.fill(min, -1); + + final long[] max = new long[nd]; + Arrays.fill(max, 1); + + final double[] ones = DoubleStream.generate(() -> 1.0).limit(nd).toArray(); + return landmarks(new IntervalIterator(min, max), new Scale(ones)); + } + + public static LandmarkTableModel landmarks(final IntervalIterator it, RealTransform tform) { + + final int nd = it.numDimensions(); + final LandmarkTableModel ltm = new LandmarkTableModel(nd); + + final RealPoint pt = new RealPoint(nd); + while (it.hasNext()) { + it.fwd(); + tform.apply(it, pt); + ltm.add(it.positionAsDoubleArray(), pt.positionAsDoubleArray()); + } + + return ltm; + } + + public static LandmarkTableModel addBboxLandmarks(LandmarkTableModel ltm, + RealIntervalIterator it, String nameFormat) { + + int i = 0; + while (it.hasNext()) { + it.fwd(); + ltm.add(it.positionAsDoubleArray(), false); + int row = ltm.getRowCount() - 1; + ltm.setValueAt(String.format(nameFormat, i), row, LandmarkTableModel.NAMECOLUMN); + i++; + } + return ltm; + } + } diff --git a/src/test/java/bigwarp/BigWarpTransformCacheTests.java b/src/test/java/bigwarp/BigWarpTransformCacheTests.java new file mode 100644 index 00000000..7cb88108 --- /dev/null +++ b/src/test/java/bigwarp/BigWarpTransformCacheTests.java @@ -0,0 +1,132 @@ +package bigwarp; + +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +import java.util.stream.IntStream; + +import org.junit.Test; + +import bdv.viewer.Source; +import net.imglib2.Cursor; +import net.imglib2.Point; +import net.imglib2.RandomAccess; +import net.imglib2.RandomAccessibleInterval; +import net.imglib2.cache.img.CachedCellImg; +import net.imglib2.realtransform.AffineTransform3D; +import net.imglib2.realtransform.Scale3D; +import net.imglib2.realtransform.Translation3D; +import net.imglib2.type.numeric.integer.AbstractIntegerType; +import net.imglib2.type.numeric.integer.UnsignedByteType; +import net.imglib2.view.Views; + +public class BigWarpTransformCacheTests { + + /** + * Test that cached transformed sources are pixelwise identical to uncached + * when using an identity transformation. + */ + @Test + public void cachedIdentityTransform() { + + final Source msSrc = BigWarpTestUtils.generateMultiscaleSource(2, "msSrc", + new long[]{32, 16, 8}, + IntStream.of(14, 15).mapToObj(x -> new Point(x, 8, 4)), + new double[]{1, 1, 1}, + new double[]{0, 0, 0}); + + final Scale3D identity = new Scale3D(1, 1, 1); + final BigWarpData bwData = BigWarpInit.initData(); + BigWarpInit.add(bwData, BigWarpInit.createSources(bwData, msSrc, 0, true), identity, () -> "identity"); + BigWarpInit.add(bwData, BigWarpInit.createSources(bwData, msSrc, 1, false)); + bwData.applyTransformations(); + bwData.wrapMovingSources(); + + Source mvgSrc = bwData.getMovingSource(0).getSpimSource(); + Source tgtSrc = bwData.getTargetSource(0).getSpimSource(); + assertEquals("moving source is not cached", CachedCellImg.class, mvgSrc.getSource(0, 0).getClass()); + + assertTrue("scale level 0 not equal", equal(tgtSrc.getSource(0, 0), mvgSrc.getSource(0, 0))); + assertTrue("scale level 1 not equal", equal(tgtSrc.getSource(0, 1), mvgSrc.getSource(0, 1))); + } + + /** + * Test that cached transformed sources with an offset are pixelwise + * identical to uncached when using an identity transformation. + */ + @Test + public void cachedIdentityTransformOffset() { + + final Source msSrc = BigWarpTestUtils.generateMultiscaleSource(2, "msSrc", + new long[]{32, 16, 8}, + IntStream.of(14, 15).mapToObj(x -> new Point(x, 8, 4)), + new double[]{1, 1, 1}, + new double[]{0.2, 1.3, -2.7}); + + final Scale3D identity = new Scale3D(1, 1, 1); + final BigWarpData bwData = BigWarpInit.initData(); + BigWarpInit.add(bwData, BigWarpInit.createSources(bwData, msSrc, 0, true), identity, () -> "identity"); + BigWarpInit.add(bwData, BigWarpInit.createSources(bwData, msSrc, 1, false)); + bwData.applyTransformations(); + bwData.wrapMovingSources(); + + Source mvgSrc = bwData.getMovingSource(0).getSpimSource(); + Source tgtSrc = bwData.getTargetSource(0).getSpimSource(); + assertEquals("moving source is not cached", CachedCellImg.class, mvgSrc.getSource(0, 0).getClass()); + + assertTrue("scale level 0 not equal", equal(tgtSrc.getSource(0, 0), mvgSrc.getSource(0, 0))); + assertTrue("scale level 1 not equal", equal(tgtSrc.getSource(0, 1), mvgSrc.getSource(0, 1))); + } + + /** + * Test that cached transformed sources with an offset are pixelwise + * identical to uncached when using an identity transformation. + */ + @Test + public void cachedTranslationTransformOffset() { + + final Source msSrc = BigWarpTestUtils.generateMultiscaleSource(2, "msSrc", + new long[]{32, 16, 8}, + IntStream.of(14, 15).mapToObj(x -> new Point(x, 8, 4)), + new double[]{1, 1, 1}, + new double[]{0.2, 1.3, -2.7}); + + final Translation3D translation = new Translation3D(-2.5, 1.1, 3.3); + final BigWarpData bwData = BigWarpInit.initData(); + BigWarpInit.add(bwData, BigWarpInit.createSources(bwData, msSrc, 0, true), translation, () -> "translation(-2.5, 1.1, 3.3)"); + BigWarpInit.add(bwData, BigWarpInit.createSources(bwData, msSrc, 1, false)); + bwData.applyTransformations(); + bwData.wrapMovingSources(); + + Source mvgSrc = bwData.getMovingSource(0).getSpimSource(); + Source tgtSrc = bwData.getTargetSource(0).getSpimSource(); + assertEquals("moving source is not cached", CachedCellImg.class, mvgSrc.getSource(0, 0).getClass()); + + assertTrue("scale level 0 not equal", equal(tgtSrc.getSource(0, 0), mvgSrc.getSource(0, 0))); + assertTrue("scale level 1 not equal", equal(tgtSrc.getSource(0, 1), mvgSrc.getSource(0, 1))); + + final AffineTransform3D mvgTform0 = new AffineTransform3D(); + mvgSrc.getSourceTransform(0, 0, mvgTform0); + + final AffineTransform3D tgtTform0 = new AffineTransform3D(); + tgtSrc.getSourceTransform(0, 0, tgtTform0); + tgtTform0.preConcatenate(translation); + + assertArrayEquals("mvg transform is not the translated tgt transform", tgtTform0.getRowPackedCopy(), mvgTform0.getRowPackedCopy(), 1e-9); + } + + private > boolean equal(RandomAccessibleInterval a, RandomAccessibleInterval b) { + + final Cursor ca = Views.flatIterable(a).cursor(); + final RandomAccess rab = b.randomAccess(); + while (ca.hasNext()) { + ca.fwd(); + rab.setPosition(ca); + if (ca.get().getInteger() != rab.get().getInteger()) + return false; + + } + return true; + } +} diff --git a/src/test/java/bigwarp/Sources2Dtests.java b/src/test/java/bigwarp/Sources2Dtests.java index d1d417d5..0c34c67c 100644 --- a/src/test/java/bigwarp/Sources2Dtests.java +++ b/src/test/java/bigwarp/Sources2Dtests.java @@ -77,7 +77,7 @@ public static & RealType> Source loadSource( Stri AffineTransform3D xfm = new AffineTransform3D(); xfm.translate(0, 0, zOffset); - return new RandomAccessibleIntervalSource<>(img, Util.getTypeFromInterval(img), xfm, imp.getTitle()); + return new RandomAccessibleIntervalSource<>(img, img.getType(), xfm, imp.getTitle()); } } diff --git a/src/test/java/bigwarp/apply/BigWarpApplyTests.java b/src/test/java/bigwarp/apply/BigWarpApplyTests.java new file mode 100644 index 00000000..6539d6e8 --- /dev/null +++ b/src/test/java/bigwarp/apply/BigWarpApplyTests.java @@ -0,0 +1,291 @@ +package bigwarp.apply; + +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; + +import java.util.Arrays; +import java.util.List; +import java.util.stream.IntStream; + +import org.junit.Test; + +import bdv.ij.ApplyBigwarpPlugin; +import bdv.viewer.Interpolation; +import bigwarp.BigWarpData; +import bigwarp.BigWarpInit; +import bigwarp.BigWarpTestUtils; +import bigwarp.landmarks.LandmarkTableModel; +import bigwarp.transforms.BigWarpTransform; +import ij.ImagePlus; +import net.imglib2.img.Img; +import net.imglib2.img.display.imagej.ImageJFunctions; +import net.imglib2.iterator.RealIntervalIterator; +import net.imglib2.realtransform.BoundingBoxEstimation; +import net.imglib2.realtransform.InvertibleRealTransform; +import net.imglib2.type.numeric.integer.UnsignedByteType; +import net.imglib2.util.Intervals; + +public class BigWarpApplyTests { + + private static final double EPS = 1e-6; + + @Test + public void testExportSimple() { + + final long[] pt = new long[]{16, 8, 4}; + final ImagePlus mvg = new BigWarpTestUtils.TestImagePlusBuilder().title("mvg") + .position(pt).build(); + + final ImagePlus tgt = new BigWarpTestUtils.TestImagePlusBuilder().title("tgt") + .position(pt).build(); + + final BigWarpData bwData = BigWarpInit.initData(); + BigWarpInit.add(bwData, BigWarpInit.createSources(bwData, mvg, 0, 0, true)); + BigWarpInit.add(bwData, BigWarpInit.createSources(bwData, tgt, 1, 0, false)); + bwData.wrapMovingSources(); + + final LandmarkTableModel ltm = BigWarpTestUtils.identityLandmarks(3); + final List resList = transformToTarget(mvg, tgt, ltm); + assertEquals(1, resList.size()); + + final ImagePlus res = resList.get(0); + assertResolutionsEqual(tgt, res); + assertOriginsEqual(tgt, res);; + + final Img img = ImageJFunctions.wrapByte(res); + assertEquals(1, img.getAt(pt).get()); + assertEquals(0, img.getAt(0, 0, 0).get()); + assertEquals(0, img.getAt(pt[0] - 2, pt[1] - 2, pt[2] - 2).get()); + } + + @Test + public void testExportOffset() { + + final long[] pt = new long[]{16, 8, 4}; + final long[] tgtOffset = new long[]{-3, -2, -1}; + final double[] tgtOffsetDouble = Arrays.stream(tgtOffset).mapToDouble(x -> x).toArray(); + + final long[] tlatedPt = IntStream.of(0, 1, 2).mapToLong(i -> { + return pt[i] - tgtOffset[i]; + }).toArray(); + + double[] resolution = new double[]{1, 1, 1}; + final ImagePlus mvg = new BigWarpTestUtils.TestImagePlusBuilder().title("mvg") + .resolution(resolution) + .position(pt).build(); + + final ImagePlus tgt = new BigWarpTestUtils.TestImagePlusBuilder().title("tgt") + .resolution(resolution) + .position(pt).offset(tgtOffsetDouble).build(); + + final BigWarpData bwData = BigWarpInit.initData(); + BigWarpInit.add(bwData, BigWarpInit.createSources(bwData, mvg, 0, 0, true)); + BigWarpInit.add(bwData, BigWarpInit.createSources(bwData, tgt, 1, 0, false)); + bwData.wrapMovingSources(); + + final LandmarkTableModel ltm = BigWarpTestUtils.identityLandmarks(3); + final List resList = transformToTarget(mvg, tgt, ltm); + assertEquals(1, resList.size()); + + final ImagePlus res = resList.get(0); + assertResolutionsEqual(tgt, res); + assertOriginsEqual(tgt, res); + + final Img img = ImageJFunctions.wrapByte(res); + assertEquals(1, img.getAt(tlatedPt).get()); + assertEquals(0, img.getAt(0, 0, 0).get()); + assertEquals(0, img.getAt(tlatedPt[0] - 2, tlatedPt[1] - 2, tlatedPt[2] - 2).get()); + } + + @Test + public void testExportFovOffset() { + + final double[] resolution = new double[]{1, 1, 1}; + final long[] pt = new long[]{16, 8, 4}; + + // choose the min such that the non-zero point is at [0,0,0] + final double[] min = Arrays.stream(pt).mapToDouble(x -> x).toArray(); + + // choose the fov such that the image is size [2,2,2] + final double[] fov = IntStream.of(0, 1, 2).mapToDouble(i -> { + return 1.9 * resolution[i]; + }).toArray(); + + final ImagePlus mvg = new BigWarpTestUtils.TestImagePlusBuilder().title("mvg") + .resolution(resolution) + .position(pt).build(); + + final BigWarpData bwData = BigWarpInit.initData(); + BigWarpInit.add(bwData, BigWarpInit.createSources(bwData, mvg, 0, 0, true)); + bwData.wrapMovingSources(); + + final LandmarkTableModel ltm = BigWarpTestUtils.identityLandmarks(3); + final List resList = transformToSpec( + mvg, + min, fov, resolution, + ltm); + + assertEquals(1, resList.size()); + final ImagePlus result = resList.get(0); + assertResolutionsEqual(resolution, result); + assertOriginsEqual(min, result); + + final Img img = ImageJFunctions.wrapByte(result); + assertArrayEquals("result image the wrong size", new long[]{2, 2, 2}, Intervals.dimensionsAsLongArray(img)); + + assertEquals(1, img.getAt(0, 0, 0).get()); + assertEquals(0, img.getAt(1, 1, 1).get()); + } + + @Test + public void testExportPtsSimple() { + + final double[] resolution = new double[]{1, 1, 1}; + final long[] pt = new long[]{16, 8, 4}; + + final ImagePlus mvg = new BigWarpTestUtils.TestImagePlusBuilder().title("mvg") + .resolution(resolution) + .position(pt).build(); + + final BigWarpData bwData = BigWarpInit.initData(); + BigWarpInit.add(bwData, BigWarpInit.createSources(bwData, mvg, 0, 0, true)); + bwData.wrapMovingSources(); + + // add a set of landmarks that define a 3x3x3 box around the non-zero pixel + final double[] min = new double[]{15.0, 7.0, 3.0}; + final double[] max = new double[]{17.0, 9.0, 5.0}; + final double[] step = new double[]{1.0, 1.0, 1.0}; + final long[] expectedResultSize = new long[]{3, 3, 3}; + + final LandmarkTableModel ltm = BigWarpTestUtils.identityLandmarks(3); + BigWarpTestUtils.addBboxLandmarks(ltm, new RealIntervalIterator(min, max, step), "bbox-%d"); + final List resList = transformToPtsSpec(mvg, resolution, ltm, "bbox.*"); + + assertEquals(1, resList.size()); + final ImagePlus result = resList.get(0); + assertResolutionsEqual(resolution, result); + assertOriginsEqual(min, result); + + final Img img = ImageJFunctions.wrapByte(result); + assertArrayEquals("result image the wrong size", expectedResultSize, Intervals.dimensionsAsLongArray(img)); + assertEquals(1, img.getAt(1, 1, 1).get()); + assertEquals(0, img.getAt(0, 0, 0).get()); + } + + private static void assertResolutionsEqual(ImagePlus expected, ImagePlus actual) { + + assertEquals("width", expected.getCalibration().pixelWidth, actual.getCalibration().pixelWidth, EPS); + assertEquals("height", expected.getCalibration().pixelHeight, actual.getCalibration().pixelHeight, EPS); + assertEquals("depth", expected.getCalibration().pixelDepth, actual.getCalibration().pixelDepth, EPS); + } + + private static void assertOriginsEqual(ImagePlus expected, ImagePlus actual) { + + assertEquals("origin x", expected.getCalibration().xOrigin, actual.getCalibration().xOrigin, EPS); + assertEquals("origin y", expected.getCalibration().yOrigin, actual.getCalibration().yOrigin, EPS); + assertEquals("origin z", expected.getCalibration().zOrigin, actual.getCalibration().zOrigin, EPS); + } + + private static void assertResolutionsEqual(double[] expected, ImagePlus actual) { + + assertEquals("width", expected[0], actual.getCalibration().pixelWidth, EPS); + assertEquals("height", expected[0], actual.getCalibration().pixelHeight, EPS); + assertEquals("depth", expected[2], actual.getCalibration().pixelDepth, EPS); + } + + private static void assertOriginsEqual(double[] expected, ImagePlus actual) { + + assertEquals("origin x", expected[0], actual.getCalibration().xOrigin, EPS); + assertEquals("origin y", expected[1], actual.getCalibration().yOrigin, EPS); + assertEquals("origin z", expected[2], actual.getCalibration().zOrigin, EPS); + } + + private static List transformToTarget(ImagePlus mvg, ImagePlus tgt, LandmarkTableModel ltm) { + + final BigWarpData bwData = BigWarpInit.createBigWarpDataFromImages(mvg, tgt); + bwData.wrapMovingSources(); + final BoundingBoxEstimation bboxEst = new BoundingBoxEstimation(BoundingBoxEstimation.Method.CORNERS); + final InvertibleRealTransform invXfm = new BigWarpTransform( ltm, BigWarpTransform.AFFINE ).getTransformation(); + + return ApplyBigwarpPlugin.apply( + bwData, + ltm, + invXfm, + BigWarpTransform.AFFINE, // tform type + ApplyBigwarpPlugin.TARGET, // fov option + null, + bboxEst, + ApplyBigwarpPlugin.TARGET, + null, + null, + null, + Interpolation.NEARESTNEIGHBOR, + false, // virtual + 1, // nThreads + true, + null, // writeOpts + false); + } + + private static List transformToSpec(final ImagePlus mvg, + final double[] offset, final double[] fov, final double[] res, + final LandmarkTableModel ltm) { + + ImagePlus tgt = null; + final BigWarpData bwData = BigWarpInit.createBigWarpDataFromImages(mvg, tgt); + bwData.wrapMovingSources(); + final BoundingBoxEstimation bboxEst = new BoundingBoxEstimation(BoundingBoxEstimation.Method.CORNERS); + final InvertibleRealTransform invXfm = new BigWarpTransform( ltm, BigWarpTransform.AFFINE ).getTransformation(); + + return ApplyBigwarpPlugin.apply( + bwData, + ltm, + invXfm, + BigWarpTransform.AFFINE, // tform type + ApplyBigwarpPlugin.SPECIFIED_PHYSICAL, // fov option + null, + bboxEst, + ApplyBigwarpPlugin.SPECIFIED, + res, + fov, + offset, + Interpolation.NEARESTNEIGHBOR, + false, // virtual + 1, // nThreads + true, + null, // writeOpts + false); + } + + private static List transformToPtsSpec(final ImagePlus mvg, + final double[] res, + final LandmarkTableModel ltm, + final String fieldOfViewPointFilter) { + + ImagePlus tgt = null; + final BigWarpData bwData = BigWarpInit.createBigWarpDataFromImages(mvg, tgt); + bwData.wrapMovingSources(); + final BoundingBoxEstimation bboxEst = new BoundingBoxEstimation(BoundingBoxEstimation.Method.CORNERS); + final InvertibleRealTransform invXfm = new BigWarpTransform( ltm, BigWarpTransform.AFFINE ).getTransformation(); + + return ApplyBigwarpPlugin.apply( + bwData, + ltm, + invXfm, + BigWarpTransform.AFFINE, // tform type + ApplyBigwarpPlugin.LANDMARK_POINTS, // fov option + fieldOfViewPointFilter, + bboxEst, + ApplyBigwarpPlugin.SPECIFIED, + res, + null, // fov + null, // offset + Interpolation.NEARESTNEIGHBOR, + false, // virtual + 1, // nThreads + true, + null, // writeOpts + false); + } + +}