Skip to content

Commit

Permalink
feat: methods for permuting metadata axes
Browse files Browse the repository at this point in the history
* for use by downstream importers
  • Loading branch information
bogovicj committed May 9, 2024
1 parent 5b01482 commit d457a1f
Show file tree
Hide file tree
Showing 6 changed files with 434 additions and 43 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,16 @@
import java.net.URISyntaxException;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Optional;

import org.apache.commons.lang3.ArrayUtils;
import org.janelia.saalfeldlab.n5.DatasetAttributes;
import org.janelia.saalfeldlab.n5.N5URI;
import org.janelia.saalfeldlab.n5.universe.N5TreeNode;
import org.janelia.saalfeldlab.n5.universe.metadata.N5CosemMetadata.CosemTransform;
import org.janelia.saalfeldlab.n5.universe.metadata.axes.Axis;
import org.janelia.saalfeldlab.n5.universe.metadata.axes.AxisUtils;
import org.janelia.saalfeldlab.n5.universe.metadata.ome.ngff.v04.NgffSingleScaleAxesMetadata;
import org.janelia.saalfeldlab.n5.universe.metadata.ome.ngff.v04.coordinateTransformations.CoordinateTransformation;
import org.janelia.saalfeldlab.n5.universe.metadata.ome.ngff.v04.coordinateTransformations.ScaleCoordinateTransformation;
import org.janelia.saalfeldlab.n5.universe.metadata.ome.ngff.v04.coordinateTransformations.TranslationCoordinateTransformation;
Expand All @@ -16,17 +21,21 @@
import com.google.gson.JsonNull;

import net.imglib2.realtransform.AffineGet;
import net.imglib2.realtransform.AffineTransform3D;
import net.imglib2.realtransform.Scale;
import net.imglib2.realtransform.Scale2D;
import net.imglib2.realtransform.Scale3D;
import net.imglib2.realtransform.ScaleAndTranslation;
import net.imglib2.realtransform.Translation;
import net.imglib2.realtransform.Translation2D;
import net.imglib2.realtransform.Translation3D;
import net.imglib2.util.Pair;

