BioLib  4.1.1
A GUI-less version of Bio .NET library for editing & annotating various microscopy image formats.
Loading...
Searching...
No Matches
BioLib.SlideBase Class Reference
Inheritance diagram for BioLib.SlideBase:

Public Member Functions

 SlideBase (BioImage source, SlideImage im, bool enableCache=true)
 
void RebuildSchemaForWell (BioImage b)
 
override IReadOnlyDictionary< string, byte[]> GetExternImages ()
 
byte[] GetTile (TileInfo tileInfo, ZCT coord)
 

Static Public Member Functions

static string DetectVendor (string source)
 
static byte[] ConvertRgbaToRgb (byte[] rgbaArray)
 

Public Attributes

readonly SlideImage SlideImage
 

Protected Member Functions

void InitResolutions (IDictionary< int, BruTile.Resolution > resolutions, int tileWidth, int tileHeight)
 
override void Dispose (bool disposing)
 

Constructor & Destructor Documentation

◆ SlideBase()

BioLib.SlideBase.SlideBase ( BioImage source,
SlideImage im,
bool enableCache = true )
21 {
22 Source = source.file;
23 SlideImage = im;
24 Image = im;
25 double baseUnitsPerPixel = source.PhysicalSizeX > 0
26 ? source.PhysicalSizeX
27 : source.PhysicalSizeY > 0
28 ? source.PhysicalSizeY
29 : 1;
30 MinUnitsPerPixel = UseRealResolution ? baseUnitsPerPixel : 1;
31 if (MinUnitsPerPixel <= 0) MinUnitsPerPixel = 1;
32 var height = SlideImage.Dimensions.Height;
33 var width = SlideImage.Dimensions.Width;
34 double stageX = 0;
35 double stageY = 0;
36 if (source.Resolutions != null && source.Resolutions.Count > 0)
37 {
38 stageX = source.Resolutions[0].StageSizeX;
39 stageY = source.Resolutions[0].StageSizeY;
40 }
41 //ExternInfo = GetInfo();
42 Schema = new TileSchema
43 {
44 YAxis = YAxis.OSM,
45 Format = "jpg",
46 Extent = new Extent(
47 stageX,
48 -(stageY + (height * MinUnitsPerPixel)),
49 stageX + (width * MinUnitsPerPixel),
50 -stageY),
51 OriginX = stageX,
52 OriginY = stageY,
53 };
54 InitResolutions(Schema.Resolutions, 256, 256);
55 }

Member Function Documentation

◆ ConvertRgbaToRgb()

static byte[] BioLib.SlideBase.ConvertRgbaToRgb ( byte[] rgbaArray)
static
297 {
298 // Initialize a new byte array for RGB24 format
299 byte[] rgbArray = new byte[(rgbaArray.Length / 4) * 3];
300
301 for (int i = 0, j = 0; i < rgbaArray.Length; i += 4, j += 3)
302 {
303 // Copy the R, G, B values, skip the A value
304 rgbArray[j] = rgbaArray[i]; // B
305 rgbArray[j + 1] = rgbaArray[i + 1]; // G
306 rgbArray[j + 2] = rgbaArray[i + 2]; // R
307 }
308
309 return rgbArray;
310 }

◆ DetectVendor()

static string BioLib.SlideBase.DetectVendor ( string source)
static
58 {
59 return SlideImage.DetectVendor(source);
60 }
static string DetectVendor(string filename)
Quickly determine whether a whole slide image is recognized.
Definition SlideImage.cs:71

◆ Dispose()

override void BioLib.SlideBase.Dispose ( bool disposing)
protected
332 {
333 if (!disposedValue)
334 {
335 if (disposing)
336 {
337 SlideImage.Dispose();
338 }
339 disposedValue = true;
340 }
341 base.Dispose(disposing);
342 }
virtual void Dispose(bool disposing)
Dispose.
Definition SlideImage.cs:629

◆ GetExternImages()

override IReadOnlyDictionary< string, byte[]> BioLib.SlideBase.GetExternImages ( )
144 {
145 throw new NotImplementedException();
146 /*
147 Dictionary<string, byte[]> images = new Dictionary<string, byte[]>();
148 var r = Math.Max(Schema.Extent.Height, Schema.Extent.Width) / 512;
149 images.Add("preview", GetSlice(new SliceInfo { Extent = Schema.Extent, Resolution = r }));
150 foreach (var item in SlideImage.GetAssociatedImages())
151 {
152 var dim = item.Value.Dimensions;
153 images.Add(item.Key, ImageUtil.GetJpeg(item.Value.Data, 4, 4 * (int)dim.Width, (int)dim.Width, (int)dim.Height));
154 }
155 return images;
156 */
157 }

