BioCore  5.0.0
A Windows .NET microscopy library and application for viewing, editing, and annotating images.
Loading...
Searching...
No Matches
ZarrExporter.cs
Go to the documentation of this file.
1using BioLib;
2using ZarrNET.Core;
3using ZarrNET.Core.OmeZarr.Metadata;
4using PixelFormat = AForge.PixelFormat;
5
6namespace BioCore
7{
8 // Exports fully loaded images. Slide readers expose only a viewport buffer,
9 // so they must not be treated as complete planes here.
10 internal static class ZarrExporter
11 {
12 internal static async Task SaveAsync(BioImage image, string destination)
13 {
14 if (File.Exists(destination) || Directory.Exists(destination))
15 throw new IOException("The destination already exists. Choose a new folder name.");
16 if (image.isPyramidal || image.IsZarrSource)
17 throw new NotSupportedException("Saving pyramidal or Zarr-backed images is not yet supported. Save a fully loaded image instead.");
18 if (image.Resolutions.Count == 0 || image.SizeZ <= 0 || image.SizeC <= 0 || image.SizeT <= 0)
19 throw new InvalidOperationException("The image dimensions are incomplete.");
20
21 var resolution = image.Resolutions[0];
22 var format = resolution.PixelFormat;
23 int bytesPerSample = format switch
24 {
25 PixelFormat.Format8bppIndexed or PixelFormat.Format24bppRgb => 1,
26 PixelFormat.Format16bppGrayScale or PixelFormat.Format48bppRgb => 2,
27 _ => throw new NotSupportedException($"The pixel format {format} is not supported for OME-Zarr export.")
28 };
29 bool rgb = format == PixelFormat.Format24bppRgb || format == PixelFormat.Format48bppRgb;
30 if (rgb && image.SizeC != 1)
31 throw new NotSupportedException("Interleaved RGB images with multiple logical channels are not supported.");
32 int width = resolution.SizeX, height = resolution.SizeY;
33 if (width <= 0 || height <= 0)
34 throw new InvalidOperationException("The image has invalid dimensions.");
35 int outputChannels = rgb ? 3 : image.SizeC;
36 const int chunk = 512;
37 var descriptor = new BioImageDescriptor(width, height, new ZarrNET.Core.ZCT(image.SizeZ, outputChannels, image.SizeT))
38 {
39 Name = Path.GetFileNameWithoutExtension(image.filename),
40 DataType = bytesPerSample == 1 ? "uint8" : "uint16",
41 PhysicalSizeX = image.PhysicalSizeX,
42 PhysicalSizeY = image.PhysicalSizeY,
43 PhysicalSizeZ = image.PhysicalSizeZ,
44 ChunkX = chunk,
45 ChunkY = chunk
46 };
47
48 string fullPath = Path.GetFullPath(destination);
49 string temporary = fullPath + ".partial-" + Guid.NewGuid().ToString("N");
50 try
51 {
52 var levels = new List<ResolutionLevelDescriptor> { new(width, height, 1.0) };
53 await using (var writer = await OmeZarrWriter.CreateAsync(temporary, descriptor, levels).ConfigureAwait(false))
54 {
55 for (int t = 0; t < image.SizeT; t++)
56 for (int z = 0; z < image.SizeZ; z++)
57 for (int c = 0; c < outputChannels; c++)
58 {
59 int index = image.GetFrameIndex(z, rgb ? 0 : c, t);
60 if (index < 0 || index >= image.Buffers.Count || image.Buffers[index] == null)
61 throw new InvalidOperationException($"Image plane Z{z} C{c} T{t} is missing.");
62 var buffer = image.Buffers[index];
63 if (buffer.SizeX != width || buffer.SizeY != height || buffer.PixelFormat != format || buffer.Bytes == null)
64 throw new InvalidOperationException("The image does not contain complete, matching pixel planes.");
65 int components = rgb ? 3 : 1;
66 int rowBytes = checked(width * components * bytesPerSample);
67 if (buffer.Stride < rowBytes || buffer.Bytes.Length < checked(buffer.Stride * height))
68 throw new InvalidOperationException("An image plane has an invalid pixel buffer.");
69 for (int y = 0; y < height; y += chunk)
70 for (int x = 0; x < width; x += chunk)
71 {
72 int w = Math.Min(chunk, width - x), h = Math.Min(chunk, height - y);
73 var tile = new byte[checked(w * h * bytesPerSample)];
74 for (int row = 0; row < h; row++)
75 for (int col = 0; col < w; col++)
76 {
77 // AForge's RGB buffers use BGR sample order.
78 int component = rgb ? 2 - c : 0;
79 int src = (y + row) * buffer.Stride + ((x + col) * components + component) * bytesPerSample;
80 int dst = (row * w + col) * bytesPerSample;
81 if (bytesPerSample == 2 && !buffer.LittleEndian)
82 {
83 tile[dst] = buffer.Bytes[src + 1];
84 tile[dst + 1] = buffer.Bytes[src];
85 }
86 else
87 Buffer.BlockCopy(buffer.Bytes, src, tile, dst, bytesPerSample);
88 }
89 await writer.WriteRegionAsync(t, c, z, y, x, h, w, tile, levelIndex: 0).ConfigureAwait(false);
90 }
91 }
92 }
93 if (image.Annotations.Count > 0)
94 Zarr.SaveROIs(image, temporary);
95 // Some Windows file-system filters briefly hold newly written
96 // chunks after the writer closes. Retry a transient move failure.
97 for (int attempt = 0; ; attempt++)
98 {
99 try
100 {
101 Directory.Move(temporary, fullPath);
102 break;
103 }
104 catch (IOException) when (attempt < 3 && !File.Exists(fullPath) && !Directory.Exists(fullPath))
105 {
106 await Task.Delay(100 * (attempt + 1)).ConfigureAwait(false);
107 }
108 }
109 }
110 finally
111 {
112 if (Directory.Exists(temporary))
113 Directory.Delete(temporary, true);
114 }
115 }
116 }
117}
OpenTK.Graphics.OpenGL.PixelFormat PixelFormat
Definition SlideGLArea.cs:8