BioImager  5.0.0
A .NET microscopy imaging application based on Bio library. Supports various microscopes by using imported libraries & GUI automation. Supports XInput game controllers to move stage, take images, run ImageJ macros on images or Bio C# scripts.
Loading...
Searching...
No Matches
ImageView.Overview.cs
Go to the documentation of this file.
1using AForge;
2using BioLib;
3using Bitmap = AForge.Bitmap;
4using System.ComponentModel;
5using Drawing = System.Drawing;
6
7namespace BioImager
8{
9 public partial class ImageView
10 {
11 public Drawing.Bitmap overviewImage;
12 private readonly PictureBox overviewPanel = new PictureBox();
13 private BioImage overviewSource;
14 private BioImage previewRequestImage;
15 private (int Z, int C, int T) previewRequestCoordinate;
16 private int previewVersion;
17 private bool overviewDragging;
18 private bool showOverview = true;
19
20 [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
21 public bool ShowOverview
22 {
23 get => showOverview;
24 set
25 {
26 showOverview = value;
27 UpdateOverviewVisibility();
28 }
29 }
30
31 private void InitializeOverview()
32 {
33 gl.Dock = DockStyle.Fill;
34 gl.ContextMenuStrip = contextMenuStrip;
35 contextMenuStrip.Opening += contextMenuStrip_Opening;
36 overviewPanel.Name = "overviewPanel";
37 overviewPanel.BackColor = Drawing.Color.Black;
38 overviewPanel.Cursor = Cursors.Cross;
39 overviewPanel.Visible = false;
40 overviewPanel.ContextMenuStrip = contextMenuStrip;
41 overviewPanel.Paint += OverviewPaint;
42 overviewPanel.MouseDown += OverviewMouseDown;
43 overviewPanel.MouseMove += OverviewMouseMove;
44 overviewPanel.MouseUp += OverviewMouseUp;
45 overviewPanel.MouseWheel += ImageView_MouseWheel;
46 overviewPanel.MouseEnter += (_, _) => overviewPanel.Focus();
47 overviewPanel.MouseCaptureChanged += (_, _) =>
48 {
49 if (!overviewPanel.Capture)
50 overviewDragging = false;
51 };
52 viewpanel.Controls.Add(overviewPanel);
53 Disposed += (_, _) =>
54 {
55 previewVersion++;
56 overviewImage?.Dispose();
57 overviewImage = null;
58 };
59 }
60
61 private BioImage OverviewSelectedImage =>
62 SelectedIndex >= 0 && SelectedIndex < Images.Count ? Images[SelectedIndex] : null;
63
64 private void UpdateOverviewVisibility()
65 {
66 if (overviewPanel.IsDisposed)
67 return;
68 overviewPanel.Visible = ShowOverview && overviewImage != null &&
69 overviewSource == OverviewSelectedImage;
70 if (overviewPanel.Visible)
71 overviewPanel.BringToFront();
72 }
73
74 private void SetOverviewPreview(Drawing.Image source, BioImage image)
75 {
76 int width = image.isPyramidal ? image.Resolutions[0].SizeX : source.Width;
77 int height = image.isPyramidal ? image.Resolutions[0].SizeY : source.Height;
78 double ratio = 160.0 / Math.Max(width, height);
79 int previewWidth = Math.Max(1, (int)Math.Round(width * ratio));
80 int previewHeight = Math.Max(1, (int)Math.Round(height * ratio));
81 var thumbnail = new Drawing.Bitmap(previewWidth, previewHeight);
82 using (var graphics = Drawing.Graphics.FromImage(thumbnail))
83 {
84 graphics.InterpolationMode = Drawing.Drawing2D.InterpolationMode.HighQualityBilinear;
85 graphics.DrawImage(source, 0, 0, previewWidth, previewHeight);
86 }
87 overviewImage?.Dispose();
88 overviewImage = thumbnail;
89 overviewSource = image;
90 overview = new AForge.Rectangle(8, 8, previewWidth, previewHeight);
91 overviewPanel.SetBounds(overview.X, overview.Y, overview.Width, overview.Height);
92 UpdateOverviewVisibility();
93 overviewPanel.Invalidate();
94 }
95
96 private void UpdateRegularOverview()
97 {
98 BioImage image = OverviewSelectedImage;
99 if (image == null || image.isPyramidal || SelectedIndex >= Bitmaps.Count)
100 return;
101 previewVersion++;
102 previewRequestImage = null;
103 SetOverviewPreview(Bitmaps[SelectedIndex], image);
104 }
105
106 private async void InitPreview()
107 {
108 BioImage image = OverviewSelectedImage;
109 if (image == null || !image.isPyramidal)
110 return;
111 var coordinate = (image.Coordinate.Z, image.Coordinate.C, image.Coordinate.T);
112 if (previewRequestImage == image && previewRequestCoordinate == coordinate)
113 return;
114 previewRequestImage = image;
115 previewRequestCoordinate = coordinate;
116 int version = ++previewVersion;
117 UpdateOverviewVisibility();
118 try
119 {
120 if (image.OpenSlideBase != null)
121 {
122 // OpenSlideBase.GetTile returns a schema-sized tile, not an arbitrary
123 // region. Choose a level that retains enough pixels for the overview.
124 var slide = image.OpenSlideBase;
125 var levels = slide.Schema.Resolutions.OrderBy(r => r.Value.UnitsPerPixel).ToArray();
126 var levelInfo = levels.Where(r =>
127 Math.Max(slide.Schema.Extent.Width, slide.Schema.Extent.Height) /
128 r.Value.UnitsPerPixel >= 160).DefaultIfEmpty(levels[0]).Last();
129 int width = (int)Math.Ceiling(slide.Schema.Extent.Width / levelInfo.Value.UnitsPerPixel);
130 int height = (int)Math.Ceiling(slide.Schema.Extent.Height / levelInfo.Value.UnitsPerPixel);
131 if ((long)width * height > 16_000_000)
132 return;
133 using Drawing.Bitmap slidePreview = await Task.Run(() => SlideOverviewBuilder.Build(width, height,
134 levelInfo.Value.TileWidth, levelInfo.Value.TileHeight,
135 (column, row) => slide.GetTile(new BruTile.TileInfo
136 {
137 Index = new BruTile.TileIndex(column, row, levelInfo.Key)
138 })));
139 if (version == previewVersion && !IsDisposed && !Disposing && image == OverviewSelectedImage)
140 SetOverviewPreview(slidePreview, image);
141 return;
142 }
143 // Use a native slide level, never the label or macro image.
144 int level = GetPreviewLevel(image);
145 var resolution = image.Resolutions[level];
146 if ((long)resolution.SizeX * resolution.SizeY > 16_000_000)
147 return;
148 int frame = image.GetFrameIndex(coordinate.Z, coordinate.C, coordinate.T);
149 using Bitmap tile = await image.GetTile(frame, level, 0, 0, resolution.SizeX, resolution.SizeY,
150 new ZCT(coordinate.Item1, coordinate.Item2, coordinate.Item3), true);
151 if (tile == null || version != previewVersion || IsDisposed || Disposing ||
152 image != OverviewSelectedImage)
153 return;
154 using Drawing.Bitmap display = ToSysBitmap(tile);
155 SetOverviewPreview(display, image);
156 }
157 catch (Exception ex)
158 {
159 System.Diagnostics.Trace.WriteLine($"Could not load slide overview: {ex.Message}");
160 }
161 }
162
163 private RectangleD OverviewWorldBounds(BioImage image)
164 {
165 if (image.isPyramidal)
166 {
167 // The renderer addresses the tile schema's world coordinates, which
168 // may use a different calibration from BioImage's metadata.
169 var extent = image.OpenSlideBase?.Schema.Extent ?? image.SlideBase?.Schema.Extent;
170 if (extent is { } schemaExtent)
171 return new RectangleD(schemaExtent.MinX, -schemaExtent.MaxY, schemaExtent.Width, schemaExtent.Height);
172 double units = image.GetUnitPerPixel(0);
173 return new RectangleD(0, 0, image.Resolutions[0].SizeX * units,
174 image.Resolutions[0].SizeY * units);
175 }
176 return new RectangleD(image.Volume.Location.X, image.Volume.Location.Y,
177 image.Volume.Width, image.Volume.Height);
178 }
179
180 private void OverviewPaint(object sender, PaintEventArgs e)
181 {
182 BioImage image = OverviewSelectedImage;
183 if (overviewImage == null || image == null || overviewSource != image)
184 return;
185 e.Graphics.DrawImageUnscaled(overviewImage, 0, 0);
186 RectangleD bounds = OverviewWorldBounds(image);
187 if (bounds.W <= 0 || bounds.H <= 0)
188 return;
189 // Use this viewer's transform even when a different tab is active.
190 PointD topLeft = image.isPyramidal
191 ? new PointD(image.PyramidalOrigin.X, image.PyramidalOrigin.Y)
192 : new PointD(-ViewWidth / 2 / (PxWmicron * Scale.Width) - Origin.X,
193 -ViewHeight / 2 / (PxHmicron * Scale.Height) - Origin.Y);
194 double viewWidth = image.isPyramidal ? ViewWidth * image.Resolution : ViewWidth / (PxWmicron * Scale.Width);
195 double viewHeight = image.isPyramidal ? ViewHeight * image.Resolution : ViewHeight / (PxHmicron * Scale.Height);
196 double left = Math.Clamp((topLeft.X - bounds.X) / bounds.W, 0, 1);
197 double top = Math.Clamp((topLeft.Y - bounds.Y) / bounds.H, 0, 1);
198 double right = Math.Clamp((topLeft.X + viewWidth - bounds.X) / bounds.W, 0, 1);
199 double bottom = Math.Clamp((topLeft.Y + viewHeight - bounds.Y) / bounds.H, 0, 1);
200 if (right > left && bottom > top)
201 {
202 using var pen = new Drawing.Pen(Drawing.Color.OrangeRed, 2);
203 e.Graphics.DrawRectangle(pen, (float)(left * (overview.Width - 1)),
204 (float)(top * (overview.Height - 1)), (float)((right - left) * (overview.Width - 1)),
205 (float)((bottom - top) * (overview.Height - 1)));
206 }
207 e.Graphics.DrawRectangle(Drawing.Pens.White, 0, 0, overview.Width - 1, overview.Height - 1);
208 }
209
210 private void NavigateOverview(int x, int y)
211 {
212 BioImage image = OverviewSelectedImage;
213 if (image == null || overviewImage == null || overviewSource != image)
214 return;
215 App.viewer = this;
216 RectangleD bounds = OverviewWorldBounds(image);
217 double targetX = bounds.X + Math.Clamp(x / (double)overview.Width, 0, 1) * bounds.W;
218 double targetY = bounds.Y + Math.Clamp(y / (double)overview.Height, 0, 1) * bounds.H;
219 if (image.isPyramidal)
220 {
221 PyramidalOrigin = new PointD(targetX - ViewWidth * Resolution / 2.0,
222 targetY - ViewHeight * Resolution / 2.0);
223 UpdateImages();
224 }
225 else
226 Origin = new PointD(-targetX, -targetY);
227 UpdateView();
228 }
229
230 private MouseEventArgs OverviewCanvasEvent(MouseEventArgs e) =>
231 new MouseEventArgs(e.Button, e.Clicks, e.X + overview.X, e.Y + overview.Y, e.Delta);
232
233 private void OverviewMouseDown(object sender, MouseEventArgs e)
234 {
235 if (e.Button == MouseButtons.Left)
236 {
237 overviewDragging = true;
238 overviewPanel.Capture = true;
239 NavigateOverview(e.X, e.Y);
240 }
241 else if (e.Button == MouseButtons.Middle)
242 pictureBox_MouseDown(sender, OverviewCanvasEvent(e));
243 }
244
245 private void OverviewMouseMove(object sender, MouseEventArgs e)
246 {
247 if (overviewDragging && e.Button.HasFlag(MouseButtons.Left))
248 NavigateOverview(e.X, e.Y);
249 else if (e.Button == MouseButtons.Middle)
250 rgbPictureBox_MouseMove(sender, OverviewCanvasEvent(e));
251 }
252
253 private void OverviewMouseUp(object sender, MouseEventArgs e)
254 {
255 if (e.Button == MouseButtons.Left)
256 {
257 overviewDragging = false;
258 overviewPanel.Capture = false;
259 }
260 else if (e.Button == MouseButtons.Middle)
261 pictureBox_MouseUp(sender, OverviewCanvasEvent(e));
262 }
263 }
264}
AForge.Bitmap Bitmap
AForge.RectangleD RectangleD
System.Drawing Drawing
AForge.PointD PointD
BioLib.BioImage BioImage
Definition TabsView.cs:15
AForge.ZCT ZCT
Definition TabsView.cs:17
List< BioImage > Images
Definition ImageView.cs:323
new AForge.SizeF Scale
Scale when opening image stacks.
Definition ImageView.cs:779
PointD Origin
A property of the class PointD. *‍/.
Definition ImageView.cs:718
PointD PyramidalOrigin
Setting the value of the pyramidalOrigin variable. *‍/.
Definition ImageView.cs:730
void UpdateView(bool refreshImages=true)
double PxHmicron
Only used when opening stacks.
Definition ImageView.cs:588
double PxWmicron
Only used when opening stacks.
Definition ImageView.cs:573
int SelectedIndex
A property that is used to set the selected index of the image. *‍/.
Definition ImageView.cs:390
async void UpdateImages(bool updatePyramidal=true)
double Resolution
A property of the class ImageViewer. It is a getter and setter for the resolution of the image.
Definition ImageView.cs:746
Drawing.Bitmap overviewImage