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
SAMTool.cs
1using AForge;
2using Atk;
3using Gtk;
4using System;
5using System.Collections.Generic;
6using System.Linq;
7using System.Text;
8using System.Threading.Tasks;
9using Gdk;
10using System.Threading;
11using System.IO;
12
13namespace BioGTK
14{
15
16 public class SAMTool : Gtk.Window
17 {
18 static Random rng = new Random();
19 #region Properties
20 ImageView view;
21 public static SAM sam;
22 public static MicroSAM microsam;
23 public List<Promotion> Promotions = new List<Promotion>();
24 public bool init = false;
25 public static bool microSAM
26 {
27 get
28 {
29 if (microsam == null)
30 return false;
31 else
32 return true;
33 }
34 }
35 int id = 1;
36 string autoSavePath = "Masks";
37 private ROI SelectedROI
38 {
39 get
40 {
41 List<ROI> roi = view.GetSelectedROIs();
42 if (roi.Count > 0)
43 return roi.Last();
44 else
45 return view.Images[0].Annotations[0];
46 return null;
47 }
48 set
49 {
50 for (int i = 0; i < ImageView.SelectedImage.Annotations.Count; i++)
51 {
52 if (ImageView.SelectedImage.Annotations[i].Selected)
53 ImageView.SelectedImage.Annotations[i] = value;
54 }
55 }
56 }
57 private AForge.Point _startPoint;
58 private Builder _builder;
59 Gtk.FileChooserDialog filechooser;
60#pragma warning disable 649
61 [Builder.Object]
62 public MenuBar MainMenu;
63 [Builder.Object]
64 private MenuItem saveSelected;
65 [Builder.Object]
66 private MenuItem setAutoSave;
67 [Builder.Object]
68 private MenuItem toROIMenu;
69 [Builder.Object]
70 private Gtk.CheckMenuItem autoSaveMenu;
71 [Builder.Object]
72 private Gtk.Button runButton;
73 [Builder.Object]
74 private Gtk.Button clearButton;
75 [Builder.Object]
76 private Gtk.Entry textBox;
77 [Builder.Object]
78 private Gtk.Scale thresholdBar;
79 [Builder.Object]
80 private Gtk.Scale pointsBar;
81 [Builder.Object]
82 private Gtk.Scale predictionBar;
83 [Builder.Object]
84 private Gtk.Scale stabilityBar;
85 [Builder.Object]
86 private Gtk.Scale layersBar;
87 [Builder.Object]
88 private Gtk.Button deleteDuplicatesBut;
89 [Builder.Object]
90 private Gtk.Button clearSliceBut;
91 [Builder.Object]
92 private Gtk.Button SAMEncodeBut;
93 [Builder.Object]
94 private Gtk.CheckButton foregroundBut;
95 [Builder.Object]
96 private Gtk.Scale minAreaBar;
97 [Builder.Object]
98 private Gtk.Scale maxAreaBar;
99#pragma warning restore 649
100
101 #endregion
102
103 #region Constructors / Destructors
104
109 public static SAMTool Create(bool micro_SAM)
110 {
111 Builder builder = new Builder(new FileStream(System.IO.Path.GetDirectoryName(Environment.ProcessPath) + "/" + "Glade/SAM.glade", FileMode.Open));
112 if (micro_SAM)
113 {
114 sam = MicroSAM.Instance();
115 microsam = MicroSAM.Instance();
116 }
117 else
118 sam = SAM.Instance();
119 return new SAMTool(builder, builder.GetObject("SAMTool").Handle);
120 }
121
122 /* Setting up the UI. */
123 protected SAMTool(Builder builder, IntPtr handle) : base(handle)
124 {
125 //First lets make sure the model files are installed.
126 string app = System.IO.Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location);
127 if (microSAM)
128 {
129 if (!File.Exists(app + "/micro-sam-decoder.onnx") || !File.Exists(app + "/micro-sam-encoder.onnx"))
130 {
131 MessageDialog dialog = new MessageDialog(
132 this,
133 DialogFlags.Modal,
134 MessageType.Info,
135 ButtonsType.Ok,
136 "Place Model files \"micro-sam-decoder.onnx\" and \"micro-sam-encoder.onnx\" in the application folder to use SAM Tool.");
137 dialog.Run();
138 return;
139 }
140 }
141 else
142 {
143 if (!File.Exists(app + "/decoder-quant.onnx") || !File.Exists(app + "/encoder-quant.onnx"))
144 {
145 MessageDialog dialog = new MessageDialog(
146 this,
147 DialogFlags.Modal,
148 MessageType.Info,
149 ButtonsType.Ok,
150 "Place Model files \"decoder-quant.onnx\" and \"encoder-quant.onnx\" in the application folder to use SAM Tool.");
151 dialog.Run();
152 return;
153 }
154 }
155 view = App.viewer;
156 _builder = builder;
157 builder.Autoconnect(this);
158 thresholdBar.Adjustment.Lower = 0;
159 if (ImageView.SelectedImage.SelectedBuffer.BitsPerPixel > 8)
160 thresholdBar.Adjustment.Upper = ushort.MaxValue;
161 else
162 thresholdBar.Adjustment.Upper = 255;
163 stabilityBar.Adjustment.Lower = 0;
164 stabilityBar.Adjustment.Upper = 1;
165 stabilityBar.Adjustment.StepIncrement = 0.05;
166 stabilityBar.Value = 0.5;
167 predictionBar.Adjustment.Lower = 0;
168 predictionBar.Adjustment.Upper = 1;
169 predictionBar.Adjustment.StepIncrement = 0.05;
170 predictionBar.Value = 0.5;
171 pointsBar.Adjustment.Lower = 0;
172 pointsBar.Adjustment.Upper = 100;
173 pointsBar.Adjustment.StepIncrement = 1;
174 pointsBar.Value = 4;
175 layersBar.Adjustment.Lower = 0;
176 layersBar.Adjustment.Upper = 4;
177 layersBar.Value = 0;
178 minAreaBar.Value = 0;
179 minAreaBar.Adjustment.Upper = ImageView.SelectedImage.Volume.Width * ImageView.SelectedImage.Volume.Height;
180 minAreaBar.Adjustment.Lower = 0;
181
182 maxAreaBar.Adjustment.Upper = ImageView.SelectedImage.Volume.Width * ImageView.SelectedImage.Volume.Height;
183 maxAreaBar.Adjustment.Lower = 0;
184 maxAreaBar.Value = ImageView.SelectedImage.Volume.Width * ImageView.SelectedImage.Volume.Height;
185 foregroundBut.Active = true;
186 Directory.CreateDirectory("Masks");
187 filechooser =
188 new Gtk.FileChooserDialog("Choose file to open",
189 this,
190 FileChooserAction.Open,
191 "Cancel", ResponseType.Cancel,
192 "OK", ResponseType.Accept);
194 Init();
195 App.ApplyStyles(this);
196 init = true;
197 }
198
199 #endregion
200
201 #region Handlers
202
204 protected void SetupHandlers()
205 {
206 saveSelected.ButtonPressEvent += saveSelectedTiffClick;
207 view.ButtonReleaseEvent += PictureBox_ButtonReleaseEvent;
208 setAutoSave.ButtonPressEvent += SetAutoSave_ButtonPressEvent;
209 this.DeleteEvent += SAMTool_DeleteEvent;
210 textBox.Changed += TextBox_Changed;
211 toROIMenu.ButtonPressEvent += ToROIMenu_ButtonPressEvent;
212 runButton.ButtonPressEvent += AutoSAMMenu_ButtonPressEvent;
213 clearButton.ButtonPressEvent += ClearButton_ButtonPressEvent;
214 deleteDuplicatesBut.ButtonPressEvent += DeleteDuplicatesBut_ButtonPressEvent;
215 SAMEncodeBut.ButtonPressEvent += SAMEncodeBut_ButtonPressEvent;
216 clearSliceBut.ButtonPressEvent += ClearSliceBut_ButtonPressEvent;
217 }
218
219 private void ClearSliceBut_ButtonPressEvent(object o, ButtonPressEventArgs args)
220 {
221 for (int i = 0; i < ImageView.SelectedImage.Annotations.Count; i++)
222 {
223 if (ImageView.SelectedImage.Annotations[i].coord == App.viewer.GetCoordinate())
224 ImageView.SelectedImage.Annotations.Remove(ImageView.SelectedImage.Annotations[i]);
225 }
226 }
227
228 private void SAMEncodeBut_ButtonPressEvent(object o, ButtonPressEventArgs args)
229 {
230 if(MicroSAM.theSingleton== null)
231 sam.Encode(ImageView.SelectedImage);
232 else
233 microsam.Encode(ImageView.SelectedImage);
234 }
235
236 private void DeleteDuplicatesBut_ButtonPressEvent(object sender, ButtonPressEventArgs args)
237 {
238 SAM.RemoveDuplicates(true, (float)(3*ImageView.SelectedImage.PhysicalSizeX), true, (float)maxAreaBar.Value);
239 }
240 private void ClearButton_ButtonPressEvent(object o, ButtonPressEventArgs args)
241 {
242 List<ROI> masks = new List<ROI>();
243 foreach (ROI item in ImageView.SelectedImage.Annotations)
244 {
245 if(item.type == ROI.Type.Mask)
246 masks.Add(item);
247 }
248 foreach (ROI mask in masks)
249 {
250 ImageView.SelectedImage.Annotations.Remove(mask);
251 mask.Dispose();
252 }
253 foreach(ROI mask in ImageView.selectedAnnotations)
254 {
255 if(mask.type == ROI.Type.Mask)
256 mask.Dispose();
257 }
258 ImageView.selectedAnnotations.Clear();
259 Promotions.Clear();
260
261 }
262
263 private void AutoSAMMenu_ButtonPressEvent(object o, ButtonPressEventArgs args)
264 {
265 SAM.Run((float)minAreaBar.Value, (float)maxAreaBar.Value, (float)stabilityBar.Value, (float)predictionBar.Value, (int)layersBar.Value, (int)pointsBar.Value);
266 }
267
268 private void ToROIMenu_ButtonPressEvent(object o, ButtonPressEventArgs args)
269 {
270 List<ROI> rois = new List<ROI>();
271 List<ROI> roiss = new List<ROI>();
272 rois.AddRange(ImageView.SelectedImage.AnnotationsR);
273 rois.AddRange(ImageView.SelectedImage.AnnotationsG);
274 rois.AddRange(ImageView.SelectedImage.AnnotationsB);
275 foreach (ROI r in roiss)
276 {
277 if (r.roiMask == null)
278 continue;
279 List<PointD> ps = GetEdgePolygonFromMask(new Bitmap("",r.roiMask.Width,r.roiMask.Height,PixelFormat.Format32bppArgb, r.roiMask.GetBytes(), App.viewer.GetCoordinate(), 0));
280 for (int i = 0; i < ps.Count; i++)
281 {
282 ps[i] = new PointD(ps[i].X * ImageView.SelectedImage.PhysicalSizeX, ps[i].Y * ImageView.SelectedImage.PhysicalSizeY);
283 }
284 ROI rs = ROI.CreatePolygon(App.viewer.GetCoordinate(), ps.ToArray());
285 rois.Add(rs);
286 }
287 ImageView.SelectedImage.Annotations.AddRange(rois.ToArray());
288 }
289 static List<PointD> GetEdgePolygonFromMask(Bitmap maskBitmap)
290 {
291 List<PointD> edgePolygon = new List<PointD>();
292
293 // Lock the bitmap data to access pixel values
294 BitmapData data = maskBitmap.LockBits(new AForge.Rectangle(0, 0, maskBitmap.Width, maskBitmap.Height),
295 ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb);
296
297 unsafe
298 {
299 byte* ptr = (byte*)data.Scan0;
300
301 for (int y = 1; y < maskBitmap.Height - 1; y++)
302 {
303 for (int x = 1; x < maskBitmap.Width - 1; x++)
304 {
305 int index = y * data.Stride + x * 4;
306
307 // Check if the pixel is non-zero (white)
308 if (ptr[index] > 0)
309 {
310 // Check if at least one neighboring pixel is zero (black)
311 if (ptr[index - data.Stride] == 0 || ptr[index + data.Stride] == 0 || ptr[index - 4] == 0 || ptr[index + 4] == 0)
312 {
313 edgePolygon.Add(new PointD(x, y));
314 }
315 }
316 }
317 }
318 }
319
320 // Unlock the bitmap data
321 maskBitmap.UnlockBits(data);
322
323 return OrderPolygon(edgePolygon);
324 }
325 static List<PointD> OrderPolygon(List<PointD> polygon)
326 {
327 // Calculate the centroid of the polygon
328 PointD centroid = new PointD((int)polygon.Average(p => p.X), (int)polygon.Average(p => p.Y));
329
330 // Sort the points based on polar angle
331 List<PointD> orderedPolygon = polygon.OrderBy(p => Math.Atan2(p.Y - centroid.Y, p.X - centroid.X)).ToList();
332
333 return orderedPolygon;
334 }
335 private void SAMTool_DeleteEvent(object o, DeleteEventArgs args)
336 {
337 if (MicroSAM.theSingleton == null)
338 sam.Dispose();
339 else
340 microsam.Dispose();
341 BioLib.Recorder.AddLine("App.CloseWindow(\"SAM Tool\");",false);
342 }
343
355 private void SetAutoSave_ButtonPressEvent(object o, ButtonPressEventArgs args)
356 {
357 filechooser.Action = FileChooserAction.SelectFolder;
358 filechooser.Title = "Choose Auto Save Folder.";
359 if (filechooser.Run() != (int)ResponseType.Accept)
360 return;
361 filechooser.Hide();
362 autoSavePath = filechooser.CurrentFolder;
363 }
364
365 private void TextBox_Changed(object sender, EventArgs e)
366 {
367 id = 1;
368 }
369
376 public void Init(bool wait = false)
377 {
378 if (ImageView.SelectedImage == null)
379 return;
380 if (MicroSAM.theSingleton == null)
381 {
382 sam.LoadONNXModel();
383 Thread th2 = new Thread(sam.Encode);
384 th2.Start();
385 }
386 else
387 {
388 microsam.LoadONNXModel();
389 Thread th2 = new Thread(microsam.Encode);
390 th2.Start();
391 }
392 do
393 {
394 Thread.Sleep(250);
395 if (microsam.Initialized)
396 break;
397 } while (wait);
398 BioLib.Recorder.AddLine("App.samTool.Init(true);",false);
399 }
400
401 internal static void Encode()
402 {
403 if(MicroSAM.theSingleton == null)
404 sam.Encode(ImageView.SelectedImage);
405 else
406 microsam.Encode(ImageView.SelectedImage);
407 }
408
420 private void PictureBox_ButtonReleaseEvent(object o, ButtonReleaseEventArgs e)
421 {
422 if (view.Images.Count < 1)
423 return;
424 if (view.Images[0].Annotations.Count == 0)
425 return;
426 if (SelectedROI == null)
427 SelectedROI = view.Images[0].Annotations[0];
428 List<ROI> rois = new List<ROI>();
429 rois.AddRange(ImageView.SelectedImage.Annotations);
430 PointD[] pls = SelectedROI.ImagePoints(ImageView.SelectedImage.Resolutions[0]);
431 if (Tools.currentTool.type == Tools.Tool.Type.point && rois.Count > 0 && e.Event.State.HasFlag(ModifierType.Button1Mask))
432 {
433 ForeGround type = this.foregroundBut.Active == true ? ForeGround.foreground : ForeGround.background;
434 Promotion promtt = new PointPromotion(type);
435 (promtt as PointPromotion).X = (int)pls[0].X;
436 (promtt as PointPromotion).Y = (int)pls[0].Y;
437 Transforms trs = new Transforms(1024);
438 PointPromotion ptn = trs.ApplyCoords((promtt as PointPromotion), ImageView.SelectedBuffer.Width, ImageView.SelectedBuffer.Height);
439 this.Promotions.Add(ptn);
440 float[] maskk;
441 if(MicroSAM.theSingleton != null)
442 maskk = microsam.Decode(ImageView.SelectedImage, this.Promotions, ImageView.SelectedBuffer.Width, ImageView.SelectedBuffer.Height);
443 else
444 maskk = sam.Decode(ImageView.SelectedImage, this.Promotions, ImageView.SelectedBuffer.Width, ImageView.SelectedBuffer.Height);
445 this.ShowMask(maskk, ImageView.SelectedBuffer.Width, ImageView.SelectedBuffer.Height);
446 this.Promotions.Clear();
447 }
448
449 if (Tools.currentTool.type == Tools.Tool.Type.rect && e.Event.State.HasFlag(ModifierType.Button1Mask))
450 {
451 SelectedROI.Selected = true;
452 BoxPromotion promt = new BoxPromotion();
453 if (pls[0].X < (int)pls[3].X || pls[0].Y < (int)pls[3].Y)
454 {
455 (promt as BoxPromotion).mLeftUp.X = (int)pls[0].X;
456 (promt as BoxPromotion).mLeftUp.Y = (int)pls[0].Y;
457 (promt as BoxPromotion).mRightBottom.X = (int)pls[3].X;
458 (promt as BoxPromotion).mRightBottom.Y = (int)pls[3].Y;
459 }
460 else
461 {
462 (promt as BoxPromotion).mLeftUp.X = (int)pls[3].X;
463 (promt as BoxPromotion).mLeftUp.Y = (int)pls[3].Y;
464 (promt as BoxPromotion).mRightBottom.X = (int)pls[0].X;
465 (promt as BoxPromotion).mRightBottom.Y = (int)pls[0].Y;
466 }
467 Transforms ts = new Transforms(1024);
468 var pb = ts.ApplyBox(promt, ImageView.SelectedBuffer.Width, ImageView.SelectedBuffer.Height);
469 this.Promotions.Add(pb);
470
471 float[] mask;
472 if (MicroSAM.theSingleton != null)
473 mask = microsam.Decode(ImageView.SelectedImage, this.Promotions, ImageView.SelectedBuffer.Width, ImageView.SelectedBuffer.Height);
474 else
475 mask = sam.Decode(ImageView.SelectedImage, this.Promotions, ImageView.SelectedBuffer.Width, ImageView.SelectedBuffer.Height);
476 ShowMask(mask, ImageView.SelectedBuffer.Width, ImageView.SelectedBuffer.Height);
477 Promotions.Clear();
478 }
479 }
480
488 protected void saveSelectedTiffClick(object sender, EventArgs a)
489 {
490 filechooser.Action = FileChooserAction.Save;
491 //Saving Tiff can cause a crash on Windows so we check the platform.
492 if (Environment.OSVersion.Platform == PlatformID.Win32NT)
493 {
494 filechooser.Title = "Save File PNG";
495 }
496 else
497 {
498 filechooser.Title = "Save File Tiff";
499 }
500 if (filechooser.Run() != (int)ResponseType.Accept)
501 return;
502 if (Environment.OSVersion.Platform == PlatformID.Win32NT)
503 {
504 SelectedROI.roiMask.Pixbuf.Save(filechooser.Filename, "png");
505 }
506 else
507 {
508 SelectedROI.roiMask.Pixbuf.Save(filechooser.Filename, "tiff");
509 }
510 filechooser.Hide();
511 }
512 #endregion
513
522 void ShowMask(float[] mask, int width, int height)
523 {
524 if (SelectedROI.type != ROI.Type.Point && SelectedROI.type != ROI.Type.Rectangle)
525 return;
526 ROI an = ROI.CreateMask(App.viewer.GetCoordinate(),mask,width,height,
527 new PointD(ImageView.SelectedImage.StageSizeX,ImageView.SelectedImage.StageSizeY),ImageView.SelectedImage.PhysicalSizeX,ImageView.SelectedImage.PhysicalSizeY);
528 byte r = (byte)rng.Next(0, 255);
529 byte g = (byte)rng.Next(0, 255);
530 byte b = (byte)rng.Next(0, 255);
531 an.fillColor = AForge.Color.FromArgb(r, g, b);
532
533 if (autoSaveMenu.Active)
534 {
535 //Saving Tiff can cause a crash on Windows so we check the platform.
536 if (Environment.OSVersion.Platform == PlatformID.Win32NT)
537 {
538 an.roiMask.Pixbuf.Save(autoSavePath + "/" + textBox.Text + id + ".png", "png");
539 }
540 else
541 {
542 an.roiMask.Pixbuf.Save(autoSavePath + "/" + textBox.Text + id + ".tiff", "tiff");
543 }
544 id++;
545 }
546 SelectedROI = an;
547 }
548 }
549}
new float[] Decode(BioImage b, List< Promotion > promotions, int orgWid, int orgHei)
Definition MicroSAM.cs:152
new void LoadONNXModel()
The function LoadONNXModel loads ONNX models for encoding and decoding.
Definition MicroSAM.cs:46
static new MicroSAM Instance()
Definition MicroSAM.cs:36
new void Dispose()
The Dispose function disposes of resources and sets variables to null.
Definition MicroSAM.cs:353
float[] Decode(BioImage b, List< Promotion > promotions, int orgWid, int orgHei)
Definition SAM.cs:153
static SAM Instance()
Definition SAM.cs:33
void Dispose()
The Dispose function disposes of resources and sets variables to null.
Definition SAM.cs:471
void LoadONNXModel()
The function LoadONNXModel loads ONNX models for encoding and decoding.
Definition SAM.cs:43
void Init(bool wait=false)
Definition SAMTool.cs:376
static SAMTool Create(bool micro_SAM)
Definition SAMTool.cs:109
void saveSelectedTiffClick(object sender, EventArgs a)
Definition SAMTool.cs:488
void SetupHandlers()
This function sets up the event handlers for the menu items.
Definition SAMTool.cs:204
Definition ML.cs:17
Definition Zarr.cs:20