Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 13 additions & 5 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
<parent>
<groupId>org.scijava</groupId>
<artifactId>pom-scijava</artifactId>
<version>39.0.0</version> <!-- 39.0.0 was released on 2024-10-15, see https://mvnrepository.com/artifact/org.scijava/pom-scijava -->
<version>45.1.0</version> <!-- 45.1.0 was released on 2026-07-19, see https://mvnrepository.com/artifact/org.scijava/pom-scijava -->
<relativePath />
</parent>

Expand Down Expand Up @@ -138,10 +138,18 @@
NOTE: To prevent attributes.json read errors the render n5 version should be kept in-sync with
the hot-knife n5 version if you need to run concurrent code from both repos on the same n5 group.
-->
<n5-version>3.3.0</n5-version>

<imglib2.version>7.1.4</imglib2.version>
<imglib2-algorithm.version>0.17.2</imglib2-algorithm.version>
<n5-version>4.0.1</n5-version>

<!-- pom-scijava 45 bumps Descriptor_based_registration to 3.0.1, whose SPIM_Registration replacement
(net.preibisch:multiview-reconstruction) drops the point-descriptor / DoG API render's geometric
descriptor matcher is built on (mpicbg.pointdescriptor.matcher.*, mpicbg.spim.segmentation.*). Porting
that matcher is a separate effort, so keep the 2.x line reactor-wide (render-app also pins 2.1.3). -->
<Descriptor_based_registration.version>2.1.3</Descriptor_based_registration.version>

<!-- pom-scijava 45 bumps Stitching_ to 3.1.10, which is compiled for JDK 21 and trips the
EnforceBytecodeVersion enforcer rule (render targets JDK 11). Keep 3.1.9 (pom-scijava 39's version,
JDK 8 bytecode); render only uses stable mpicbg.stitching.* / stitching.* APIs. -->
<Stitching.version>3.1.9</Stitching.version>

<jackson-version>2.14.3</jackson-version> <!-- NOTE: 2.15.3 causes NullPointerException in maven enforcer plugin -->

Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
package org.janelia.alignment.loader;

import com.google.common.base.Throwables;

import ij.process.ImageProcessor;

import org.apache.commons.lang.exception.ExceptionUtils;
import org.junit.Assert;
import org.junit.Test;

Expand All @@ -21,7 +22,7 @@ public void testLoad() {
try {
ip = loader.load(urlString);
} catch (final Throwable t) {
final String failureMessage = ExceptionUtils.getStackTrace(t);
final String failureMessage = Throwables.getStackTraceAsString(t);
Assert.fail("failed load with following exception: " + failureMessage);
}
Assert.assertEquals("invalid width", 5000, ip.getWidth());
Expand Down
9 changes: 9 additions & 0 deletions render-ws-java-client/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,15 @@
<dependency>
<groupId>sc.fiji</groupId>
<artifactId>bigdataviewer-vistools</artifactId>
<!-- pom-scijava 45 no longer manages this artifact; pin the version pom-scijava 39 provided. -->
<version>1.0.0-beta-36</version>
</dependency>

<!-- RestartSolverClient uses org.apache.commons.lang.math.DoubleRange (commons-lang 2.x). This used to
arrive transitively; declare it directly (version managed by pom-scijava at 2.6). -->
<dependency>
<groupId>commons-lang</groupId>
<artifactId>commons-lang</artifactId>
</dependency>

<dependency>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
import com.beust.jcommander.ParametersDelegate;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import com.google.gson.JsonPrimitive;

import java.io.IOException;
import java.nio.file.Files;
Expand All @@ -21,6 +20,7 @@
import net.imglib2.RandomAccess;
import net.imglib2.RandomAccessibleInterval;
import net.imglib2.img.basictypeaccess.AccessFlags;
import net.imglib2.type.NativeType;
import net.imglib2.type.numeric.RealType;
import net.imglib2.type.numeric.real.FloatType;

Expand All @@ -34,13 +34,9 @@
import org.janelia.render.client.parameter.CommandLineParameters;
import org.janelia.render.client.parameter.RenderWebServiceParameters;
import org.janelia.render.client.parameter.ZRangeParameters;
import org.janelia.saalfeldlab.n5.DataBlock;
import org.janelia.saalfeldlab.n5.DataType;
import org.janelia.saalfeldlab.n5.N5Reader;
import org.janelia.saalfeldlab.n5.imglib2.N5Utils;
import org.janelia.saalfeldlab.n5.universe.N5Factory;
import org.janelia.saalfeldlab.n5.zarr.ZarrDatasetAttributes;
import org.janelia.saalfeldlab.n5.zarr.ZarrKeyValueReader;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

Expand Down Expand Up @@ -417,117 +413,31 @@ private int[] readOptionalIntArray(final N5Reader reader, final String dataset)
}

