7using System.Text.Json.Nodes;
15 public static class ZarrMetadataFixup
17 public static void AddRgbDisplayMetadata(
string zarrDir, BioImage image)
19 if (image ==
null ||
string.IsNullOrWhiteSpace(zarrDir))
22 var pixelFormat = GetSourcePixelFormat(image);
23 bool interleavedRgb = IsInterleavedRgb(pixelFormat);
24 int exportChannelCount = interleavedRgb && image.SizeC == 1
26 : Math.Max(1, image.SizeC);
28 if (exportChannelCount <= 1)
31 var omeroNode = CreateRgbDisplayMetadata(image);
33 PatchRootScaleMetadata(Path.Combine(zarrDir,
"zarr.json"));
34 PatchRootScaleMetadata(Path.Combine(zarrDir,
".zattrs"));
35 PatchRootMetadata(Path.Combine(zarrDir,
"zarr.json"), omeroNode,
true);
36 PatchRootMetadata(Path.Combine(zarrDir,
".zattrs"), omeroNode,
false);
39 public static JsonObject CreateRgbDisplayMetadata(BioImage image)
41 var pixelFormat = GetSourcePixelFormat(image);
42 bool interleavedRgb = IsInterleavedRgb(pixelFormat);
43 int exportChannelCount = interleavedRgb && image.SizeC == 1
45 : Math.Max(1, image.SizeC);
46 int maxValue = GetDisplayMaxValue(pixelFormat);
47 return BuildOmeroNode(exportChannelCount, maxValue, Path.GetFileNameWithoutExtension(image?.Filename));
50 private static JsonObject BuildOmeroNode(
int channelCount,
int maxValue,
string? imageName)
52 var channels =
new JsonArray();
53 string[] labels = channelCount == 3
54 ?
new[] {
"Red",
"Green",
"Blue" }
55 : Enumerable.Range(1, channelCount).Select(i => $
"Channel {i}").ToArray();
56 string[] colors = channelCount == 3
57 ?
new[] {
"FF0000",
"00FF00",
"0000FF" }
58 : Enumerable.Repeat(
"FFFFFF", channelCount).ToArray();
60 for (
int i = 0; i < channelCount; i++)
62 channels.Add(
new JsonObject
66 [
"color"] = colors[i],
67 [
"family"] =
"linear",
69 [
"label"] = labels[i],
70 [
"window"] =
new JsonObject
84 [
"name"] =
string.IsNullOrWhiteSpace(imageName) ?
"image" : imageName,
85 [
"channels"] = channels,
86 [
"rdefs"] =
new JsonObject
91 [
"model"] = channelCount > 1 ?
"color" :
"greyscale"
96 private static void PatchRootMetadata(
string path, JsonObject omeroNode,
bool isV3Root)
98 if (!File.Exists(path))
103 var text = File.ReadAllText(path);
104 var root = JsonNode.Parse(text) as JsonObject;
110 var attributes = root[
"attributes"] as JsonObject;
111 var ome = attributes?[
"ome"] as JsonObject;
114 ome[
"omero"] = omeroNode.DeepClone();
118 root[
"omero"] = omeroNode.DeepClone();
123 root.ToJsonString(
new JsonSerializerOptions { WriteIndented = true }));
131 private static void PatchRootScaleMetadata(
string path)
133 if (!File.Exists(path))
138 var text = File.ReadAllText(path);
139 var root = JsonNode.Parse(text) as JsonObject;
143 if (root[
"attributes"] is JsonObject attributes &&
144 attributes[
"ome"] is JsonObject ome &&
145 ome[
"multiscales"] is JsonArray multiscales)
147 foreach (var multiscaleNode
in multiscales.OfType<JsonObject>())
149 NormalizeScaleEntries(multiscaleNode);
152 else if (root[
"multiscales"] is JsonArray legacyMultiscales)
154 foreach (var multiscaleNode
in legacyMultiscales.OfType<JsonObject>())
156 NormalizeScaleEntries(multiscaleNode);
162 root.ToJsonString(
new JsonSerializerOptions { WriteIndented = true }));
170 private static void NormalizeScaleEntries(JsonObject multiscaleNode)
172 if (multiscaleNode[
"datasets"] is not JsonArray datasets)
175 foreach (var datasetNode
in datasets.OfType<JsonObject>())
177 if (datasetNode[
"coordinateTransformations"] is not JsonArray transforms)
180 foreach (var transformNode
in transforms.OfType<JsonObject>())
182 if (!
string.Equals(transformNode[
"type"]?.GetValue<string>(),
"scale", StringComparison.OrdinalIgnoreCase))
185 if (transformNode[
"scale"] is not JsonArray scale)
191 for (
int i = 0; i < scale.Count; i++)
193 if (scale[i] is JsonValue value && value.TryGetValue<
double>(out
double d) && (!
double.IsFinite(d) || d <= 0))
202 private static PixelFormat GetSourcePixelFormat(BioImage image)
204 if (image.Resolutions.Count > 0)
205 return image.Resolutions[0].PixelFormat;
207 if (image.Buffers.Count > 0)
208 return image.Buffers[0].PixelFormat;
210 return PixelFormat.Format16bppGrayScale;
213 private static bool IsInterleavedRgb(PixelFormat format)
215 return format == PixelFormat.Format24bppRgb ||
216 format == PixelFormat.Format32bppArgb ||
217 format == PixelFormat.Format32bppPArgb ||
218 format == PixelFormat.Format32bppRgb ||
219 format == PixelFormat.Format48bppRgb ||
220 format == PixelFormat.Format64bppArgb ||
221 format == PixelFormat.Format64bppPArgb;
224 private static int GetDisplayMaxValue(PixelFormat format)
228 PixelFormat.Format16bppGrayScale => ushort.MaxValue,
229 PixelFormat.Format48bppRgb => ushort.MaxValue,
230 PixelFormat.Format64bppArgb => ushort.MaxValue,
231 PixelFormat.Format64bppPArgb => ushort.MaxValue,