public class MetadataUtils {

// duplicate variables in N5ScalePyramidExporter in n5-ij
public static final String DOWN_SAMPLE = "Sample";
public static final String DOWN_AVERAGE = "Average";

public static double[] mul(final double[] a, final double[] b) {

final double[] out = new double[a.length];
Expand Down Expand Up @@ -116,7 +125,7 @@ public static ScaleAndTranslation scaleTranslationFromCoordinateTransformations(
if (cts == null || cts.length == 0)
return null;

ScaleAndTranslation out = coordinateTransformToScaleAndTranslation(cts[0]);
final ScaleAndTranslation out = coordinateTransformToScaleAndTranslation(cts[0]);
for (int i = 1; i < cts.length; i++) {
out.preConcatenate(coordinateTransformToScaleAndTranslation(cts[i]));
}
Expand Down Expand Up @@ -218,7 +227,7 @@ public static String normalizeGroupPath(final String path) {
}

/**
* Returns a relative group path from the child absolute path group path child
* 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.
Expand Down Expand Up @@ -307,4 +316,196 @@ else if (scale.length == 3)
return null;
}

@SuppressWarnings("unchecked")
public static <M extends N5DatasetMetadata> M metadataForThisScale(final String newPath,
final M baseMetadata,
final String downsampleMethod,
final double[] baseResolution,
final double[] absoluteDownsamplingFactors,
final double[] absoluteScale,
final double[] absoluteTranslation) {

if (baseMetadata == null)
return null;

/**
* if metadata is N5SingleScaleMetadata and not a subclass of it then this is using N5Viewer
* metadata which does not have an offset
*/
if (baseMetadata.getClass().equals(N5SingleScaleMetadata.class)) {
return (M)buildN5VMetadata(newPath, (N5SingleScaleMetadata)baseMetadata, downsampleMethod, baseResolution, absoluteDownsamplingFactors);
} else if (baseMetadata instanceof N5CosemMetadata) {
return (M)buildCosemMetadata(newPath, (N5CosemMetadata)baseMetadata, absoluteScale, absoluteTranslation);

} else if (baseMetadata instanceof NgffSingleScaleAxesMetadata) {
return (M)buildNgffMetadata(newPath, (NgffSingleScaleAxesMetadata)baseMetadata, absoluteScale, absoluteTranslation);
} else
return baseMetadata;
}

public static N5SingleScaleMetadata buildN5VMetadata(
final String path,
final N5SingleScaleMetadata baseMetadata,
final String downsampleMethod,
final double[] baseResolution,
final double[] downsamplingFactors) {

/**
* N5Viewer metadata doesn't have a way to directly represent offset. Rather, the half-pixel
* offsets that averaging downsampling introduces are assumed when downsampling factors are
* not equal to ones.
*
* As a result, we use downsampling factors with average downsampling, but set the factors
* to one otherwise.
*/
final int nd = baseResolution.length > 3 ? 3 : baseResolution.length;
final double[] resolution = new double[nd];
final double[] factors = new double[nd];

if (downsampleMethod.equals(DOWN_AVERAGE)) {
System.arraycopy(baseResolution, 0, resolution, 0, nd);
System.arraycopy(downsamplingFactors, 0, factors, 0, nd);
} else {
for (int i = 0; i < nd; i++)
resolution[i] = baseResolution[i] * downsamplingFactors[i];

Arrays.fill(factors, 1);
}

final AffineTransform3D transform = new AffineTransform3D();
for (int i = 0; i < nd; i++)
transform.set(resolution[i], i, i);

return new N5SingleScaleMetadata(
path,
transform,
factors,
resolution,
baseMetadata.getOffset(),
baseMetadata.unit(),
baseMetadata.getAttributes(),
baseMetadata.minIntensity(),
baseMetadata.maxIntensity(),
baseMetadata.isLabelMultiset());

}

public static N5CosemMetadata buildCosemMetadata(
final String path,
final N5CosemMetadata baseMetadata,
final double[] absoluteResolution,
final double[] absoluteTranslation) {

final double[] resolution = new double[absoluteResolution.length];
System.arraycopy(absoluteResolution, 0, resolution, 0, absoluteResolution.length);

final double[] translation = new double[absoluteTranslation.length];
System.arraycopy(absoluteTranslation, 0, translation, 0, absoluteTranslation.length);

return new N5CosemMetadata(
path,
new CosemTransform(
baseMetadata.getCosemTransform().axes,
resolution,
translation,
baseMetadata.getCosemTransform().units),
baseMetadata.getAttributes());
}

public static NgffSingleScaleAxesMetadata buildNgffMetadata(
final String path,
final NgffSingleScaleAxesMetadata baseMetadata,
final double[] absoluteResolution,
final double[] absoluteTranslation) {

final double[] resolution = new double[absoluteResolution.length];
System.arraycopy(absoluteResolution, 0, resolution, 0, absoluteResolution.length);

final double[] translation = new double[absoluteTranslation.length];
System.arraycopy(absoluteTranslation, 0, translation, 0, absoluteTranslation.length);

return new NgffSingleScaleAxesMetadata(
path,
resolution,
translation,
baseMetadata.getAxes(),
baseMetadata.getAttributes());
}

@SuppressWarnings("unchecked")
public static <M extends N5Metadata> M permuteSpatialMetadata(final M metadata, final int[] axisPermutation) {

if (metadata == null)
return null;

/**
* if metadata is N5SingleScaleMetadata and not a subclass of it then this is using N5Viewer
* metadata which does not have an offset
*/
if (metadata.getClass().equals(N5SingleScaleMetadata.class)) {
return (M)permuteN5vMetadata((N5SingleScaleMetadata)metadata, axisPermutation);
} else if (metadata instanceof N5CosemMetadata) {
return (M)permuteCosemMetadata((N5CosemMetadata)metadata, axisPermutation);
} else if (metadata instanceof NgffSingleScaleAxesMetadata) {
return (M)permuteNgffMetadata((NgffSingleScaleAxesMetadata)metadata, axisPermutation);
} else
return metadata;
}

public static NgffSingleScaleAxesMetadata permuteNgffMetadata(final NgffSingleScaleAxesMetadata metadata, int[] axisPermutation) {

final Axis[] axes = metadata.getAxes();
final Axis[] axesPermuted = new Axis[axes.length];
for (int i = 0; i < axes.length; i++)
axesPermuted[i] = axes[i];

AxisUtils.permute(axesPermuted, axesPermuted, axisPermutation);

return new NgffSingleScaleAxesMetadata(
metadata.getPath(),
AxisUtils.permute(metadata.getScale(), axisPermutation),
AxisUtils.permute(metadata.getTranslation(), axisPermutation),
axesPermuted,
metadata.getAttributes());
}

public static N5CosemMetadata permuteCosemMetadata(final N5CosemMetadata metadata, int[] axisPermutation) {

final double[] newScales = AxisUtils.permute(metadata.getCosemTransform().scale, axisPermutation);
final double[] newTranslation = AxisUtils.permute(metadata.getCosemTransform().translate, axisPermutation);

final String[] newAxes = ArrayUtils.clone(metadata.getCosemTransform().axes);
AxisUtils.permute(newAxes, newAxes, axisPermutation);

final String[] newUnits = ArrayUtils.clone(metadata.getCosemTransform().units);
AxisUtils.permute(newUnits, newUnits, axisPermutation);

return new N5CosemMetadata(
metadata.getPath(),
new CosemTransform(newAxes, newScales, newTranslation, newUnits),
metadata.getAttributes());
}

public static N5SingleScaleMetadata permuteN5vMetadata(final N5SingleScaleMetadata metadata, int[] axisPermutation) {

final double[] newScales = AxisUtils.permute(metadata.getPixelResolution(), axisPermutation);
final double[] newOffset = AxisUtils.permute(metadata.getOffset(), axisPermutation);
final double[] newFactors = AxisUtils.permute(metadata.getDownsamplingFactors(), axisPermutation);

final AffineTransform3D offsetTform = new AffineTransform3D();
offsetTform.translate(newOffset);

final AffineTransform3D transform = N5SingleScaleMetadataParser.buildTransform(newFactors, newScales, Optional.of(offsetTform));

return new N5SingleScaleMetadata(
metadata.getPath(),
transform,
newFactors,
newScales,
newOffset,
metadata.unit(),
metadata.getAttributes());

}

}
Original file line number Diff line number Diff line change
Expand Up @@ -54,15 +54,28 @@ public NgffSingleScaleAxesMetadata(final String path,

this.path = MetadataUtils.normalizeGroupPath(path);

this.scale = scale;
this.translation = translation;
this.scale = scale != null ? scale : ones(axes.length);
this.translation = translation != null ? translation : new double[axes.length];
this.axes = axes;

this.datasetAttributes = datasetAttributes;

coordinateTransformations = MetadataUtils.buildScaleTranslationTransformList(scale, translation);
coordinateTransformations = MetadataUtils.buildScaleTranslationTransformList(this.scale, this.translation);
if (Arrays.stream(axes).allMatch(x -> x.getType().equals(Axis.SPACE))) {
this.transform = MetadataUtils.scaleTranslationTransforms(this.scale, this.translation);
} else {
final int[] spaceIndexes = AxisUtils.indexes(axes, x -> x.getType().equals(Axis.SPACE));
final double[] spaceScale = AxisUtils.permute(this.scale, spaceIndexes);
final double[] spaceTranslation = AxisUtils.permute(this.translation, spaceIndexes);
this.transform = MetadataUtils.scaleTranslationTransforms(spaceScale, spaceTranslation);
}
}

private static double[] ones(final int N) {

this.transform = MetadataUtils.scaleTranslationTransforms(scale, translation);
final double[] ones = new double[N];
Arrays.fill(ones, 1);
return ones;
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -93,13 +93,17 @@ public Optional<OmeNgffMetadata> parseMetadata(final N5Reader n5, final N5TreeNo
attrs[i] = dsetMeta[i].getAttributes();
}

// maybe axes can be flipped first?
ArrayUtils.reverse(ms.axes);

final NgffSingleScaleAxesMetadata[] msChildrenMeta = OmeNgffMultiScaleMetadata.buildMetadata(
nd, node.getPath(), ms.datasets, attrs, ms.coordinateTransformations, ms.metadata, ms.axes);

MetadataUtils.updateChildrenMetadata(node, msChildrenMeta, false);

// axes need to be flipped after the child is created
ArrayUtils.reverse(ms.axes);
// is this actually true?
// ArrayUtils.reverse(ms.axes);

multiscales[j] = new OmeNgffMultiScaleMetadata(ms, msChildrenMeta);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,9 @@
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;

Expand Down
Loading

0 comments on commit d457a1f

Please sign in to comment.