BioGTK  6.7.0
A .NET library & program for annotating, editing various microscopy imaging formats using Bioformats supported images. Including whole slide, pyramidal, and series.
Loading...
Searching...
No Matches
ZarrMetadataFixup.cs
1using AForge;
2using Bio;
3using System;
4using System.IO;
5using System.Linq;
6using System.Text.Json;
7using System.Text.Json.Nodes;
8
9namespace BioGTK
10{
15 public static class ZarrMetadataFixup
16 {
17 public static void AddRgbDisplayMetadata(string zarrDir, BioImage image)
18 {
19 if (image == null || string.IsNullOrWhiteSpace(zarrDir))
20 return;
21
22 var pixelFormat = GetSourcePixelFormat(image);
23 bool interleavedRgb = IsInterleavedRgb(pixelFormat);
24 int exportChannelCount = interleavedRgb && image.SizeC == 1
25 ? 3
26 : Math.Max(1, image.SizeC);
27
28 if (exportChannelCount <= 1)
29 return;
30
31 var omeroNode = CreateRgbDisplayMetadata(image);
32
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);
37 }
38
39 public static JsonObject CreateRgbDisplayMetadata(BioImage image)
40 {
41 var pixelFormat = GetSourcePixelFormat(image);
42 bool interleavedRgb = IsInterleavedRgb(pixelFormat);
43 int exportChannelCount = interleavedRgb && image.SizeC == 1
44 ? 3
45 : Math.Max(1, image.SizeC);
46 int maxValue = GetDisplayMaxValue(pixelFormat);
47 return BuildOmeroNode(exportChannelCount, maxValue, Path.GetFileNameWithoutExtension(image?.Filename));
48 }
49
50 private static JsonObject BuildOmeroNode(int channelCount, int maxValue, string? imageName)
51 {
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();
59
60 for (int i = 0; i < channelCount; i++)
61 {
62 channels.Add(new JsonObject
63 {
64 ["active"] = true,
65 ["coefficient"] = 1,
66 ["color"] = colors[i],
67 ["family"] = "linear",
68 ["inverted"] = false,
69 ["label"] = labels[i],
70 ["window"] = new JsonObject
71 {
72 ["min"] = 0,
73 ["max"] = maxValue,
74 ["start"] = 0,
75 ["end"] = maxValue
76 }
77 });
78 }
79
80 return new JsonObject
81 {
82 ["version"] = "0.5",
83 ["id"] = 0,
84 ["name"] = string.IsNullOrWhiteSpace(imageName) ? "image" : imageName,
85 ["channels"] = channels,
86 ["rdefs"] = new JsonObject
87 {
88 ["defaultZ"] = 0,
89 ["defaultT"] = 0,
90 ["defaultC"] = 0,
91 ["model"] = channelCount > 1 ? "color" : "greyscale"
92 }
93 };
94 }
95
96 private static void PatchRootMetadata(string path, JsonObject omeroNode, bool isV3Root)
97 {
98 if (!File.Exists(path))
99 return;
100
101 try
102 {
103 var text = File.ReadAllText(path);
104 var root = JsonNode.Parse(text) as JsonObject;
105 if (root == null)
106 return;
107
108 if (isV3Root)
109 {
110 var attributes = root["attributes"] as JsonObject;
111 var ome = attributes?["ome"] as JsonObject;
112 if (ome == null)
113 return;
114 ome["omero"] = omeroNode.DeepClone();
115 }
116 else
117 {
118 root["omero"] = omeroNode.DeepClone();
119 }
120
121 File.WriteAllText(
122 path,
123 root.ToJsonString(new JsonSerializerOptions { WriteIndented = true }));
124 }
125 catch
126 {
127 // Keep the save flow resilient if the metadata patch fails.
128 }
129 }
130
131 private static void PatchRootScaleMetadata(string path)
132 {
133 if (!File.Exists(path))
134 return;
135
136 try
137 {
138 var text = File.ReadAllText(path);
139 var root = JsonNode.Parse(text) as JsonObject;
140 if (root == null)
141 return;
142
143 if (root["attributes"] is JsonObject attributes &&
144 attributes["ome"] is JsonObject ome &&
145 ome["multiscales"] is JsonArray multiscales)
146 {
147 foreach (var multiscaleNode in multiscales.OfType<JsonObject>())
148 {
149 NormalizeScaleEntries(multiscaleNode);
150 }
151 }
152 else if (root["multiscales"] is JsonArray legacyMultiscales)
153 {
154 foreach (var multiscaleNode in legacyMultiscales.OfType<JsonObject>())
155 {
156 NormalizeScaleEntries(multiscaleNode);
157 }
158 }
159
160 File.WriteAllText(
161 path,
162 root.ToJsonString(new JsonSerializerOptions { WriteIndented = true }));
163 }
164 catch
165 {
166 // Keep the save flow resilient if the metadata patch fails.
167 }
168 }
169
170 private static void NormalizeScaleEntries(JsonObject multiscaleNode)
171 {
172 if (multiscaleNode["datasets"] is not JsonArray datasets)
173 return;
174
175 foreach (var datasetNode in datasets.OfType<JsonObject>())
176 {
177 if (datasetNode["coordinateTransformations"] is not JsonArray transforms)
178 continue;
179
180 foreach (var transformNode in transforms.OfType<JsonObject>())
181 {
182 if (!string.Equals(transformNode["type"]?.GetValue<string>(), "scale", StringComparison.OrdinalIgnoreCase))
183 continue;
184
185 if (transformNode["scale"] is not JsonArray scale)
186 continue;
187
188 if (scale.Count < 3)
189 continue;
190
191 for (int i = 0; i < scale.Count; i++)
192 {
193 if (scale[i] is JsonValue value && value.TryGetValue<double>(out double d) && (!double.IsFinite(d) || d <= 0))
194 {
195 scale[i] = 1.0;
196 }
197 }
198 }
199 }
200 }
201
202 private static PixelFormat GetSourcePixelFormat(BioImage image)
203 {
204 if (image.Resolutions.Count > 0)
205 return image.Resolutions[0].PixelFormat;
206
207 if (image.Buffers.Count > 0)
208 return image.Buffers[0].PixelFormat;
209
210 return PixelFormat.Format16bppGrayScale;
211 }
212
213 private static bool IsInterleavedRgb(PixelFormat format)
214 {
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;
222 }
223
224 private static int GetDisplayMaxValue(PixelFormat format)
225 {
226 return format switch
227 {
228 PixelFormat.Format16bppGrayScale => ushort.MaxValue,
229 PixelFormat.Format48bppRgb => ushort.MaxValue,
230 PixelFormat.Format64bppArgb => ushort.MaxValue,
231 PixelFormat.Format64bppPArgb => ushort.MaxValue,
232 _ => byte.MaxValue
233 };
234 }
235 }
236}
Definition ML.cs:17