◆ GetTile()

byte[] BioLib.SlideBase.GetTile ( TileInfo tileInfo,
ZCT coord )
179 {
180 try
181 {
182 if (Schema == null)
183 {
184 Console.WriteLine("Schema is null.");
185 return null;
186 }
187 var r = Schema.Resolutions[tileInfo.Index.Level].UnitsPerPixel;
188 var tileWidth = Schema.Resolutions[tileInfo.Index.Level].TileWidth;
189 var tileHeight = Schema.Resolutions[tileInfo.Index.Level].TileHeight;
190
191 // Tile extents are expressed in translated stage/world space, but
192 // the underlying Zarr pixels still start at image-local 0,0.
193 // Subtract the schema origin here so stage translation affects
194 // placement only, not the pixel read offset.
195 var schemaExtent = Schema.Extent;
196
197 // Tile placement uses translated world-space extents, but the
198 // underlying Zarr/OME pixels are still addressed on the tile grid.
199 // Derive the read origin from the tile index so translation does
200 // not introduce a fractional-pixel bias at the image edge.
201 var curLevelOffsetXPixel = tileInfo.Index.Col * tileWidth;
202 var curLevelOffsetYPixel = tileInfo.Index.Row * tileHeight;
203
204 // Use actual level pixel dimensions from BioImage.
205 int lev = tileInfo.Index.Level;
206 var levelPixelW = (long)(lev < SlideImage.BioImage.Resolutions.Count
207 ? SlideImage.BioImage.Resolutions[lev].SizeX
208 : Math.Round(Schema.Extent.Width / r));
209 var levelPixelH = (long)(lev < SlideImage.BioImage.Resolutions.Count
210 ? SlideImage.BioImage.Resolutions[lev].SizeY
211 : Math.Round(Schema.Extent.Height / r));
212
213 long readX = (long)curLevelOffsetXPixel;
214 long readY = (long)curLevelOffsetYPixel;
215
216 int curTileWidth = tileWidth;
217 int curTileHeight = tileHeight;
218
219 // Trim any part of the request that falls outside the image on
220 // the left/top instead of asking the Zarr reader to pad with black.
221 if (readX < 0)
222 {
223 curTileWidth += (int)readX;
224 readX = 0;
225 }
226 if (readY < 0)
227 {
228 curTileHeight += (int)readY;
229 readY = 0;
230 }
231
232 // Clamp tile read size so we never request pixels past the image boundary.
233 curTileWidth = (int)Math.Max(0, Math.Min(curTileWidth, levelPixelW - readX));
234 curTileHeight = (int)Math.Max(0, Math.Min(curTileHeight, levelPixelH - readY));
235
236 // Guard against fully OOB tiles
237 if (curTileWidth <= 0 || curTileHeight <= 0)
238 {
239 return new byte[tileWidth * tileHeight * 4]; // black padded tile
240 }
241
242 if (SlideImage == null)
243 {
244 Console.WriteLine("SlideImage is null.");
245 return null;
246 }
247
248 var bgraData = SlideImage.ReadRegion(
249 tileInfo.Index.Level, coord,
250 readX, readY,
251 curTileWidth, curTileHeight);
252
253 if (bgraData == null || bgraData.Length == 0)
254 return null;
255
256 int expectedSize = tileWidth * tileHeight * 4;
257
258 // Full tile — return as-is.
259 // Return the raw BGRA data (4 bytes/pixel) so the GL upload path
260 // receives a buffer that matches PixelFormat.Bgra / PixelType.UnsignedByte.
261 if (bgraData.Length == expectedSize)
262 return bgraData;
263
264 // Edge tile: ReadRegion returned fewer pixels than a full tile because
265 // this tile hangs off the image boundary. Pad into a full tileWidth x
266 // tileHeight RGBA buffer (rows outside the image stay transparent/zeroed)
267 // so the GL upload always receives a consistently-sized, correctly-strided
268 // buffer and never reads past the end.
269 int actualW = Math.Min(curTileWidth, tileWidth);
270 int actualH = Math.Min(curTileHeight, tileHeight);
271 int srcStride = actualW * 4;
272
273 // If ReadRegion returned even less than expected, derive safe row count
274 if (bgraData.Length < actualW * actualH * 4)
275 {
276 int safePixels = bgraData.Length / 4;
277 actualH = safePixels / actualW;
278 if (actualH == 0) return null;
279 }
280
281 byte[] padded = new byte[expectedSize]; // zero-initialised = transparent
282 for (int row = 0; row < actualH; row++)
283 {
284 int srcOffset = row * srcStride;
285 int dstOffset = row * tileWidth * 4;
286 Buffer.BlockCopy(bgraData, srcOffset, padded, dstOffset, srcStride);
287 }
288 return padded;
289 }
290 catch (Exception e)
291 {
292 Console.WriteLine(e.Message + " " + e.StackTrace);
293 }
294 return null;
295 }
unsafe byte[] ReadRegion(int level, ZCT zct, long x, long y, long width, long height)
Copy pre-multiplied BGRA data from a whole slide image.
Definition SlideImage.cs:282