/**
* Reads all values of a 1D coordinate array as doubles, or returns null if the array's metadata is not present.
* Reads all values of a 1D coordinate array as doubles, or returns null if the dataset is not present.
* <p>
* n5-zarr 1.3.5 fails to parse a {@code .zarray} whose {@code fill_value} is JSON {@code null} (which is how
* xarray writes coordinate arrays), so {@link N5Utils#open} cannot be used here. Instead the {@code .zarray}
* JSON is read directly, its {@code fill_value} is patched to a parseable value (it is irrelevant for chunks
* that are physically present), the resulting {@link ZarrDatasetAttributes} is built via the reader, and the
* chunks are read with {@link N5Reader#readBlock} (which honors the zarr little-endian byte order).
* Uses the same {@link N5Utils#open} overload as {@link #openHomogenizationArray} (see there for why).
* The imglib2 type handles unsigned promotion, so uint8/uint16 labels come out as their unsigned values.
*/
private double[] readCoordinateValues(final N5Reader reader, final String dataset) {
final JsonObject zArray = readZArrayJson(dataset);
if (zArray == null) {
private <T extends RealType<T> & NativeType<T>> double[] readCoordinateValues(final N5Reader reader,
final String dataset) {
if (!reader.datasetExists(dataset)) {
return null;
}
if (!zArray.has("fill_value") || zArray.get("fill_value").isJsonNull()) {
zArray.add("fill_value", new JsonPrimitive("0"));
}
if (!(reader instanceof ZarrKeyValueReader)) {
throw new IllegalArgumentException("expected a zarr reader but got " + reader.getClass().getName());
}
final ZarrDatasetAttributes attributes = ((ZarrKeyValueReader) reader).createDatasetAttributes(zArray);
if (attributes == null) {
throw new IllegalArgumentException("could not parse .zarray for coordinate dataset " + dataset);
}
if (attributes.getNumDimensions() != 1) {
final Consumer<IterableInterval<T>> noMissingBlockHandler = blocks -> { };
final RandomAccessibleInterval<T> rai = N5Utils.open(reader, dataset, noMissingBlockHandler, AccessFlags.setOf());
if (rai.numDimensions() != 1) {
throw new IllegalArgumentException("coordinate array " + dataset + " is expected to be 1-dimensional but has "
+ attributes.getNumDimensions() + " dimensions");
+ rai.numDimensions() + " dimensions");
}

final int length = (int) attributes.getDimensions()[0];
final int chunkSize = attributes.getBlockSize()[0];
final DataType dataType = attributes.getDataType();
final double[] values = new double[length];
final int numChunks = (int) Math.ceil((double) length / chunkSize);
for (int chunk = 0; chunk < numChunks; chunk++) {
final DataBlock<?> block = reader.readBlock(dataset, attributes, (long) chunk);
if (block == null) {
throw new IllegalArgumentException("missing chunk " + chunk + " of coordinate array " + dataset);
}
copyBlockValues(block.getData(), dataType, values, chunk * chunkSize);
final double[] values = new double[(int) rai.dimension(0)];
final RandomAccess<T> access = rai.randomAccess();
for (int i = 0; i < values.length; i++) {
values[i] = access.setPositionAndGet(i).getRealDouble();
}
return values;
}

/** Reads the raw .zarray JSON for a dataset from the per-array file, falling back to consolidated .zmetadata. */
private JsonObject readZArrayJson(final String dataset) {
final Path perArray = Paths.get(params.zarrPath, dataset, ".zarray");
try {
if (Files.isRegularFile(perArray)) {
return JsonParser.parseString(Files.readString(perArray)).getAsJsonObject();
}
} catch (final Exception e) {
LOG.warn("readZArrayJson: failed to read {} ({})", perArray, e.getMessage());
}
try {
final Path zMetadata = Paths.get(params.zarrPath, ".zmetadata");
if (Files.isRegularFile(zMetadata)) {
final JsonObject root = JsonParser.parseString(Files.readString(zMetadata)).getAsJsonObject();
final JsonObject metadata = root.getAsJsonObject("metadata");
if (metadata != null) {
final JsonObject zArray = metadata.getAsJsonObject(dataset + "/.zarray");
if (zArray != null) {
return zArray.deepCopy();
}
}
}
} catch (final Exception e) {
LOG.warn("readZArrayJson: failed to read .zmetadata for {} ({})", dataset, e.getMessage());
}
return null;
}

/** Copies a decoded data block into dst[offset...], applying unsigned promotion based on the data type. */
private static void copyBlockValues(final Object data, final DataType dataType, final double[] dst, final int offset) {
if (data instanceof byte[]) {
final byte[] a = (byte[]) data;
final boolean unsigned = dataType == DataType.UINT8;
for (int i = 0; i < a.length && offset + i < dst.length; i++) {
dst[offset + i] = unsigned ? (a[i] & 0xFF) : a[i];
}
} else if (data instanceof short[]) {
final short[] a = (short[]) data;
final boolean unsigned = dataType == DataType.UINT16;
for (int i = 0; i < a.length && offset + i < dst.length; i++) {
dst[offset + i] = unsigned ? (a[i] & 0xFFFF) : a[i];
}
} else if (data instanceof int[]) {
final int[] a = (int[]) data;
final boolean unsigned = dataType == DataType.UINT32;
for (int i = 0; i < a.length && offset + i < dst.length; i++) {
dst[offset + i] = unsigned ? (a[i] & 0xFFFFFFFFL) : a[i];
}
} else if (data instanceof long[]) {
final long[] a = (long[]) data;
for (int i = 0; i < a.length && offset + i < dst.length; i++) {
dst[offset + i] = a[i];
}
} else if (data instanceof float[]) {
final float[] a = (float[]) data;
for (int i = 0; i < a.length && offset + i < dst.length; i++) {
dst[offset + i] = a[i];
}
} else if (data instanceof double[]) {
final double[] a = (double[]) data;
for (int i = 0; i < a.length && offset + i < dst.length; i++) {
dst[offset + i] = a[i];
}
} else {
throw new IllegalArgumentException("unsupported coordinate block data type " + data.getClass());
}
}

/**
* Opens the (float32) homogenization array lazily.
* <p>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@
import org.janelia.saalfeldlab.googlecloud.GoogleCloudUtils;
import org.janelia.saalfeldlab.n5.KeyValueAccess;
import org.janelia.saalfeldlab.n5.LockedChannel;
import org.janelia.saalfeldlab.n5.N5Exception;
import org.janelia.saalfeldlab.n5.googlecloud.GoogleCloudStorageKeyValueAccess;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
Expand Down Expand Up @@ -659,7 +660,7 @@ void ensureWritableDirectory(final URI uri) {
if (!keyValueAccess.exists(uri.getPath())) {
try {
keyValueAccess.createDirectories(uri.getPath());
} catch (final IOException e) {
} catch (final N5Exception e) {
throw new RuntimeException("Could not create directory " + uri, e);
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
import org.janelia.saalfeldlab.n5.N5Writer;
import org.janelia.saalfeldlab.n5.imglib2.N5Utils;
import org.janelia.saalfeldlab.n5.universe.N5Factory;
import org.janelia.saalfeldlab.n5.universe.StorageFormat;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

Expand Down Expand Up @@ -106,7 +107,7 @@ public void run() {

// Read and cache some metadata of the tissue and mask datasets
// Assume that the tissue is a multiscale pyramid / mask is a standalone dataset
try (final N5Reader n5 = new N5Factory().openReader(N5Factory.StorageFormat.N5, param.n5Path)) {
try (final N5Reader n5 = new N5Factory().openReader(StorageFormat.N5, param.n5Path)) {
LOG.info("Reading metadata from {}", param.n5Path);
tissueAttributes = ExtendedAttributes.read(n5, param.fullDataset(), param.dataset);
maskAttributes = ExtendedAttributes.read(n5, param.mask, param.mask);
Expand All @@ -118,7 +119,7 @@ public void run() {
throw new IllegalArgumentException("Dataset '" + param.output + "' is different from the input dataset and already exists. Stopping.");
} else {
LOG.info("Output dataset is '{}'. Creating new dataset.", param.output);
try (final N5Writer n5Writer = new N5Factory().openWriter(N5Factory.StorageFormat.N5, param.n5Path)) {
try (final N5Writer n5Writer = new N5Factory().openWriter(StorageFormat.N5, param.n5Path)) {
n5Writer.createDataset(param.output, tissueAttributes.attrs);
}
}
Expand Down Expand Up @@ -179,7 +180,7 @@ private static Grid.Block translateAndCheckHomogeneity(

// Read the mask block and check if it is homogeneous
boolean isHomogeneous = true;
try (final N5Reader n5 = new N5Factory().openReader(N5Factory.StorageFormat.N5, param.n5Path)) {
try (final N5Reader n5 = new N5Factory().openReader(StorageFormat.N5, param.n5Path)) {
final Img<UnsignedByteType> mask = N5Utils.open(n5, param.mask);
final Interval interval = Intervals.intersect(mask, block);
final RandomAccessibleInterval<UnsignedByteType> maskPixels = Views.interval(mask, interval);
Expand Down Expand Up @@ -240,7 +241,7 @@ private static void inpaintBlock(
// Preallocate the inpainted block
final Img<UnsignedByteType> inpaintedBlock = ArrayImgs.unsignedBytes(block.dimensions);

try (final N5Reader n5 = new N5Factory().openReader(N5Factory.StorageFormat.N5, param.n5Path)) {
try (final N5Reader n5 = new N5Factory().openReader(StorageFormat.N5, param.n5Path)) {
// Load and translate the tissue and mask data
LOG.info("Loading data at {}", block.offset);
final Img<UnsignedByteType> rawTissue = N5Utils.open(n5, param.fullDataset());
Expand All @@ -265,7 +266,7 @@ private static void inpaintBlock(
LOG.info("Finished inpainting in {} ms", System.currentTimeMillis() - start);
}

try (final N5Writer n5Writer = new N5Factory().openWriter(N5Factory.StorageFormat.N5, param.n5Path)) {
try (final N5Writer n5Writer = new N5Factory().openWriter(StorageFormat.N5, param.n5Path)) {
N5Utils.saveBlock(inpaintedBlock, n5Writer, param.output, targetAttributes, block.gridPosition);
LOG.info("Wrote tissue block to '{}'", param.output);
} catch (final Exception e) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
import org.janelia.saalfeldlab.n5.spark.downsample.N5DownsamplerSpark;
import org.janelia.saalfeldlab.n5.spark.supplier.N5WriterSupplier;
import org.janelia.saalfeldlab.n5.universe.N5Factory;
import org.janelia.saalfeldlab.n5.universe.StorageFormat;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

Expand Down Expand Up @@ -96,7 +97,7 @@ public void run(final JavaSparkContext sparkContext)
basePathOrStorageUrl, sZeroDatasetPath, Arrays.toString(downsampleFactors), requiredSLevel, retryParameters);

final N5WriterSupplier n5Supplier = () ->
new N5Factory().openWriter(N5Factory.StorageFormat.N5, basePathOrStorageUrl);
new N5Factory().openWriter(StorageFormat.N5, basePathOrStorageUrl);

final N5Writer n5 = n5Supplier.get();
final DatasetAttributes fullScaleAttributes = n5.getDatasetAttributes(sZeroDatasetPath);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@
import org.janelia.saalfeldlab.n5.N5Writer;
import org.janelia.saalfeldlab.n5.imglib2.N5Utils;
import org.janelia.saalfeldlab.n5.universe.N5Factory;
import org.janelia.saalfeldlab.n5.universe.N5Factory.StorageFormat;
import org.janelia.saalfeldlab.n5.universe.StorageFormat;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import org.janelia.saalfeldlab.n5.N5Writer;
import org.janelia.saalfeldlab.n5.spark.supplier.N5WriterSupplier;
import org.janelia.saalfeldlab.n5.universe.N5Factory;
import org.janelia.saalfeldlab.n5.universe.StorageFormat;

/**
* Utilities for N5 operations.
Expand All @@ -22,7 +23,7 @@ public N5PathSupplier(final String path) {
@Override
public N5Writer get()
throws IOException {
return new N5Factory().openWriter(N5Factory.StorageFormat.N5, path);
return new N5Factory().openWriter(StorageFormat.N5, path);
}
}

Expand Down
12 changes: 12 additions & 0 deletions render-ws/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,12 @@
<groupId>jakarta.validation</groupId>
<artifactId>jakarta.validation-api</artifactId>
</exclusion>
<!-- javax.annotation classes covered by javax.annotation:javax.annotation-api;
the newer resteasy BOM (pom-scijava 45) adds this spec jar, causing duplicate classes -->
<exclusion>
<groupId>org.jboss.spec.javax.annotation</groupId>
<artifactId>jboss-annotations-api_1.3_spec</artifactId>
</exclusion>
</exclusions>
</dependency>

Expand All @@ -141,6 +147,12 @@
<groupId>jakarta.validation</groupId>
<artifactId>jakarta.validation-api</artifactId>
</exclusion>
<!-- javax.annotation classes covered by javax.annotation:javax.annotation-api;
the newer resteasy BOM (pom-scijava 45) adds this spec jar, causing duplicate classes -->
<exclusion>
<groupId>org.jboss.spec.javax.annotation</groupId>
<artifactId>jboss-annotations-api_1.3_spec</artifactId>
</exclusion>
</exclusions>
</dependency>

Expand Down
Loading