3using ZarrNET.Core.Helpers;
4using ZarrNET.Core.Nodes;
8 internal static class ZarrChannelView
10 private const int HistogramSampleSide = 1024;
12 internal static void InitializeChannels(BioImage image)
14 if (!image.IsZarrSource || image.levels ==
null || image.levels.Count == 0)
16 var level = image.levels[0];
17 int axis = Array.FindIndex(level.EffectiveAxes, a =>
string.Equals(a.Name,
"c", StringComparison.OrdinalIgnoreCase));
18 int count = axis >= 0 ? checked((
int)level.Shape[axis]) : 1;
19 int bits = level.DataType.Equals(
"uint16", StringComparison.OrdinalIgnoreCase) ? 16 : 8;
20 while (image.Channels.Count < count)
21 image.Channels.Add(
new Channel(image.Channels.Count, bits, 1));
24 internal static async Task<byte[]> ReadRgbTileAsync(BioImage image,
int levelIndex,
int x,
int y,
25 int width,
int height, AForge.ZCT coordinate,
int[] channels)
27 var levels = image.Type == BioImage.ImageType.well && image.ZarrWellLevels.Count > 0
28 ? image.ZarrWellLevels[Math.Clamp(image.WellIndex, 0, image.ZarrWellLevels.Count - 1)]
30 var level = levels[levelIndex];
31 int axisX = Array.FindIndex(level.EffectiveAxes, a =>
string.Equals(a.Name,
"x", StringComparison.OrdinalIgnoreCase));
32 int axisY = Array.FindIndex(level.EffectiveAxes, a =>
string.Equals(a.Name,
"y", StringComparison.OrdinalIgnoreCase));
33 if (axisX < 0 || axisY < 0)
34 throw new InvalidDataException(
"The Zarr image has no X or Y axis.");
35 int readWidth = Math.Min(width, checked((
int)level.Shape[axisX]) - x);
36 int readHeight = Math.Min(height, checked((
int)level.Shape[axisY]) - y);
37 var result =
new byte[checked(width * height * 4)];
38 if (readWidth <= 0 || readHeight <= 0)
40 bool sixteenBit = level.DataType.Equals(
"uint16", StringComparison.OrdinalIgnoreCase);
41 if (!sixteenBit && !level.DataType.Equals(
"uint8", StringComparison.OrdinalIgnoreCase))
42 throw new NotSupportedException($
"Zarr RGB display does not support {level.DataType}.");
43 for (
int color = 0; color < 3; color++)
45 if (channels[color] < 0)
47 var plane = await level.ReadTileAsync(x, y, readWidth, readHeight,
48 z: coordinate.Z, c: channels[color], t: coordinate.T).ConfigureAwait(
false);
49 int bytesPerSample = sixteenBit ? 2 : 1;
50 if (plane?.Data ==
null || plane.Data.Length < checked(readWidth * readHeight * bytesPerSample))
51 throw new InvalidDataException(
"The Zarr channel tile is incomplete.");
52 for (
int row = 0; row < readHeight; row++)
53 for (
int col = 0; col < readWidth; col++)
55 int source = (row * readWidth + col) * bytesPerSample;
56 result[(row * width + col) * 4 + color] = plane.Data[source + (sixteenBit ? 1 : 0)];
59 for (
int row = 0; row < readHeight; row++)
60 for (
int col = 0; col < readWidth; col++)
61 result[(row * width + col) * 4 + 3] = 255;
65 internal static async Task EnsureHistogramStatisticsAsync(BioImage image, ZCT coordinate)
67 if (!image.IsZarrSource || image.levels ==
null || image.levels.Count == 0)
69 await Task.Run(() => CalculateBufferedFrameStatistics(image, coordinate));
73 var levels = image.Type == BioImage.ImageType.well && image.ZarrWellLevels.Count > 0
74 ? image.ZarrWellLevels[Math.Clamp(image.WellIndex, 0, image.ZarrWellLevels.Count - 1)]
76 if (levels ==
null || levels.Count == 0)
79 ResolutionLevelNode level = SelectHistogramLevel(levels);
80 int axisX = Array.FindIndex(level.EffectiveAxes,
81 a =>
string.Equals(a.Name,
"x", StringComparison.OrdinalIgnoreCase));
82 int axisY = Array.FindIndex(level.EffectiveAxes,
83 a =>
string.Equals(a.Name,
"y", StringComparison.OrdinalIgnoreCase));
84 int axisC = Array.FindIndex(level.EffectiveAxes,
85 a =>
string.Equals(a.Name,
"c", StringComparison.OrdinalIgnoreCase));
86 if (axisX < 0 || axisY < 0)
89 int levelWidth = checked((
int)level.Shape[axisX]);
90 int levelHeight = checked((
int)level.Shape[axisY]);
91 int width = Math.Min(levelWidth, HistogramSampleSide);
92 int height = Math.Min(levelHeight, HistogramSampleSide);
93 int x = Math.Max(0, (levelWidth - width) / 2);
94 int y = Math.Max(0, (levelHeight - height) / 2);
95 int sourceChannels = axisC >= 0 ? checked((
int)level.Shape[axisC]) : 1;
96 bool sixteenBit = level.DataType.Equals(
"uint16", StringComparison.OrdinalIgnoreCase);
97 if (!sixteenBit && !level.DataType.Equals(
"uint8", StringComparison.OrdinalIgnoreCase))
100 int bits = sixteenBit ? 16 : 8;
101 int stride = checked(width * (sixteenBit ? 2 : 1));
102 AForge.PixelFormat pixelFormat = sixteenBit
103 ? AForge.PixelFormat.Format16bppGrayScale
104 : AForge.PixelFormat.Format8bppIndexed;
105 var combined =
new Statistics(bits);
106 var sourceStatistics =
new Statistics[sourceChannels];
107 int z = ClampCoordinate(level,
"z", coordinate.Z);
108 int t = ClampCoordinate(level,
"t", coordinate.T);
110 for (
int sourceChannel = 0; sourceChannel < sourceChannels; sourceChannel++)
112 var plane = await level.ReadTileAsync(x, y, width, height,
113 z: z, c: sourceChannel, t: t);
114 if (plane?.Data ==
null || plane.Data.Length < checked(stride * height))
117 Statistics[] planeStatistics = Statistics.FromBytes(plane.Data, width, height,
118 1, bits, stride, pixelFormat);
119 if (planeStatistics ==
null || planeStatistics.Length == 0)
122 var aggregate =
new Statistics(bits);
123 aggregate.AddStatistics(planeStatistics[0]);
124 aggregate.MeanHistogram();
125 sourceStatistics[sourceChannel] = aggregate;
126 combined.AddStatistics(planeStatistics[0]);
129 if (sourceStatistics.Any(s => s ==
null))
132 combined.MeanHistogram();
133 for (
int i = 0; i < image.Channels.Count; i++)
135 int sampleCount = Math.Max(1, image.Channels[i].range?.Length ?? 0);
136 var statistics =
new Statistics[sampleCount];
137 for (
int sample = 0; sample < sampleCount; sample++)
139 int sourceChannel = sampleCount > 1 ? sample : i;
140 statistics[sample] = sourceStatistics[
141 Math.Clamp(sourceChannel, 0, sourceStatistics.Length - 1)];
143 image.Channels[i].stats = statistics;
144 for (
int sample = 0; sample < Math.Min(statistics.Length, image.Channels[i].range.Length); sample++)
146 image.Channels[i].range[sample].Min = (int)statistics[sample].StackMin;
147 image.Channels[i].range[sample].Max = (int)statistics[sample].StackMax;
150 image.Statistics = combined;
153 private static void CalculateBufferedFrameStatistics(BioImage image, ZCT coordinate)
155 if (image.Buffers.Count == 0 || image.Channels.Count == 0)
158 var combined =
new Statistics(image.bitsPerPixel);
160 for (
int channelIndex = 0; channelIndex < image.Channels.Count; channelIndex++)
162 int frameIndex = image.GetFrameIndex(
163 Math.Clamp(coordinate.Z, 0, Math.Max(0, image.SizeZ - 1)),
164 Math.Clamp(channelIndex, 0, Math.Max(0, image.SizeC - 1)),
165 Math.Clamp(coordinate.T, 0, Math.Max(0, image.SizeT - 1)));
166 if (frameIndex < 0 || frameIndex >= image.Buffers.Count || image.Buffers[frameIndex] ==
null)
169 var buffer = image.Buffers[frameIndex];
170 buffer.Stats ??= Statistics.FromBytes(buffer);
171 if (buffer.Stats ==
null || buffer.Stats.Length == 0)
174 int sampleCount = Math.Max(1, image.Channels[channelIndex].range?.Length ?? 0);
175 var statistics =
new Statistics[sampleCount];
176 for (
int sample = 0; sample < sampleCount; sample++)
178 Statistics raw = buffer.Stats[Math.Clamp(sample, 0, buffer.Stats.Length - 1)];
179 var aggregate =
new Statistics(image.bitsPerPixel);
180 aggregate.AddStatistics(raw);
181 aggregate.MeanHistogram();
182 statistics[sample] = aggregate;
183 combined.AddStatistics(raw);
184 image.Channels[channelIndex].range[sample].Min = (int)aggregate.StackMin;
185 image.Channels[channelIndex].range[sample].Max = (int)aggregate.StackMax;
188 image.Channels[channelIndex].stats = statistics;
192 combined.MeanHistogram();
193 image.Statistics = combined;
197 internal static bool HasHistogramStatistics(BioImage image)
199 return image?.Statistics?.StackValues !=
null &&
200 image.Channels.Count > 0 &&
201 image.Channels.All(channel => channel.range?.Length > 0 &&
202 channel.stats !=
null && channel.stats.Length >= channel.range.Length &&
203 channel.stats.All(stat => stat?.StackValues !=
null));
206 private static ResolutionLevelNode SelectHistogramLevel(IReadOnlyList<ResolutionLevelNode> levels)
208 for (
int i = 0; i < levels.Count; i++)
210 var level = levels[i];
211 int axisX = Array.FindIndex(level.EffectiveAxes,
212 a =>
string.Equals(a.Name,
"x", StringComparison.OrdinalIgnoreCase));
213 int axisY = Array.FindIndex(level.EffectiveAxes,
214 a =>
string.Equals(a.Name,
"y", StringComparison.OrdinalIgnoreCase));
215 if (axisX >= 0 && axisY >= 0 &&
216 level.Shape[axisX] <= HistogramSampleSide &&
217 level.Shape[axisY] <= HistogramSampleSide)
223 private static int ClampCoordinate(ResolutionLevelNode level,
string axisName,
int value)
225 int axis = Array.FindIndex(level.EffectiveAxes,
226 a =>
string.Equals(a.Name, axisName, StringComparison.OrdinalIgnoreCase));
227 return axis < 0 ? 0 : Math.Clamp(value, 0, checked((
int)level.Shape[axis]) - 1);