◆ InitResolutions()

void BioLib.SlideBase.InitResolutions ( IDictionary< int, BruTile.Resolution > resolutions,
int tileWidth,
int tileHeight )
protected
313 {
314 for (int i = 0; i < SlideImage.LevelCount; i++)
315 {
316 /*
317 bool useInternalWidth = int.TryParse(ExternInfo.TryGetValue($"openslide.level[{i}].tile-width", out var _w) ? (string)_w : null, out var w) && w >= tileWidth;
318 bool useInternalHeight = int.TryParse(ExternInfo.TryGetValue($"openslide.level[{i}].tile-height", out var _h) ? (string)_h : null, out var h) && h >= tileHeight;
319
320 bool useInternalSize = useInternalHeight && useInternalWidth;
321 var tw = useInternalSize ? w : tileWidth;
322 var th = useInternalSize ? h : tileHeight;
323 resolutions.Add(i, new Resolution(i, MinUnitsPerPixel * SlideImage.GetLevelDownsample(i), tw, th));
324 */
325 resolutions.Add(i, new BruTile.Resolution(i, SlideImage.BioImage.GetUnitPerPixel(i), tileWidth, tileHeight));
326 }
327 }
double GetUnitPerPixel(int level)
Get Unit Per Pixel for pyramidal images.
Definition Bio.cs:2328
int LevelCount
Get the number of levels in the whole slide image.
Definition SlideImage.cs:119

◆ RebuildSchemaForWell()

void BioLib.SlideBase.RebuildSchemaForWell ( BioImage b)
75 {
76 if (b.ZarrWellLevels == null || b.ZarrWellLevels.Count == 0)
77 {
78 Log($"[RebuildSchemaForWell] ZarrWellLevels empty — schema not rebuilt");
79 return;
80 }
81 int fi = Math.Clamp(b.WellIndex, 0, b.ZarrWellLevels.Count - 1);
82 var fieldLevels = b.ZarrWellLevels[fi];
83 if (fieldLevels == null || fieldLevels.Count == 0)
84 {
85 // Requested field not yet loaded — fall back to first non-null field
86 // so the schema matches what TryReadRegionAsync will actually serve.
87 fieldLevels = b.ZarrWellLevels.FirstOrDefault(l => l != null && l.Count > 0);
88 if (fieldLevels == null)
89 {
90 Log($"[RebuildSchemaForWell] no loaded fieldLevels, schema not rebuilt");
91 return;
92 }
93 Log($"[RebuildSchemaForWell] fi={fi} null, fell back to first loaded field");
94 }
95
96 int w0 = 1, h0 = 1;
97 GetFieldDims(fieldLevels[0], out w0, out h0);
98 Log($"[RebuildSchemaForWell] field fi={fi}, level0 w={w0} h={h0}, levels={fieldLevels.Count}");
99
100 double stageX = b.Resolutions.Count > 0 ? b.Resolutions[0].StageSizeX : 0;
101 double stageY = b.Resolutions.Count > 0 ? b.Resolutions[0].StageSizeY : 0;
102
103 var newSchema = new BruTile.TileSchema
104 {
105 YAxis = BruTile.YAxis.OSM,
106 Format = "jpg",
107 Extent = new BruTile.Extent(
108 stageX,
109 -(stageY + (h0 * b.PhysicalSizeY)),
110 stageX + (w0 * b.PhysicalSizeX),
111 -stageY),
112 OriginX = stageX,
113 OriginY = stageY,
114 };
115
116 for (int lev = 0; lev < fieldLevels.Count; lev++)
117 {
118 GetFieldDims(fieldLevels[lev], out int wL, out int hL);
119 double downsample = w0 > 0 ? (double)w0 / Math.Max(1, wL) : Math.Pow(2, lev);
120 newSchema.Resolutions[lev] = new BruTile.Resolution(lev, downsample, 256, 256);
121 Log($"[RebuildSchemaForWell] level {lev}: w={wL} h={hL} unitsPerPixel={downsample}");
122 }
123
124 Schema = newSchema;
125 }

The documentation for this class was generated from the following file: