![]() |
VOOZH | about |
dotnet add package ZarrNET --version 0.6.1
NuGet\Install-Package ZarrNET -Version 0.6.1
<PackageReference Include="ZarrNET" Version="0.6.1" />
<PackageVersion Include="ZarrNET" Version="0.6.1" />Directory.Packages.props
<PackageReference Include="ZarrNET" />Project file
paket add ZarrNET --version 0.6.1
#r "nuget: ZarrNET, 0.6.1"
#:package ZarrNET@0.6.1
#addin nuget:?package=ZarrNET&version=0.6.1Install as a Cake Addin
#tool nuget:?package=ZarrNET&version=0.6.1Install as a Cake Tool
A high-performance, fully async C# library for reading and writing OME-Zarr datasets with comprehensive support for multiscale images, labels, and High-Content Screening (HCS) plate data.
โ
Zarr v2 & v3 Support - Automatic version detection and handling
โ
OME-Zarr 0.4 & 0.5 - Full spec compliance for multiscale images, labels, and HCS plates
โ
Remote Access - Read from HTTP/HTTPS, S3
โ
Physical Coordinates - ROI reading in real-world units (micrometers, seconds, etc.)
โ
Compression - Blosc, Gzip, Zstandard (zstd) codec support
โ
Memory Efficient - Chunk-level reading with streaming support
โ
Type Safe - Strongly typed metadata models and coordinate transformations
โ
Cross-Platform - .NET 10.0, works on Windows, Linux, macOS
โ
Well-Architected - Clean separation of concerns, testable, extensible
# Via NuGet
dotnet add package ZarrNET
# Or clone and build locally
git clone https://github.com/BiologyTools/Zarr.NET.git
cd ZarrNET
dotnet build
using ZarrNET;
using ZarrNET.Helpers;
await using var reader = await OmeZarrReader.OpenAsync("/path/to/dataset.zarr");
var image = reader.AsMultiscaleImage();
var level = await image.OpenResolutionLevelAsync(datasetIndex: 0);
// Read timepoint 0, channel 1, z-slice 5
var plane = await level.ReadPlaneAsync(t: 0, c: 1, z: 5);
// Get as typed 2D array
ushort[,] pixels = plane.As2DArray<ushort>(); // for uint16 data
Console.WriteLine($"Plane: {plane.Width}x{plane.Height}, max value: {pixels.Cast<ushort>().Max()}");
// Or get as byte array for interop
byte[] bytes = plane.ToBytes<ushort>(PixelFormat.Gray8);
using ZarrNET.Coordinates;
// Define ROI: 100ยตm x 100ยตm region at specific location
var roi = new PhysicalROI(
origin: [0, 0, 5.0, 50.0, 50.0], // t, c, z, y, x in physical units
size: [1, 1, 1.0, 100.0, 100.0] // timepoint, channel, z-slice, 100ยตm x 100ยตm
);
var result = await level.ReadRegionAsync(roi);
// Public HTTP server
var httpUrl = "https://example.com/datasets/image.zarr";
await using var reader = await OmeZarrReader.OpenAsync(httpUrl);
// S3 public bucket
var s3Url = "https://s3.amazonaws.com/bucket/image.zarr";
await using var s3Reader = await OmeZarrReader.OpenAsync(s3Url);
// Works exactly the same as local files
var image = reader.AsMultiscaleImage();
var plane = await level.ReadPlaneAsync(t: 0, c: 0, z: 0);
using OmeZarr.Core.Zarr.Store;
var httpClient = new HttpClient();
httpClient.DefaultRequestHeaders.Add("Authorization", "Bearer YOUR_TOKEN");
httpClient.Timeout = TimeSpan.FromMinutes(10);
var store = new HttpZarrStore("https://example.com/data.zarr", httpClient);
await using var reader = await OmeZarrReader.OpenAsync(store);
var plate = reader.AsPlate();
Console.WriteLine($"Plate: {plate.PlateMetadata.Name}");
Console.WriteLine($"Wells: {plate.Wells.Count}");
// Open well B3, field 0
var well = await plate.OpenWellAsync("B", "3");
var field = await well.OpenFieldAsync(0);
var level = await field.OpenResolutionLevelAsync(0);
// Read a plane from the field
var plane = await level.ReadPlaneAsync(c: 0, z: 0);
var image = reader.AsMultiscaleImage();
if (await image.HasLabelsAsync())
{
var labelGroup = await image.OpenLabelsAsync();
Console.WriteLine($"Available labels: {string.Join(", ", labelGroup.LabelNames)}");
var cellLabel = await labelGroup.OpenLabelAsync("cells");
var labelLevel = await cellLabel.OpenResolutionLevelAsync(0);
var labelPlane = await labelLevel.ReadPlaneAsync(t: 0, c: 0, z: 0);
var labelIds = labelPlane.As1DArray<uint>(); // typically uint32
var uniqueCells = labelIds.Distinct().Count(id => id != 0);
Console.WriteLine($"Detected {uniqueCells} cells");
}
The library is structured in clean, composable layers:
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ OmeZarrReader (Public API) โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโค
โ Node Tree Layer โ
โ - MultiscaleNode, PlateNode, WellNode, FieldNode โ
โ - ResolutionLevelNode, LabelNode โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโค
โ Coordinate Transform Layer โ
โ - PhysicalROI โ PixelRegion โ
โ - CoordinateTransformService โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโค
โ OME-Zarr Metadata Layer โ
โ - MultiscaleMetadata, PlateMetadata, etc. โ
โ - OmeAttributesParser โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโค
โ Zarr Array/Group Layer โ
โ - ZarrArray (chunk reading, region extraction) โ
โ - ZarrGroup (tree navigation) โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโค
โ Codec Pipeline โ
โ - BytesCodec, GzipCodec, ZstdCodec โ
โ - CodecPipeline (ordered application) โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโค
โ Store Layer (I/O) โ
โ - IZarrStore, LocalFileSystemStore โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
uint8, uint16, float32, float64, complex64, complex128The lower-level dtype parser also understands the core fixed-size integer and
floating-point Zarr dtypes, including v2 NumPy dtype strings such as "<f4" /
"<f8" / "<c8" / "<c16" and v3 data type names such as "float32" /
"float64" / "complex64" / "complex128".
.zarray, .zattrs, .zgroup files (widely used by Fiji, napari, OMERO)zarr.json per node (newer spec)OmeZarrReaderEntry point for opening datasets. Auto-detects node type.
await using var reader = await OmeZarrReader.OpenAsync(path);
var root = reader.OpenRoot(); // Auto-dispatch to correct type
// Or strongly-typed access
var image = reader.AsMultiscaleImage();
var plate = reader.AsPlate();
var well = reader.AsWell();
ResolutionLevelNodeRepresents a single resolution level in a multiscale pyramid.
var level = await image.OpenResolutionLevelAsync(datasetIndex: 0);
// Properties
long[] shape = level.Shape; // [t, c, z, y, x]
string dtype = level.DataType; // "uint16", "float32", etc.
double[] pixelSize = level.GetPixelSize(); // Physical size per pixel
// Reading
var plane = await level.ReadPlaneAsync(t: 0, c: 0, z: 5);
var roi = await level.ReadRegionAsync(physicalROI);
var region = await level.ReadPixelRegionAsync(pixelRegion);
// Writing
await level.WriteRegionAsync(pixelRegion, data);
For larger-than-memory and chunk-native workflows, open the underlying Zarr array and work directly with full chunks.
await using var store = new LocalFileSystemStore("/path/to/data.zarr");
var root = await ZarrGroup.OpenRootAsync(store);
var array = await root.OpenArrayAsync("0");
await foreach (var chunk in array.EnumerateChunksAsync())
{
// Decoded path: full logical chunk buffer in array order.
byte[] decoded = await array.ReadChunkDecodedAsync(chunk);
await array.WriteChunkDecodedAsync(chunk, decoded);
// Encoded path: copy compressed bytes when metadata/codecs are compatible.
byte[]? encoded = await array.ReadChunkEncodedAsync(chunk);
if (encoded is not null)
await array.WriteChunkEncodedAsync(chunk, encoded);
}
ZarrChunkRef.Shape gives the valid in-array extent for edge chunks. Decoded
chunk buffers are padded to the full effective chunk shape, matching the region
reader's fill-value behaviour. Encoded chunk access is available for non-sharded
arrays; sharded arrays store logical chunks inside shard objects.
PlaneResultResult of reading a 2D plane with convenience extraction methods.
var plane = await level.ReadPlaneAsync(t: 0, c: 0, z: 0);
int width = plane.Width;
int height = plane.Height;
// Extract as typed arrays
ushort[,] pixels2D = plane.As2DArray<ushort>();
ushort[] pixels1D = plane.As1DArray<ushort>();
// Convert to specific formats
byte[] gray8 = plane.ToBytes<ushort>(PixelFormat.Gray8);
byte[] bgra32 = plane.ToBytes<ushort>(PixelFormat.Bgra32);
PhysicalROI and PixelRegionCoordinate representations for ROI specification.
// Physical coordinates (microns, seconds, etc.)
var physicalROI = new PhysicalROI(
origin: [0, 0, 5.0, 100.0, 200.0],
size: [1, 1, 2.0, 50.0, 50.0]
);
// Pixel coordinates (array indices)
var pixelRegion = new PixelRegion(
start: [0, 0, 10, 100, 200],
end: [1, 1, 12, 150, 250]
);
var axes = level.Multiscale.Axes;
var zIndex = Array.FindIndex(axes, a => a.Name.Equals("z", StringComparison.OrdinalIgnoreCase));
var numSlices = (int)level.Shape[zIndex];
for (int z = 0; z < numSlices; z++)
{
var plane = await level.ReadPlaneAsync(t: 0, c: 0, z: z);
var pixels = plane.As1DArray<ushort>();
double meanIntensity = pixels.Average(p => (double)p);
Console.WriteLine($"Z={z}: mean intensity = {meanIntensity:F1}");
}
var cIndex = Array.FindIndex(axes, a => a.Name.Equals("c", StringComparison.OrdinalIgnoreCase));
var numChannels = (int)level.Shape[cIndex];
var channels = new List<ushort[,]>();
for (int c = 0; c < numChannels; c++)
{
var plane = await level.ReadPlaneAsync(t: 0, c: c, z: 0);
channels.Add(plane.As2DArray<ushort>());
}
// Create RGB composite, max projection, etc.
var levels = await image.OpenAllResolutionLevelsAsync();
double targetMicronsPerPixel = 0.5;
var spatialAxisIndex = 3; // y axis in t,c,z,y,x
var bestLevel = levels
.Select((l, i) => (level: l, index: i, pixelSize: l.GetPixelSize()[spatialAxisIndex]))
.OrderBy(l => Math.Abs(l.pixelSize - targetMicronsPerPixel))
.First();
Console.WriteLine($"Using level {bestLevel.index} ({bestLevel.pixelSize:G4} ยตm/px)");
var plate = reader.AsPlate();
foreach (var wellRef in plate.Wells)
{
var well = await plate.OpenWellAsync(wellRef.Path);
foreach (var fieldRef in well.Fields)
{
var field = await well.OpenFieldAsync(fieldRef.Path);
var level = await field.OpenResolutionLevelAsync(0);
// Process each field
var plane = await level.ReadPlaneAsync(c: 0, z: 0);
// ... analyze, segment, etc.
}
}
using SixLabors.ImageSharp;
using SixLabors.ImageSharp.PixelFormats;
var plane = await level.ReadPlaneAsync(t: 0, c: 0, z: 5);
byte[] pixels = plane.ToBytes<ushort>(PixelFormat.Gray8);
var img = Image.LoadPixelData<L8>(pixels, plane.Width, plane.Height);
img.SaveAsPng("output.png");
For best performance, align ROI boundaries to chunk boundaries when possible:
var chunkShape = level.ZarrArray.Metadata.ChunkShape; // e.g., [1, 1, 1, 96, 96]
// Good: aligned to chunk boundaries (multiples of 96)
var alignedROI = new PixelRegion(
start: [0, 0, 0, 0, 0],
end: [1, 1, 1, 96, 192] // 1x2 chunks
);
// Less efficient: crosses chunk boundaries
var unalignedROI = new PixelRegion(
start: [0, 0, 0, 50, 50],
end: [1, 1, 1, 150, 150] // touches 4 chunks
);
Use lower resolution levels for overview/navigation, full resolution for analysis:
// Level 0: Full resolution (slow, detailed)
// Level 3: 1/8 resolution (fast, overview)
var overview = await image.OpenResolutionLevelAsync(datasetIndex: 3);
var fullRes = await image.OpenResolutionLevelAsync(datasetIndex: 0);
For large datasets, process in tiles rather than loading entire planes:
int tileSize = 512;
for (int y = 0; y < height; y += tileSize)
{
for (int x = 0; x < width; x += tileSize)
{
var tileRegion = new PixelRegion(
start: [0, 0, 0, y, x],
end: [1, 1, 1, Math.Min(y + tileSize, height), Math.Min(x + tileSize, width)]
);
var tile = await level.ReadPixelRegionAsync(tileRegion);
// Process tile...
}
}
Contributions welcome! Please:
Built with โค๏ธ for the scientific imaging community
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net10.0 net10.0 is compatible. net10.0-android net10.0-android was computed. net10.0-browser net10.0-browser was computed. net10.0-ios net10.0-ios was computed. net10.0-maccatalyst net10.0-maccatalyst was computed. net10.0-macos net10.0-macos was computed. net10.0-tvos net10.0-tvos was computed. net10.0-windows net10.0-windows was computed. |
Showing the top 1 NuGet packages that depend on ZarrNET:
| Package | Downloads |
|---|---|
|
BioLib
A GUI-less version of Bio .NET library for editing & annotating various microscopy image formats. Supports all bioformats supported images. Integrates with ImageJ, running ImageJ filters & macro functions. Supports Windows, Linux and Mac. |
This package is not used by any popular GitHub repositories.
0.6.1: add complex64 and complex128 dtype support for Zarr v3 and v2 NumPy dtype parsing; fix byte-order handling for complex data in the codec pipeline; add optional uncompressed OME-Zarr writes for speed testing; and document the expanded dtype and compression support in the README and v2 support guide.