diff --git a/src/main/java/org/janelia/saalfeldlab/n5/universe/metadata/MetadataUtils.java b/src/main/java/org/janelia/saalfeldlab/n5/universe/metadata/MetadataUtils.java index f66236d..e994d4b 100644 --- a/src/main/java/org/janelia/saalfeldlab/n5/universe/metadata/MetadataUtils.java +++ b/src/main/java/org/janelia/saalfeldlab/n5/universe/metadata/MetadataUtils.java @@ -30,8 +30,8 @@ public class MetadataUtils { public static double[] mul(final double[] a, final double[] b) { - final double[] out = new double[ a.length ]; - for( int i = 0; i < a.length; i++ ) + final double[] out = new double[a.length]; + for (int i = 0; i < a.length; i++) out[i] = a[i] * b[i]; return out; @@ -39,8 +39,8 @@ public static double[] mul(final double[] a, final double[] b) { public static double[] mul(final double[] a, final long[] b) { - final double[] out = new double[ a.length ]; - for( int i = 0; i < a.length; i++ ) + final double[] out = new double[a.length]; + for (int i = 0; i < a.length; i++) out[i] = a[i] * b[i]; return out; @@ -91,21 +91,22 @@ public static long[] updateDownsamplingFactors(final long factor, final long[] b return factors; } - public static CoordinateTransformation[] buildScaleTranslationTransformList( final double[] scale, final double[] translation ) { + public static CoordinateTransformation[] buildScaleTranslationTransformList(final double[] scale, final double[] translation) { + int nTforms = 0; - if( scale != null ) + if (scale != null) nTforms++; - if( translation != null ) + if (translation != null) nTforms++; final CoordinateTransformation[] coordinateTransformations = new CoordinateTransformation[nTforms]; int i = 0; - if( scale != null ) + if (scale != null) coordinateTransformations[i++] = new ScaleCoordinateTransformation(scale); - if( translation != null ) + if (translation != null) coordinateTransformations[i++] = new TranslationCoordinateTransformation(translation); return coordinateTransformations; @@ -120,64 +121,64 @@ public static CoordinateTransformation[] buildScaleTranslationTransformList( * @param datasetMetadata dataset metadata * @return the single scale metadata */ - public static N5SingleScaleMetadata setDatasetAttributes( final N5SingleScaleMetadata baseMetadata, final N5DatasetMetadata datasetMetadata ) - { - if( baseMetadata.getPath().equals( datasetMetadata.getPath() )) - return new N5SingleScaleMetadata( baseMetadata.getPath(), baseMetadata.spatialTransform3d(), + public static N5SingleScaleMetadata setDatasetAttributes(final N5SingleScaleMetadata baseMetadata, final N5DatasetMetadata datasetMetadata) { + + if (baseMetadata.getPath().equals(datasetMetadata.getPath())) + return new N5SingleScaleMetadata(baseMetadata.getPath(), baseMetadata.spatialTransform3d(), baseMetadata.getDownsamplingFactors(), baseMetadata.getPixelResolution(), baseMetadata.getOffset(), - baseMetadata.unit(), datasetMetadata.getAttributes() ); + baseMetadata.unit(), datasetMetadata.getAttributes()); else return null; } - public static N5SingleScaleMetadata[] updateChildrenDatasetAttributes( final N5SingleScaleMetadata[] baseMetadata, final N5DatasetMetadata[] datasetMetadata ) - { - final HashMap bases = new HashMap<>(); - Arrays.stream( baseMetadata ).forEach( x -> { bases.put( x.getPath(), x ); } ); + public static N5SingleScaleMetadata[] updateChildrenDatasetAttributes(final N5SingleScaleMetadata[] baseMetadata, + final N5DatasetMetadata[] datasetMetadata) { + + final HashMap bases = new HashMap<>(); + Arrays.stream(baseMetadata).forEach(x -> { + bases.put(x.getPath(), x); + }); - return ( N5SingleScaleMetadata[] ) Arrays.stream( datasetMetadata ).map( x -> { - final N5SingleScaleMetadata b = bases.get( x.getPath() ); - if( b == null ) + return (N5SingleScaleMetadata[])Arrays.stream(datasetMetadata).map(x -> { + final N5SingleScaleMetadata b = bases.get(x.getPath()); + if (b == null) return null; else - return setDatasetAttributes( b, x ); - } ).filter( x -> x != null ).toArray(); + return setDatasetAttributes(b, x); + }).filter(x -> x != null).toArray(); } - public static void updateChildrenMetadata( final N5TreeNode parent, final N5Metadata[] childrenMetadata, - final boolean relative ) - { - final HashMap children = new HashMap<>(); - Arrays.stream( childrenMetadata ).forEach( x -> { + public static void updateChildrenMetadata(final N5TreeNode parent, final N5Metadata[] childrenMetadata, + final boolean relative) { + + final HashMap children = new HashMap<>(); + Arrays.stream(childrenMetadata).forEach(x -> { final String absolutePath; - if( relative ) - { + if (relative) { absolutePath = normalizeGroupPath(parent.getPath() + "/" + x.getPath()); } else { absolutePath = x.getPath(); } - children.put( absolutePath, x ); + children.put(absolutePath, x); }); - parent.childrenList().forEach( c -> { + parent.childrenList().forEach(c -> { final N5Metadata m = children.get(MetadataUtils.normalizeGroupPath(c.getPath())); - if( m != null ) - c.setMetadata( m ); + if (m != null) + c.setMetadata(m); }); } - public static String canonicalPath( final N5TreeNode parent, final String child ) - { - return canonicalPath( parent.getPath(), child ); + public static String canonicalPath(final N5TreeNode parent, final String child) { + + return canonicalPath(parent.getPath(), child); } - public static String canonicalPath( final String parent, final String child ) - { - try - { - final N5URI url = new N5URI( "?/" + parent + "/" + child ); + public static String canonicalPath(final String parent, final String child) { + + try { + final N5URI url = new N5URI("?/" + parent + "/" + child); return url.normalizeGroupPath(); - } - catch ( final URISyntaxException e ) { } + } catch (final URISyntaxException e) {} return null; } @@ -191,21 +192,24 @@ public static String normalizeGroupPath(final String path) { return path; } - public static String relativePath( final String parent, final String child ) - { - try - { - final N5URI purl = new N5URI( "?" + parent ); - final N5URI curl = new N5URI( "?" + child ); - final Path ppath = Paths.get( purl.normalizeGroupPath()); - final Path cpath = Paths.get( curl.normalizeGroupPath()); - return ppath.relativize(cpath).toString(); - } - catch ( final URISyntaxException e ) - { - e.printStackTrace(); - } - return null; + /** + * Returns a relative group path from the child absolute path group path child + * the parent absolute group path. + * + * If the child path is not a descendent of parent, child will be returned. + * + * @param parent an absolute path + * @param child an absolute path + * @return relative path from child to parent, if it exists. + */ + public static String relativePath(final String parent, final String child) { + + try { + final String purl = new N5URI("?" + parent).normalizeGroupPath(); + final String curl = new N5URI("?" + child).normalizeGroupPath(); + return new N5URI("?" + curl.replaceFirst("^"+purl, "")).normalizeGroupPath(); + } catch (final URISyntaxException e) {} + return child; } /** @@ -215,13 +219,13 @@ public static String relativePath( final String parent, final String child ) * @param d exponent * @return result */ - public static double[] pow( final double[] x, final int d ) - { - final double[] y = new double[ x.length ]; - Arrays.fill( y, 1 ); - for ( int i = 0; i < d; i++ ) - for ( int j = 0; j < x.length; j++ ) - y[ j ] *= x[ j ]; + public static double[] pow(final double[] x, final int d) { + + final double[] y = new double[x.length]; + Arrays.fill(y, 1); + for (int i = 0; i < d; i++) + for (int j = 0; j < x.length; j++) + y[j] *= x[j]; return y; } @@ -234,6 +238,7 @@ public static double[] pow( final double[] x, final int d ) * @return a string */ public static String getStringNullable(final JsonElement element) { + if (element == null || element.isJsonNull()) return null; else @@ -252,10 +257,9 @@ public static AffineGet scaleTranslationTransforms(final double[] scale, final d if (translation != null) { - if( scale != null ) { + if (scale != null) { return new ScaleAndTranslation(scale, translation); - } - else { + } else { // scale null, translation not null if (translation.length == 2) return new Translation2D(translation); diff --git a/src/main/java/org/janelia/saalfeldlab/n5/universe/metadata/ome/ngff/v04/OmeNgffMultiScaleMetadata.java b/src/main/java/org/janelia/saalfeldlab/n5/universe/metadata/ome/ngff/v04/OmeNgffMultiScaleMetadata.java index 4f8dad6..47964ea 100644 --- a/src/main/java/org/janelia/saalfeldlab/n5/universe/metadata/ome/ngff/v04/OmeNgffMultiScaleMetadata.java +++ b/src/main/java/org/janelia/saalfeldlab/n5/universe/metadata/ome/ngff/v04/OmeNgffMultiScaleMetadata.java @@ -26,8 +26,6 @@ package org.janelia.saalfeldlab.n5.universe.metadata.ome.ngff.v04; import java.net.URISyntaxException; -import java.nio.file.Path; -import java.nio.file.Paths; import java.util.Arrays; import java.util.stream.DoubleStream; @@ -139,11 +137,9 @@ private static OmeNgffDataset[] buildDatasets( final String path, final NgffSing for( int i = 0; i < children.length; i++ ) { datasets[i] = new OmeNgffDataset(); -// final Path p = Paths.get(path); -// final Path c = Paths.get(children[i].getPath()); -// datasets[i].path = p.relativize(c).toString(); - datasets[i].path = Paths.get(path).relativize(Paths.get(children[i].getPath())).toString(); + datasets[i].path = MetadataUtils.relativePath(path, children[i].getPath()); datasets[i].coordinateTransformations = children[i].getCoordinateTransformations(); + } return datasets; } diff --git a/src/main/java/org/janelia/saalfeldlab/n5/universe/metadata/ome/ngff/v04/OmeNgffMultiScaleMetadataMutable.java b/src/main/java/org/janelia/saalfeldlab/n5/universe/metadata/ome/ngff/v04/OmeNgffMultiScaleMetadataMutable.java index dbc3144..595decc 100644 --- a/src/main/java/org/janelia/saalfeldlab/n5/universe/metadata/ome/ngff/v04/OmeNgffMultiScaleMetadataMutable.java +++ b/src/main/java/org/janelia/saalfeldlab/n5/universe/metadata/ome/ngff/v04/OmeNgffMultiScaleMetadataMutable.java @@ -1,10 +1,12 @@ package org.janelia.saalfeldlab.n5.universe.metadata.ome.ngff.v04; +import java.net.URISyntaxException; import java.nio.file.Paths; import java.util.ArrayList; import java.util.List; import org.janelia.saalfeldlab.n5.DatasetAttributes; +import org.janelia.saalfeldlab.n5.N5URI; import org.janelia.saalfeldlab.n5.universe.metadata.MetadataUtils; import org.janelia.saalfeldlab.n5.universe.metadata.axes.Axis; @@ -55,8 +57,7 @@ public void addChild(final int idx, final NgffSingleScaleAxesMetadata child) { final OmeNgffDataset dset = new OmeNgffDataset(); // paths are relative to this object - dset.path = Paths.get(getPath()).relativize(Paths.get(child.getPath())).toString(); - + dset.path = MetadataUtils.relativePath(getPath(), child.getPath()); dset.coordinateTransformations = child.getCoordinateTransformations(); if (idx < 0) { diff --git a/src/test/java/org/janelia/saalfeldlab/n5/universe/metadata/ome/ngff/v04/BuildMultiscaleTest.java b/src/test/java/org/janelia/saalfeldlab/n5/universe/metadata/ome/ngff/v04/BuildMultiscaleTest.java new file mode 100644 index 0000000..be54628 --- /dev/null +++ b/src/test/java/org/janelia/saalfeldlab/n5/universe/metadata/ome/ngff/v04/BuildMultiscaleTest.java @@ -0,0 +1,65 @@ +package org.janelia.saalfeldlab.n5.universe.metadata.ome.ngff.v04; + +import static org.junit.Assert.assertEquals; + +import org.janelia.saalfeldlab.n5.N5URI; +import org.janelia.saalfeldlab.n5.universe.metadata.axes.Axis; +import org.janelia.saalfeldlab.n5.universe.metadata.axes.AxisUtils; +import org.junit.Test; + +public class BuildMultiscaleTest { + + @Test + public void buildNgffMultiscale() { + + testHelper("", new String[]{"s0", "s1", "s2", "s3"}); + + testHelper("a", new String[]{"s0", "s1", "s2", "s3"}); + testHelper("a/b", new String[]{"s0", "s1", "s2", "s3"}); + testHelper("a/b/c", new String[]{"s0", "s1", "s2", "s3"}); + + testHelper("a", new String[]{"0/s0", "0/s1", "0/s2", "0/s3"}); + } + + private static void testHelper(final String path, final String[] childPaths) { + + final String downsampleMethod = "sampling"; + final Axis[] axes = AxisUtils.buildAxes("x", "y", "z"); + final OmeNgffMultiScaleMetadataMutable ms = new OmeNgffMultiScaleMetadataMutable(path); + + for (int i = 0; i < childPaths.length; i++) { + final double s = Math.pow(2, i); + ms.addChild(buildScaleLevelMetadata(childPaths[i], new double[]{s, s, s}, axes)); + } + + final OmeNgffMultiScaleMetadata meta = new OmeNgffMultiScaleMetadata(ms.getAxes().length, + path, path, downsampleMethod, "0.4", + ms.getAxes(), + ms.getDatasets(), null, + ms.coordinateTransformations, ms.metadata, true); + + for (int i = 0; i < childPaths.length; i++) { + assertEquals( + String.format("multiscale path incorrect for root: %s, child: %s", path, childPaths[i]), + childPaths[i], meta.getDatasets()[i].path); + } + + // test building children from multiscales + // these metadata's path variables must be relative to the root + final NgffSingleScaleAxesMetadata[] children = OmeNgffMultiScaleMetadata.buildMetadata(3, path, null, meta); + for (int i = 0; i < childPaths.length; i++) { + // ensure the paths are equal up to normalization + assertEquals( + String.format("single scale path incorrect for root: %s, child: %s", path, childPaths[i]), + N5URI.normalizeGroupPath(path + "/" + childPaths[i]), + N5URI.normalizeGroupPath(children[i].getPath())); + } + } + + private static NgffSingleScaleAxesMetadata buildScaleLevelMetadata(final String path, final double[] res, + final Axis[] axes) { + + return new NgffSingleScaleAxesMetadata(path, res, null, axes, null); + } + +}