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
TabsView.cs
1using AForge;
2using Bio;
3using Gtk;
4using omero.gateway;
5using Pango;
6using System;
7using System.Collections;
8using System.Collections.Generic;
9using System.ComponentModel;
10using System.Diagnostics;
11using System.IO;
12using System.Linq;
13using System.Runtime.InteropServices;
14using System.Text;
15using System.Text.Json;
16using System.Threading.Tasks;
17
18namespace BioGTK
19{
20
21 public class TabsView : Window
22 {
23 #region Properties
24 private Builder _builder;
25 private List<ImageView> viewers = new List<ImageView>();
26 private const int MaxRecentImages = 10;
27 private readonly List<string> recentImages = new List<string>();
28 private readonly Menu recentImagesMenu = new Menu();
29 private MenuItem openRecentMenu;
30 private MenuItem clearRecentImagesMenu;
31 private static readonly string RecentImagesPath = System.IO.Path.Combine(
32 Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData),
33 "BioGTK", "recent-images.json");
34 private static string Q(string s)
35 {
36 return "\"" + s.Replace("\\", "\\\\").Replace("\"", "\\\"") + "\"";
37 }
38 private static string Bool(bool value)
39 {
40 return value.ToString().ToLowerInvariant();
41 }
42 private static string StrArray(string[] values)
43 {
44 return "new string[] { " + string.Join(", ", values.Select(Q)) + " }";
45 }
46 public ImageView GetViewer(int i)
47 {
48 return viewers[i];
49 }
50 public ImageView? GetViewer(string name)
51 {
52 for (int v = 0; v < viewers.Count; v++)
53 {
54 for (int i = 0; i < viewers[v].Images.Count; i++)
55 {
56 if (viewers[v].Images[i].Filename == name)
57 return viewers[v];
58 }
59 }
60 return null;
61 }
62 public int GetViewerCount()
63 {
64 return viewers.Count;
65 }
66 public void RemoveViewer(ImageView v)
67 {
68 viewers.Remove(v);
69 }
70 public void AddViewer(ImageView v)
71 {
72 viewers.Add(v);
73 }
74
75 private static string GetSeriesDisplayName(string file, int series, int seriesCount)
76 {
77 if (seriesCount <= 1)
78 return System.IO.Path.GetFileName(file);
79
80 string baseName = System.IO.Path.GetFileNameWithoutExtension(file);
81 string ext = System.IO.Path.GetExtension(file);
82 if (string.IsNullOrWhiteSpace(ext))
83 ext = string.Empty;
84 return $"{baseName}_s{series}{ext}";
85 }
86 private static bool IsRemoteZarrSource(BioImage image)
87 {
88 if (image == null || !image.IsZarrSource)
89 return false;
90
91 string source = image.SourceFile;
92 return !string.IsNullOrWhiteSpace(source) &&
93 (source.StartsWith("https://", StringComparison.OrdinalIgnoreCase) ||
94 source.StartsWith("http://", StringComparison.OrdinalIgnoreCase) ||
95 source.StartsWith("s3://", StringComparison.OrdinalIgnoreCase));
96 }
97
98 private static bool IsZarrLikeSource(string file)
99 {
100 if (string.IsNullOrWhiteSpace(file))
101 return false;
102
103 return file.Contains(".zarr", StringComparison.OrdinalIgnoreCase) ||
104 file.StartsWith("https://", StringComparison.OrdinalIgnoreCase) ||
105 file.StartsWith("http://", StringComparison.OrdinalIgnoreCase) ||
106 file.StartsWith("s3://", StringComparison.OrdinalIgnoreCase);
107 }
108
109 private static void EnsureLargeZarrUsesPyramidalViewer(BioImage image)
110 {
111 if (image == null || !image.IsZarrSource || image.Resolutions.Count == 0)
112 return;
113
114 image.PyramidNativeLevelCount = image.Resolutions.Count;
115 if (image.Resolutions.Count > 1)
116 return;
117
118 const int largeImageDimension = 2048;
119 const int pyramidTargetDimension = 1024;
120 Resolution baseResolution = image.Resolutions[0];
121 if (baseResolution.SizeX <= largeImageDimension &&
122 baseResolution.SizeY <= largeImageDimension)
123 {
124 return;
125 }
126
127 Resolution previous = baseResolution;
128 int width = baseResolution.SizeX;
129 int height = baseResolution.SizeY;
130 while (width > pyramidTargetDimension || height > pyramidTargetDimension)
131 {
132 width = Math.Max(1, (width + 1) / 2);
133 height = Math.Max(1, (height + 1) / 2);
134 previous = new Resolution(
135 width,
136 height,
137 previous.PixelFormat,
138 previous.PhysicalSizeX * 2,
139 previous.PhysicalSizeY * 2,
140 previous.PhysicalSizeZ,
141 previous.StageSizeX,
142 previous.StageSizeY,
143 previous.StageSizeZ);
144 image.Resolutions.Add(previous);
145 }
146
147 // OpenURL creates SlideBase before returning. Rebuild it so its tile
148 // schema includes the synthetic levels that were added above.
149 var slideImage = new SlideImage { BioImage = image };
150 image.SlideBase = new SlideBase(image, slideImage);
151 }
152
153 private static async Task ApplyRemoteZarrDefaultCoordinateAsync(BioImage image, string source)
154 {
155 if (image == null || string.IsNullOrWhiteSpace(source))
156 return;
157 if (!source.StartsWith("https://", StringComparison.OrdinalIgnoreCase) &&
158 !source.StartsWith("http://", StringComparison.OrdinalIgnoreCase) &&
159 !source.StartsWith("s3://", StringComparison.OrdinalIgnoreCase))
160 {
161 return;
162 }
163
164 try
165 {
166 string metadataRoot = source;
167 if (metadataRoot.StartsWith("s3://", StringComparison.OrdinalIgnoreCase))
168 {
169 metadataRoot = "https://uk1s3.embassy.ebi.ac.uk/" +
170 metadataRoot.Substring("s3://".Length).TrimStart('/');
171 }
172
173 using var http = new System.Net.Http.HttpClient();
174 string json = await http.GetStringAsync(metadataRoot.TrimEnd('/') + "/zarr.json")
175 .ConfigureAwait(false);
176 using var document = JsonDocument.Parse(json);
177
178 JsonElement root = document.RootElement;
179 JsonElement omero = default;
180 bool foundOmero = false;
181 if (root.TryGetProperty("attributes", out JsonElement attributes))
182 {
183 if (attributes.TryGetProperty("ome", out JsonElement ome) &&
184 ome.TryGetProperty("omero", out omero))
185 {
186 foundOmero = true;
187 }
188 else if (attributes.TryGetProperty("omero", out omero))
189 {
190 foundOmero = true;
191 }
192 }
193 if (!foundOmero && root.TryGetProperty("omero", out omero))
194 foundOmero = true;
195 if (!foundOmero || !omero.TryGetProperty("rdefs", out JsonElement rdefs))
196 return;
197
198 int defaultZ = image.Coordinate.Z;
199 int defaultT = image.Coordinate.T;
200 if (rdefs.TryGetProperty("defaultZ", out JsonElement zValue) && zValue.TryGetInt32(out int z))
201 defaultZ = z;
202 if (rdefs.TryGetProperty("defaultT", out JsonElement tValue) && tValue.TryGetInt32(out int t))
203 defaultT = t;
204
205 defaultZ = Math.Clamp(defaultZ, 0, Math.Max(0, image.SizeZ - 1));
206 defaultT = Math.Clamp(defaultT, 0, Math.Max(0, image.SizeT - 1));
207 image.Coordinate = new ZCT(defaultZ, image.Coordinate.C, defaultT);
208 }
209 catch (Exception ex) when (
210 ex is System.Net.Http.HttpRequestException ||
211 ex is TaskCanceledException ||
212 ex is JsonException)
213 {
214 Console.WriteLine("Could not read the OME-Zarr default coordinate: " + ex.Message);
215 }
216 }
217
218 private async Task<BioImage?> OpenSeriesAsTabAsync(
219 string file,
220 int series,
221 int seriesCount,
222 bool tab,
223 bool tile,
224 int tileX,
225 int tileY,
226 int tileSizeX,
227 int tileSizeY,
228 bool addToRegistry = true)
229 {
230 BioImage im = await BioImage.OpenFile(file, series, tab, false, tile, tileX, tileY, tileSizeX, tileSizeY);
231 if (im == null)
232 return null;
233
234 if (seriesCount > 1)
235 im.Rename(GetSeriesDisplayName(file, series, seriesCount));
236
237 if (addToRegistry)
238 Images.AddImage(im);
239
240 AddTab(im, file);
241 return im;
242 }
243
244 private async Task<BioImage?> OpenSeriesAsViewAsync(
245 string file,
246 int series,
247 int seriesCount)
248 {
249 BioImage im = await BioImage.OpenFile(file, series, false, false, false, 0, 0, 0, 0);
250 if (im == null)
251 return null;
252
253 ShowImageView(im, file, series, seriesCount);
254 return im;
255 }
256
257 private async Task<List<BioImage>> CollectPyramidRootsAsync(string file)
258 {
259 if (IsZarrLikeSource(file))
260 {
261 BioImage? zarrImage = await BioImage.OpenFile(file, 0, false, false, false, 0, 0, 0, 0);
262 List<BioImage> singleRoot = new List<BioImage>();
263 if (zarrImage != null)
264 {
265 EnsureLargeZarrUsesPyramidalViewer(zarrImage);
266 await ApplyRemoteZarrDefaultCoordinateAsync(zarrImage, file);
267 singleRoot.Add(zarrImage);
268 }
269 return singleRoot;
270 }
271
272 int seriesCount = 1;
273 try
274 {
275 seriesCount = Math.Max(1, BioImage.GetSeriesCount(file));
276 }
277 catch
278 {
279 seriesCount = 1;
280 }
281
282 List<BioImage?> seriesImages = new List<BioImage?>(seriesCount);
283 for (int series = 0; series < seriesCount; series++)
284 {
285 BioImage? im = await BioImage.OpenFile(file, series, false, false, false, 0, 0, 0, 0);
286 seriesImages.Add(im);
287 }
288
289 List<BioImage> roots = new List<BioImage>();
290 for (int series = 0; series < seriesImages.Count; series++)
291 {
292 BioImage? cur = seriesImages[series];
293 BioImage? prev = series > 0 ? seriesImages[series - 1] : null;
294 if (series > 0 && IsContinuationSeries(prev, cur))
295 {
296 cur?.Dispose();
297 continue;
298 }
299
300 if (cur != null)
301 roots.Add(cur);
302 }
303
304 return roots;
305 }
306
307 private static bool IsContinuationSeries(BioImage? prev, BioImage? cur)
308 {
309 if (prev == null || cur == null)
310 return false;
311 if (prev.Resolutions.Count == 0 || cur.Resolutions.Count == 0)
312 return false;
313
314 Resolution pr = prev.Resolutions[0];
315 Resolution cr = cur.Resolutions[0];
316 return pr.SizeX > cr.SizeX &&
317 pr.SizeY > cr.SizeY &&
318 pr.PixelFormat == cr.PixelFormat;
319 }
320
321 private void ShowImageView(BioImage im, string file, int series, int seriesCount)
322 {
323 if (seriesCount > 1)
324 im.Rename(GetSeriesDisplayName(file, series, seriesCount));
325
326 TryLoadZarrROIs(im);
327 ImageView view = ImageView.Create(im);
328 view.RefreshZarrLabelOverlays(file);
329 if (!viewers.Contains(view))
330 viewers.Add(view);
331
332 Label dummy = new Gtk.Label(im.Filename);
333 dummy.Text = im.Filename;
334 dummy.UseMarkup = false;
335 dummy.Visible = false;
336 Label l = new Gtk.Label(im.Filename);
337 l.Text = im.Filename;
338 l.UseMarkup = false;
339 tabsView.AppendPage(dummy, l);
340 view.Present();
341 RememberRecentImage(file);
342 }
343
344 private async Task OpenPyramidRootsAsViewsAsync(string file, bool addToRegistry = false)
345 {
346 List<BioImage> roots = await CollectPyramidRootsInBackgroundAsync(file);
347 for (int series = 0; series < roots.Count; series++)
348 {
349 if (addToRegistry)
350 Images.AddImage(roots[series]);
351 ShowImageView(roots[series], file, series, roots.Count);
352 }
353 }
354
355 private async Task OpenPyramidRootsAsTabsAsync(string file)
356 {
357 List<BioImage> roots = await CollectPyramidRootsInBackgroundAsync(file);
358 for (int series = 0; series < roots.Count; series++)
359 {
360 BioImage im = roots[series];
361 if (roots.Count > 1)
362 im.Rename(GetSeriesDisplayName(file, series, roots.Count));
363 Images.AddImage(im);
364 AddTab(im, file);
365 }
366 }
367
368 private Task<List<BioImage>> CollectPyramidRootsInBackgroundAsync(string file)
369 {
370 return Task.Run(async () => await CollectPyramidRootsAsync(file));
371 }
372
373#pragma warning disable 649
374 [Builder.Object]
375 public MenuBar ImageJMenu;
376 [Builder.Object]
377 private MenuItem recentMenu;
378 [Builder.Object]
379 private MenuItem commandsMenu;
380 [Builder.Object]
381 private MenuItem runMenu;
382 [Builder.Object]
383 private MenuItem searchMenu;
384 [Builder.Object]
385 private MenuItem updateMenu;
386 [Builder.Object]
387 private MenuItem renameMenu;
388 [Builder.Object]
389 public MenuBar MainMenu;
390 [Builder.Object]
391 private MenuItem openURLMenu;
392 [Builder.Object]
393 private MenuItem openImagesMenu;
394 [Builder.Object]
395 private MenuItem openOMEImagesMenu;
396 [Builder.Object]
397 private MenuItem openOMESeriesMenu;
398 [Builder.Object]
399 private MenuItem openSeriesMenu;
400 [Builder.Object]
401 private MenuItem openZarr;
402 [Builder.Object]
403 private MenuItem openQuPathProject;
404 [Builder.Object]
405 private MenuItem addImagesToTabMenu;
406 [Builder.Object]
407 private MenuItem saveNumPyBut;
408 [Builder.Object]
409 private MenuItem addOMEImagesToTab;
410 [Builder.Object]
411 private MenuItem saveSelectedTiff;
412 [Builder.Object]
413 private MenuItem saveSelectedOME;
414 [Builder.Object]
415 private MenuItem saveTabOME;
416 [Builder.Object]
417 private MenuItem saveTabTiffMenu;
418 [Builder.Object]
419 private MenuItem saveSeriesMenu;
420 [Builder.Object]
421 private MenuItem savePyramidalMenu;
422 [Builder.Object]
423 private MenuItem saveZarr;
424 [Builder.Object]
425 private MenuItem saveQuPathProject;
426 [Builder.Object]
427 private MenuItem imagesToStack;
428 [Builder.Object]
429 private MenuItem uploadImageMenu;
430
431 [Builder.Object]
432 private Gtk.CheckMenuItem rgbMenu;
433 [Builder.Object]
434 private Gtk.CheckMenuItem filteredMenu;
435 [Builder.Object]
436 private Gtk.CheckMenuItem rawMenu;
437 [Builder.Object]
438 private Gtk.CheckMenuItem emissionMenu;
439
440 [Builder.Object]
441 private MenuItem toolsMenu;
442 [Builder.Object]
443 private MenuItem setToolMenu;
444 [Builder.Object]
445 private MenuItem runSAMMenu;
446 [Builder.Object]
447 private MenuItem plateToolMenu;
448
449 [Builder.Object]
450 private MenuItem roiManagerMenu;
451 [Builder.Object]
452 private MenuItem exportROIsToCSVMenu;
453 [Builder.Object]
454 private MenuItem importROIsFromCSVMenu;
455 [Builder.Object]
456 private MenuItem exportROIsOfFolderOfImagesMenu;
457 [Builder.Object]
458 private MenuItem importROIsFromImageJMenu;
459 [Builder.Object]
460 private MenuItem exportROIsFromImageJMenu;
461 [Builder.Object]
462 private MenuItem importROIQuPath;
463 [Builder.Object]
464 private MenuItem exportROIQuPath;
465 [Builder.Object]
466 private MenuItem importPointsNapari;
467 [Builder.Object]
468 private MenuItem importShapesNapari;
469 [Builder.Object]
470 private MenuItem exportROINapari;
471
472
473 [Builder.Object]
474 private MenuItem autoThresholdAllMenu;
475 [Builder.Object]
476 private MenuItem channelsToolMenu;
477 [Builder.Object]
478 private MenuItem switchRedBlueMenu;
479
480 [Builder.Object]
481 private MenuItem rotateFlipMenu;
482 [Builder.Object]
483 private MenuItem stackToolMenu;
484 [Builder.Object]
485 private MenuItem focusMenu;
486 [Builder.Object]
487 private MenuItem extractRegionMenu;
488
489 [Builder.Object]
490 private MenuItem to8BitMenu;
491 [Builder.Object]
492 private MenuItem to16BitMenu;
493 [Builder.Object]
494 private MenuItem to24BitMenu;
495 [Builder.Object]
496 private MenuItem to32BitMenu;
497 [Builder.Object]
498 private MenuItem to48BitMenu;
499 [Builder.Object]
500 private MenuItem toFloatMenu;
501 [Builder.Object]
502 private MenuItem toShortMenu;
503
504 [Builder.Object]
505 private MenuItem filtersMenu;
506
507 [Builder.Object]
508 private MenuItem functionsToolMenu;
509 [Builder.Object]
510 private MenuItem consoleMenu;
511 [Builder.Object]
512 private MenuItem scriptRunnerMenu;
513 [Builder.Object]
514 private MenuItem scriptRecorderMenu;
515
516 [Builder.Object]
517 private MenuItem aboutMenu;
518
519 [Builder.Object]
520 private Notebook tabsView;
521
522 [Builder.Object]
523 private Menu contextMenu;
524 [Builder.Object]
525 private MenuItem tabClose;
526 [Builder.Object]
527 private MenuItem tabDuplicate;
528 [Builder.Object]
529 private MenuItem runMicroSAMMenu;
530 [Builder.Object]
531 private MenuItem omeroMenu;
532 [Builder.Object]
533 private MenuItem AIMenu;
534
535#pragma warning restore 649
536
537 #endregion
538
539 #region Constructors / Destructors
540
545 public static TabsView Create()
546 {
547 Builder builder = new Builder(new FileStream(System.IO.Path.GetDirectoryName(Environment.ProcessPath) + "/" + "Glade/TabsView.glade", FileMode.Open));
548 return new TabsView(builder, builder.GetObject("TabsView").Handle);
549 }
550 Menu recent = new Menu();
551 /* Setting up the UI. */
552 protected TabsView(Builder builder, IntPtr handle) : base(handle)
553 {
554 _builder = builder;
555 App.tabsView = this;
556 builder.Autoconnect(this);
557 InitializeOpenRecentMenu();
558 filteredMenu.Active = true;
560 progress = Progress.Create(title, "", "");
562 filechooser =
563 new Gtk.FileChooserDialog("Choose file to open",
564 this,
565 FileChooserAction.Open,
566 "Cancel", ResponseType.Cancel,
567 "OK", ResponseType.Accept);
568 filechooser.SelectMultiple = true;
569 Menu m = new Menu();
570 foreach (RotateFlipType flip in (RotateFlipType[])Enum.GetValues(typeof(RotateFlipType)))
571 {
572 MenuItem mi = new MenuItem(flip.ToString());
573 mi.ButtonPressEvent += RotateFlip_ButtonPressEvent;
574 m.Append(mi);
575 }
576 rotateFlipMenu.Submenu = m;
577 rotateFlipMenu.ShowAll();
578 string a = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
579 Menu me = new Menu();
580 foreach (char c in a)
581 {
582 MenuItem mi = new MenuItem(c.ToString());
583 Menu men = new Menu();
584 foreach (Fiji.Macro.Command command in Fiji.Macro.Commands.Values)
585 {
586 if (command.Name.StartsWith(c))
587 {
588 MenuItem menuItem = new MenuItem(command.Name.ToString());
589 menuItem.ButtonPressEvent += CommandMenuItem_ButtonPressEvent;
590 men.Append(menuItem);
591 }
592 }
593 mi.Submenu = men;
594 me.Append(mi);
595 }
596 commandsMenu.Submenu = me;
597 commandsMenu.ShowAll();
598 recentMenu.Submenu = recent;
599
600 App.ApplyStyles(this);
601 Menu rm = new Menu();
602 foreach(Scripting.Script s in Scripting.scripts.Values)
603 {
604 MenuItem mi = new MenuItem(s.name);
605 mi.ButtonPressEvent += Mi_ButtonPressEvent;
606 rm.Append(mi);
607 }
608 //runMenu.ShowAll();
609 Plugins.Initialize();
610 ML.ML.Initialize();
611 AIMenu.ShowAll();
612 App.MoveToMain(this);
613 }
614 /*
615 public PointD GetTextSize(string s, string font, int size)
616 {
617 // Get the Pango context from the window
618 SKCanvas context = CreatePangoContext();
619
620 // Create a Pango layout for measuring text
621 Pango.Layout layout = new Pango.Layout(context);
622 layout.FontDescription = FontDescription.FromString(font + " " + size);
623 layout.SetText(s);
624 // Get pixel size
625 layout.GetPixelSize(out int width, out int height);
626 return new PointD(width, height);
627 }
628 */
629
634 public static bool SetImageJPath()
635 {
636 string st = BioLib.Settings.GetSettings("ImageJPath");
637 if (st != "")
638 {
639 Fiji.ImageJPath = BioLib.Settings.GetSettings("ImageJPath");
640 return true;
641 }
642 string title = "Select ImageJ Executable Location";
643 if (OperatingSystem.IsMacOS())
644 title = "Select ImageJ Executable Location (Fiji.app/Contents/MacOS/ImageJ-macosx)";
645 Gtk.FileChooserDialog filechooser =
646 new Gtk.FileChooserDialog(title, Scripting.window,
647 FileChooserAction.Open,
648 "Cancel", ResponseType.Cancel,
649 "Save", ResponseType.Accept);
650 filechooser.SetCurrentFolder(System.IO.Path.GetDirectoryName(Environment.ProcessPath));
651 if (filechooser.Run() != (int)ResponseType.Accept)
652 return false;
653 Fiji.ImageJPath = filechooser.Filename;
654 BioLib.Settings.AddSettings("ImageJPath", filechooser.Filename);
655 filechooser.Destroy();
656 BioLib.Settings.Save();
657 return true;
658 }
659 private void Mi_ButtonPressEvent(object o, ButtonPressEventArgs args)
660 {
661 MenuItem m = (MenuItem)o;
662 if (m.Label.EndsWith(".ijm") || m.Label.EndsWith(".txt") && !m.Label.EndsWith(".cs"))
663 {
664 string file = Fiji.ImageJPath + m.Label;
665 string ma = File.ReadAllText(file);
666 Fiji.RunOnImage(ImageView.SelectedImage,ma, BioConsole.headless, BioConsole.onTab, BioConsole.useBioformats, BioConsole.resultInNewTab);
667 }
668 else
669 Scripting.RunByName(m.Label);
670 bool con = false;
671 foreach (MenuItem item in recent.Children)
672 {
673 if (item.Label == m.Label)
674 con = true;
675 }
676 if (!con)
677 recent.Append(m);
678 recentMenu.ShowAll();
679 }
680
681 private async void CommandMenuItem_ButtonPressEvent(object o, ButtonPressEventArgs args)
682 {
683 if (ImageView.SelectedImage == null) return;
684 MenuItem m = (MenuItem)o;
685 BioImage sourceImage = ImageView.SelectedImage;
686 BioImage bm;
687 string command = "run(\"" + m.Label + "\");";
688 if (ImageView.SelectedImage.isPyramidal)
689 {
690 YesNoDialog pd = YesNoDialog.Create("Run on all pyramidal levels?");
691 if (pd.Run() == (int)ResponseType.Yes)
692 {
693 StartProgress("Run Command", "Running " + m.Label + " on all pyramid levels.");
694 try
695 {
696 bm = await ImageJ.RunOnAllPyramidLevels(ImageView.SelectedImage, command, BioConsole.headless, BioConsole.onTab, BioConsole.useBioformats, BioConsole.resultInNewTab);
697 }
698 finally
699 {
700 StopProgress();
701 }
702 AddToRecent(m.Label);
703 }
704 else
705 {
706 ComboPicker cpd = ComboPicker.Create("Select pyramidal level to run command on.", ImageView.SelectedImage.Resolutions.Select(l => l.ToString()).ToArray());
707 if(cpd.Run() == (int)ResponseType.Ok)
708 {
709 int level = cpd.SelectedIndex;
710 bool currentViewOnly = false;
711 if (IsRemoteZarrSource(ImageView.SelectedImage))
712 {
713 YesNoDialog speedDialog = YesNoDialog.Create("Use only the current visible region for speed?");
714 currentViewOnly = speedDialog.Run() == (int)ResponseType.Yes;
715 }
716
717 string scopeText = currentViewOnly
718 ? "the current visible region of pyramid level " + (level + 1).ToString()
719 : "pyramid level " + (level + 1).ToString();
720 StartProgress("Run Command", "Running " + m.Label + " on " + scopeText + ".");
721 try
722 {
723 bm = await ImageJ.RunOnPyramidalImage(ImageView.SelectedImage, command, level, BioConsole.headless, BioConsole.onTab, BioConsole.useBioformats, BioConsole.resultInNewTab, currentViewOnly);
724 }
725 finally
726 {
727 StopProgress();
728 }
729 AddToRecent(m.Label);
730 }
731 else
732 {
733 return;
734 }
735 }
736 Console.WriteLine($"[TabsView.CommandMenuItem] pyramidal result={(bm == null ? "null" : bm.ID)}");
737 PreserveSourceAnnotations(sourceImage, bm);
738 AddTab(bm);
739 return;
740 }
741 bm = await Fiji.RunOnImage(ImageView.SelectedImage, "run(\"" + m.Label + "\");", BioConsole.headless, BioConsole.onTab, BioConsole.useBioformats, BioConsole.resultInNewTab);
742 Console.WriteLine($"[TabsView.CommandMenuItem] stack result={(bm == null ? "null" : bm.ID)}");
743 AddToRecent(m.Label);
744 PreserveSourceAnnotations(sourceImage, bm);
745 AddTab(bm);
746 }
747
748 private static void PreserveSourceAnnotations(BioImage source, BioImage target)
749 {
750 if (source == null || target == null)
751 return;
752
753 if (target.Annotations == null)
754 target.Annotations = new List<ROI>();
755
756 foreach (ROI roi in source.Annotations ?? Enumerable.Empty<ROI>())
757 {
758 if (roi == null)
759 continue;
760
761 ROI copy = roi.Copy();
762 ScaleAnnotationToTargetVolume(copy, source, target);
763 target.Annotations.Add(copy);
764 }
765
766 if (App.viewer == null)
767 return;
768
769 foreach (ROI roi in App.viewer.GetZarrLabelOverlaysForImage(source))
770 {
771 if (roi == null)
772 continue;
773
774 if (!string.IsNullOrWhiteSpace(roi.roiID) &&
775 target.Annotations.Any(a => a != null && a.roiID == roi.roiID))
776 continue;
777
778 ROI copy = roi.Copy();
779 ScaleAnnotationToTargetVolume(copy, source, target);
780 target.Annotations.Add(copy);
781 }
782 }
783
784 private static void ScaleAnnotationToTargetVolume(ROI roi, BioImage source, BioImage target)
785 {
786 if (roi == null || source == null || target == null)
787 return;
788
789 double sourceW = GetStageWidth(source);
790 double sourceH = GetStageHeight(source);
791 double targetW = GetStageWidth(target);
792 double targetH = GetStageHeight(target);
793
794 if (sourceW <= 0 || sourceH <= 0 || targetW <= 0 || targetH <= 0)
795 return;
796
797 double scaleX = targetW / sourceW;
798 double scaleY = targetH / sourceH;
799 if (Math.Abs(scaleX - 1.0) < 0.000001 && Math.Abs(scaleY - 1.0) < 0.000001)
800 return;
801
802 double MapX(double x) => target.Volume.Location.X + ((x - source.Volume.Location.X) * scaleX);
803 double MapY(double y) => target.Volume.Location.Y + ((y - source.Volume.Location.Y) * scaleY);
804
805 roi.BoundingBox = new AForge.RectangleD(
806 MapX(roi.BoundingBox.X),
807 MapY(roi.BoundingBox.Y),
808 roi.BoundingBox.W * scaleX,
809 roi.BoundingBox.H * scaleY);
810
811 for (int i = 0; i < roi.Points.Count; i++)
812 roi.Points[i] = new AForge.PointD(MapX(roi.Points[i].X), MapY(roi.Points[i].Y));
813
814 if (roi.roiMask != null)
815 {
816 double maskStageX = roi.roiMask.X * roi.roiMask.PhysicalSizeX;
817 double maskStageY = roi.roiMask.Y * roi.roiMask.PhysicalSizeY;
818 roi.roiMask.PhysicalSizeX *= scaleX;
819 roi.roiMask.PhysicalSizeY *= scaleY;
820 roi.roiMask.X = MapX(maskStageX) / Math.Max(roi.roiMask.PhysicalSizeX, 1e-12);
821 roi.roiMask.Y = MapY(maskStageY) / Math.Max(roi.roiMask.PhysicalSizeY, 1e-12);
822 }
823 }
824
825 private static double GetStageWidth(BioImage image)
826 {
827 if (image == null)
828 return 0;
829
830 if (image.Resolutions != null && image.Resolutions.Count > 0 &&
831 image.Resolutions[0].SizeX > 0 && image.Resolutions[0].PhysicalSizeX > 0)
832 return image.Resolutions[0].SizeX * image.Resolutions[0].PhysicalSizeX;
833
834 return image.SizeX * (image.PhysicalSizeX > 0 ? image.PhysicalSizeX : 1.0);
835 }
836
837 private static double GetStageHeight(BioImage image)
838 {
839 if (image == null)
840 return 0;
841
842 if (image.Resolutions != null && image.Resolutions.Count > 0 &&
843 image.Resolutions[0].SizeY > 0 && image.Resolutions[0].PhysicalSizeY > 0)
844 return image.Resolutions[0].SizeY * image.Resolutions[0].PhysicalSizeY;
845
846 return image.SizeY * (image.PhysicalSizeY > 0 ? image.PhysicalSizeY : 1.0);
847 }
848
849 private static void NormalizeMasksForOmeExport(BioImage image)
850 {
851 if (image == null || image.Annotations == null || image.Annotations.Count == 0)
852 return;
853
854 foreach (ROI roi in image.Annotations)
855 {
856 if (roi?.roiMask == null)
857 continue;
858
859 if (roi.roiMask.PhysicalSizeX <= 0)
860 roi.roiMask.PhysicalSizeX = image.PhysicalSizeX > 0 ? image.PhysicalSizeX : 1.0;
861
862 if (roi.roiMask.PhysicalSizeY <= 0)
863 roi.roiMask.PhysicalSizeY = image.PhysicalSizeY > 0 ? image.PhysicalSizeY : 1.0;
864 }
865 }
866
867 private void InitializeOpenRecentMenu()
868 {
869 openRecentMenu = new MenuItem("Open Recent");
870 openRecentMenu.Submenu = recentImagesMenu;
871 clearRecentImagesMenu = new MenuItem("Clear Recent Files");
872 clearRecentImagesMenu.Activated += ClearRecentImagesMenu_Activated;
873 Menu fileMenu = (Menu)openImagesMenu.Parent;
874 int recentMenuPosition = Array.IndexOf(fileMenu.Children, openImagesMenu) + 1;
875 fileMenu.Insert(openRecentMenu, recentMenuPosition);
876 fileMenu.Insert(clearRecentImagesMenu, recentMenuPosition + 1);
877
878 try
879 {
880 if (File.Exists(RecentImagesPath))
881 {
882 var savedImages = JsonSerializer.Deserialize<List<string>>(File.ReadAllText(RecentImagesPath));
883 if (savedImages != null)
884 {
885 recentImages.AddRange(savedImages
886 .Where(source => !string.IsNullOrWhiteSpace(source))
887 .Distinct(StringComparer.Ordinal)
888 .Take(MaxRecentImages));
889 }
890 }
891 }
892 catch (Exception ex) when (ex is IOException || ex is UnauthorizedAccessException || ex is JsonException)
893 {
894 Console.WriteLine("Could not load recent images: " + ex.Message);
895 }
896
897 RefreshOpenRecentMenu();
898 }
899
900 private void ClearRecentImagesMenu_Activated(object sender, EventArgs args)
901 {
902 recentImages.Clear();
903 try
904 {
905 if (File.Exists(RecentImagesPath))
906 File.Delete(RecentImagesPath);
907 }
908 catch (Exception ex) when (ex is IOException || ex is UnauthorizedAccessException)
909 {
910 Console.WriteLine("Could not clear recent images: " + ex.Message);
911 }
912
913 RefreshOpenRecentMenu();
914 }
915
916 private void RememberRecentImage(string source)
917 {
918 if (string.IsNullOrWhiteSpace(source))
919 return;
920
921 bool remote = source.StartsWith("https://", StringComparison.OrdinalIgnoreCase) ||
922 source.StartsWith("http://", StringComparison.OrdinalIgnoreCase) ||
923 source.StartsWith("s3://", StringComparison.OrdinalIgnoreCase);
924 // Generated images should not become recent entries unless they have a saved source.
925 if (!remote && !File.Exists(source) && !Directory.Exists(source))
926 return;
927 if (!remote)
928 source = System.IO.Path.GetFullPath(source);
929
930 StringComparison comparison = !remote && OperatingSystem.IsWindows()
931 ? StringComparison.OrdinalIgnoreCase : StringComparison.Ordinal;
932 recentImages.RemoveAll(item => string.Equals(item, source, comparison));
933 recentImages.Insert(0, source);
934 if (recentImages.Count > MaxRecentImages)
935 recentImages.RemoveRange(MaxRecentImages, recentImages.Count - MaxRecentImages);
936
937 try
938 {
939 Directory.CreateDirectory(System.IO.Path.GetDirectoryName(RecentImagesPath));
940 File.WriteAllText(RecentImagesPath, JsonSerializer.Serialize(recentImages));
941 }
942 catch (Exception ex) when (ex is IOException || ex is UnauthorizedAccessException)
943 {
944 Console.WriteLine("Could not save recent images: " + ex.Message);
945 }
946
947 RefreshOpenRecentMenu();
948 }
949
950 private void RefreshOpenRecentMenu()
951 {
952 foreach (Widget item in recentImagesMenu.Children)
953 {
954 recentImagesMenu.Remove(item);
955 item.Destroy();
956 }
957
958 foreach (string source in recentImages)
959 {
960 var item = new MenuItem(source) { UseUnderline = false };
961 item.Activated += async (sender, args) => await OpenRecentImageAsync(source);
962 recentImagesMenu.Append(item);
963 }
964
965 openRecentMenu.Sensitive = recentImages.Count > 0;
966 clearRecentImagesMenu.Sensitive = recentImages.Count > 0;
967 openRecentMenu.ShowAll();
968 clearRecentImagesMenu.ShowAll();
969 }
970
971 private async Task OpenRecentImageAsync(string source)
972 {
973 StartProgress("Open Recent", "Opening");
974 try
975 {
976 await FlushGtkEventsAsync();
977 await OpenPyramidRootsAsTabsAsync(source);
978 }
979 catch (Exception ex)
980 {
981 using var dialog = new MessageDialog(this, DialogFlags.Modal,
982 MessageType.Error, ButtonsType.Ok, "{0}", ex.Message);
983 dialog.Title = "Open Recent Failed";
984 dialog.Run();
985 dialog.Destroy();
986 }
987 finally
988 {
989 StopProgress();
990 }
991 }
992
993 void AddToRecent(string name)
994 {
995 bool con = false;
996 foreach (MenuItem item in recent.Children)
997 {
998 if (item.Label == name)
999 {
1000 con = true;
1001 break;
1002 }
1003 }
1004 MenuItem mi = new MenuItem(name);
1005 if (!con)
1006 recent.Append(mi);
1007 mi.ButtonPressEvent += CommandMenuItem_ButtonPressEvent;
1008 recentMenu.ShowAll();
1009 }
1010
1018 private void RotateFlip_ButtonPressEvent(object o, ButtonPressEventArgs args)
1019 {
1020 if(ImageView.SelectedImage==null) return;
1021 MenuItem m = (MenuItem)o;
1022 RotateFlipType r = Enum.Parse<RotateFlipType>(m.Label);
1023 ImageView.SelectedImage.RotateFlip(r);
1024 App.viewer.UpdateImage();
1025 App.viewer.UpdateView();
1026 }
1027
1028 #endregion
1029
1030 #region Handlers
1031
1033 protected void SetupHandlers()
1034 {
1035 openURLMenu.ButtonPressEvent += OpenURLMenu_ButtonPressEvent;
1036 openImagesMenu.ButtonPressEvent += openImagesMenuClick;
1037 openOMEImagesMenu.ButtonPressEvent += openOMEImagesMenuClick;
1038 openOMESeriesMenu.ButtonPressEvent += openOMESeriesMenuClick;
1039 openSeriesMenu.ButtonPressEvent += openSeriesMenuClick;
1040 openQuPathProject.ButtonPressEvent += openQuPathProjectMenuClick;
1041 addImagesToTabMenu.ButtonPressEvent += addImagesToTabMenuClick;
1042 addOMEImagesToTab.ButtonPressEvent += addOMEImagesToTabClick;
1043 openZarr.ButtonPressEvent += OpenZarr_ButtonPressEvent;
1044 saveSelectedTiff.ButtonPressEvent += saveSelectedTiffClick;
1045 saveSelectedOME.ButtonPressEvent += saveSelectedOMEClick;
1046 saveNumPyBut.ButtonPressEvent += SaveNumPyBut_ButtonPressEvent;
1047
1048 saveTabOME.ButtonPressEvent += saveTabOMEClick;
1049 saveTabTiffMenu.ButtonPressEvent += saveTabTiffMenuClick;
1050 saveSeriesMenu.ButtonPressEvent += saveSeriesMenuClick;
1051 savePyramidalMenu.ButtonPressEvent += SavePyramidalMenu_ButtonPressEvent;
1052 saveZarr.ButtonPressEvent += SaveZarr_ButtonPressEvent;
1053 saveQuPathProject.ButtonPressEvent += saveQuPathProject_ButtonPressEvent;
1054 imagesToStack.ButtonPressEvent += imagesToStackClick;
1055 uploadImageMenu.ButtonPressEvent += UploadImageMenu_ButtonPressEvent;
1056
1057 rgbMenu.ButtonPressEvent += RgbMenu_ButtonPressEvent;
1058 filteredMenu.ButtonPressEvent += FilteredMenu_ButtonPressEvent;
1059 rawMenu.ButtonPressEvent += RawMenu_ButtonPressEvent;
1060 emissionMenu.ButtonPressEvent += EmissionMenu_ButtonPressEvent;
1061
1062 toolsMenu.ButtonPressEvent += toolsMenuClick;
1063 setToolMenu.ButtonPressEvent += setToolMenuClick;
1064 runSAMMenu.ButtonPressEvent += RunSAMMenu_ButtonPressEvent;
1065 runMicroSAMMenu.ButtonPressEvent += RunMicroSAMMenu_ButtonPressEvent;
1066 plateToolMenu.ButtonPressEvent += PlateToolMenu_ButtonPressEvent;
1067 extractRegionMenu.ButtonPressEvent += ExtractRegionMenu_ButtonPressEvent;
1068
1069 roiManagerMenu.ButtonPressEvent += roiManagerMenuClick;
1070 exportROIsToCSVMenu.ButtonPressEvent += exportROIsToCSVMenuClick;
1071 importROIsFromCSVMenu.ButtonPressEvent += importROIsFromCSVMenuClick;
1072 exportROIsOfFolderOfImagesMenu.ButtonPressEvent += exportROIsOfFolderOfImagesMenuClick;
1073 importROIsFromImageJMenu.ButtonPressEvent += ImportROIsFromImageJMenu_ButtonPressEvent;
1074 exportROIsFromImageJMenu.ButtonPressEvent += ExportROIsFromImageJMenu_ButtonPressEvent;
1075 importROIQuPath.ButtonPressEvent += ImportROIQuPath_ButtonPressEvent;
1076 exportROIQuPath.ButtonPressEvent += ExportROIQuPath_ButtonPressEvent;
1077 importPointsNapari.ButtonPressEvent += ImportPointsNapari_ButtonPressEvent;
1078 importShapesNapari.ButtonPressEvent += ImportShapesNapari_ButtonPressEvent;
1079 exportROINapari.ButtonPressEvent += ExportROINapari_ButtonPressEvent;
1080
1081 autoThresholdAllMenu.ButtonPressEvent += autoThresholdAllMenuClick;
1082 channelsToolMenu.ButtonPressEvent += channelsToolMenuClick;
1083 switchRedBlueMenu.ButtonPressEvent += switchRedBlueMenuClick;
1084
1085 stackToolMenu.ButtonPressEvent += stackToolMenuClick;
1086 focusMenu.ButtonPressEvent += FocusMenu_ButtonPressEvent;
1087
1088 to8BitMenu.ButtonPressEvent += to8BitMenuClick;
1089 to16BitMenu.ButtonPressEvent += to16BitMenuClick;
1090 to24BitMenu.ButtonPressEvent += to24BitMenuClick;
1091 to32BitMenu.ButtonPressEvent += to32BitMenuClick;
1092 to48BitMenu.ButtonPressEvent += to48BitMenuClick;
1093 toFloatMenu.ButtonPressEvent += ToFloatMenu_ButtonPressEvent;
1094 toShortMenu.ButtonPressEvent += ToShortMenu_ButtonPressEvent;
1095
1096 filtersMenu.ButtonPressEvent += filtersMenuClick;
1097 functionsToolMenu.ButtonPressEvent += functionsToolMenuClick;
1098 consoleMenu.ButtonPressEvent += consoleMenuClick;
1099 scriptRunnerMenu.ButtonPressEvent += scriptRunnerMenuClick;
1100 scriptRecorderMenu.ButtonPressEvent += ScriptRecorderMenu_ButtonPressEvent;
1101
1102 aboutMenu.ButtonPressEvent += AboutClick;
1103
1104 tabClose.ButtonPressEvent += TabClose_ButtonPressEvent;
1105 tabDuplicate.ButtonPressEvent += TabDuplicate_ButtonPressEvent;
1106 this.FocusInEvent += TabsView_FocusInEvent;
1107 rgbMenu.ButtonPressEvent += RgbMenu_ButtonPressEvent;
1108 filteredMenu.ButtonPressEvent += FilteredMenu_ButtonPressEvent;
1109 rawMenu.ButtonPressEvent += RawMenu_ButtonPressEvent;
1110 emissionMenu.ButtonPressEvent += EmissionMenu_ButtonPressEvent;
1111 tabsView.SwitchPage += TabsView_SwitchPage;
1112 tabsView.ButtonPressEvent += TabsView_ButtonPressEvent;
1113 this.WindowStateEvent += TabsView_WindowStateEvent;
1114 this.DeleteEvent += TabsView_DeleteEvent;
1115 omeroMenu.ButtonPressEvent += OmeroMenu_ButtonPressEvent;
1116
1117 searchMenu.ButtonPressEvent += SearchMenu_ButtonPressEvent;
1118 updateMenu.ButtonPressEvent += UpdateMenu_ButtonPressEvent;
1119 renameMenu.ButtonPressEvent += RenameMenu_ButtonPressEvent;
1120 AIMenu.ButtonPressEvent += AiMenu_ButtonPressEvent;
1121
1122 // Enable drag-and-drop for this window
1123 Gtk.Drag.DestSet(this, DestDefaults.All, new TargetEntry[]
1124 {
1125 new TargetEntry("text/uri-list", TargetFlags.OtherApp, 0)
1126 }, Gdk.DragAction.Copy);
1127 this.DragDataReceived += TabsView_DragDataReceived;
1128
1129 }
1130 private async void SaveZarr_ButtonPressEvent(object o, ButtonPressEventArgs args)
1131 {
1132 Gtk.FileChooserDialog filechooser =
1133 new Gtk.FileChooserDialog("Choose Zarr file to save.",
1134 this,
1135 FileChooserAction.Save,
1136 "Cancel", ResponseType.Cancel,
1137 "Open", ResponseType.Accept);
1138 if (filechooser.Run() != (int)ResponseType.Accept)
1139 return;
1140 filechooser.Hide();
1141 StartProgress("Saving Zarr", "Saving " + ImageView.SelectedImage.ID + " as Zarr file.");
1142 BioImage.Progress = 0;
1143 await Task.Yield();
1144 if (ImageView.SelectedImage == null)
1145 ImageView.SelectedImage = await BioImage.OpenFile(filechooser.Filename);
1146
1147 await Task.Run(() =>
1148 {
1149 Zarr.SaveZarr(ImageView.SelectedImage, filechooser.Filename);
1150 Zarr.SaveV2Compatibility(ImageView.SelectedImage, filechooser.Filename);
1151 MergeZarrLabelOverlaysIntoAnnotations(ImageView.SelectedImage);
1152 TrySaveZarrROIs(ImageView.SelectedImage, filechooser.Filename);
1153 TrySaveZarrLabelOverlays(ImageView.SelectedImage, filechooser.Filename);
1154 });
1155 StopProgress();
1156 }
1157
1158 private async void OpenZarr_ButtonPressEvent(object o, ButtonPressEventArgs args)
1159 {
1160 Gtk.FileChooserDialog filechooser =
1161 new Gtk.FileChooserDialog("Choose Zarr file to open.",
1162 this,
1163 FileChooserAction.SelectFolder,
1164 "Cancel", ResponseType.Cancel,
1165 "Open", ResponseType.Accept);
1166 if (filechooser.Run() != (int)ResponseType.Accept)
1167 return;
1168 filechooser.Hide();
1169 await Task.Yield();
1170 string[] sts = filechooser.Filenames;
1171 StartProgressHidden("Open Zarr", "Opening");
1172 try
1173 {
1174 await Task.Yield();
1175 foreach (string f in filechooser.Filenames)
1176 {
1177 await OpenPyramidRootsAsViewsAsync(f, true);
1178 }
1179 }
1180 finally
1181 {
1182 StopProgress();
1183 }
1184 }
1185
1186 private async void OpenURLMenu_ButtonPressEvent(object o, ButtonPressEventArgs args)
1187 {
1188 OpenUrlBox box = OpenUrlBox.Create();
1189 try
1190 {
1191 if (box.Run() == (int)ResponseType.Ok)
1192 {
1193 string url = box.GetUrl();
1194 StartProgress("Open URL", "Opening");
1195 try
1196 {
1197 await Task.Yield();
1198 await OpenPyramidRootsAsViewsAsync(url, true);
1199 }
1200 catch (Exception ex)
1201 {
1202 var dialog = new MessageDialog(
1203 this,
1204 DialogFlags.Modal,
1205 MessageType.Error,
1206 ButtonsType.Ok,
1207 ex.Message);
1208 dialog.Title = "Open URL Failed";
1209 dialog.Run();
1210 dialog.Destroy();
1211 }
1212 finally
1213 {
1214 StopProgress();
1215 }
1216 }
1217 }
1218 finally
1219 {
1220 box.Destroy();
1221 }
1222 }
1223
1224 private void ImportPointsNapari_ButtonPressEvent(object o, ButtonPressEventArgs args)
1225 {
1226 Gtk.FileChooserDialog filechooser =
1227 new Gtk.FileChooserDialog("Choose Napari Points files to import.",
1228 this,
1229 FileChooserAction.Open,
1230 "Cancel", ResponseType.Cancel,
1231 "Open", ResponseType.Accept);
1232 filechooser.SelectMultiple = true;
1233 if (filechooser.Run() != (int)ResponseType.Accept)
1234 return;
1235 string[] sts = filechooser.Filenames;
1236 Napari.Initialize(ImageView.SelectedImage.PhysicalSizeX, ImageView.SelectedImage.PhysicalSizeY,ImageView.SelectedImage.SizeT > 1);
1237 foreach (var f in filechooser.Filenames)
1238 {
1239 if (f.EndsWith(".CSV") || f.EndsWith(".csv"))
1240 ImageView.SelectedImage.Annotations.AddRange(Napari.ParsePointsFile(f));
1241 }
1242 filechooser.Destroy();
1243 BioLib.Recorder.Record("Napari.ParsePointsFile(" + StrArray(sts) + ");");
1244 }
1245
1246 private void ExportROINapari_ButtonPressEvent(object o, ButtonPressEventArgs args)
1247 {
1248 Gtk.FileChooserDialog filechooser =
1249 new Gtk.FileChooserDialog("Choose filename for Shape and Points CSV files to save to.",
1250 this,
1251 FileChooserAction.Save,
1252 "Cancel", ResponseType.Cancel,
1253 "Open", ResponseType.Accept);
1254 if (filechooser.Run() != (int)ResponseType.Accept)
1255 return;
1256 string file = filechooser.Filename;
1257 Napari.Initialize(ImageView.SelectedImage.PhysicalSizeX, ImageView.SelectedImage.PhysicalSizeY, ImageView.SelectedImage.SizeT > 1);
1258 Napari.WriteNapariFiles(file + "_points.csv", file + "_shapes.csv", ImageView.SelectedImage.Annotations);
1259 filechooser.Destroy();
1260 BioLib.Recorder.Record("Napari.WriteNapariFiles(" + Q(file + "_points.csv") + ", " + Q(file + "_shapes.csv") + ", ImageView.SelectedImage.Annotations);");
1261 }
1262
1263 private void ImportShapesNapari_ButtonPressEvent(object o, ButtonPressEventArgs args)
1264 {
1265 Gtk.FileChooserDialog filechooser =
1266 new Gtk.FileChooserDialog("Choose Napari Shape files to import.",
1267 this,
1268 FileChooserAction.Open,
1269 "Cancel", ResponseType.Cancel,
1270 "Open", ResponseType.Accept);
1271 filechooser.SelectMultiple = true;
1272 if (filechooser.Run() != (int)ResponseType.Accept)
1273 return;
1274 Napari.Initialize(ImageView.SelectedImage.PhysicalSizeX, ImageView.SelectedImage.PhysicalSizeY, ImageView.SelectedImage.SizeT > 1);
1275 foreach (var f in filechooser.Filenames)
1276 {
1277 if (f.EndsWith(".CSV") || f.EndsWith(".csv"))
1278 {
1279 List<ROI> rs = Napari.ParseShapesFile(f);
1280 ImageView.SelectedImage.Annotations.AddRange(rs);
1281 }
1282 }
1283 filechooser.Destroy();
1284 }
1285
1286 private void UploadImageMenu_ButtonPressEvent(object o, ButtonPressEventArgs args)
1287 {
1288 if (BioLib.OMERO.sc == null)
1289 OmeroMenu_ButtonPressEvent(o, args);
1290 BioImage b = ImageView.SelectedImage;
1291 var user = BioLib.OMERO.gateway.getLoggedInUser();
1292 long userId = user.getId();
1293 long imgid = 0;
1294 try
1295 {
1296 imgid = long.Parse(b.ID);
1297 }
1298 catch (Exception e)
1299 {
1300 Console.WriteLine(e.Message);
1301 }
1302 BioLib.Recorder.Record("OMERO.Upload(" + (b?.ID ?? string.Empty) + ", " + userId + ");");
1303 if(imgid == 0)
1304 {
1305 BioLib.OMERO.Upload(b, userId);
1306 }
1307 try
1308 {
1309 imgid = long.Parse(b.ID);
1310 }
1311 catch (Exception e)
1312 {
1313 Console.WriteLine(e.Message);
1314 }
1315 }
1316
1317 private void AiMenu_ButtonPressEvent(object o, ButtonPressEventArgs args)
1318 {
1319 AI.Create();
1320 BioLib.Recorder.Record("AI.Create();");
1321 }
1322
1323 private async void TabsView_DragDataReceived(object o, DragDataReceivedArgs args)
1324 {
1325 // Convert the received data to a string
1326 string receivedData = System.Text.Encoding.UTF8.GetString(args.SelectionData.Data);
1327
1328 // Parse the URIs (file paths)
1329 string[] uris = receivedData.Split(new[] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries);
1330 foreach (string uri in uris)
1331 {
1332 if (Uri.TryCreate(uri, UriKind.Absolute, out Uri fileUri) && fileUri.IsFile)
1333 {
1334 string filePath = fileUri.LocalPath;
1335 await OpenPyramidRootsAsTabsAsync(filePath);
1336 }
1337 }
1338
1339 args.RetVal = true; // Indicate the drop was handled
1340 }
1341
1342 private void OmeroMenu_ButtonPressEvent(object o, ButtonPressEventArgs args)
1343 {
1344 Login log = Login.Create();
1345 int status = log.Run();
1346 if (status == (int)ResponseType.Ok)
1347 {
1348 OMERO omr = OMERO.Create();
1349 omr.Show();
1350 }
1351 BioLib.Recorder.Record("Login.Create();");
1352 }
1353
1354 private void SaveNumPyBut_ButtonPressEvent(object o, ButtonPressEventArgs args)
1355 {
1356 Gtk.FileChooserDialog filechooser =
1357 new Gtk.FileChooserDialog("Choose filename for NumPy file to save to.",
1358 this,
1359 FileChooserAction.Save,
1360 "Cancel", ResponseType.Cancel,
1361 "Open", ResponseType.Accept);
1362 filechooser.SelectMultiple = false;
1363 if (filechooser.Run() != (int)ResponseType.Accept)
1364 return;
1365 NumPy.SaveNumPy(ImageView.SelectedImage,filechooser.Filename);
1366 filechooser.Hide();
1367 BioLib.Recorder.Record("NumPy.SaveNumPy(ImageView.SelectedImage, " + Q(filechooser.Filename) + ");");
1368 }
1369
1370 private void RunMicroSAMMenu_ButtonPressEvent(object o, ButtonPressEventArgs args)
1371 {
1372 SAMTool sam = SAMTool.Create(true);
1373 if (sam.init)
1374 sam.Show();
1375 App.samTool = sam;
1376 BioLib.Recorder.Record("SAMTool.Create(true);");
1377 }
1378
1379 public void RenameTab(string name, string newName)
1380 {
1381 int i = 0;
1382 foreach (Widget item in tabsView.Children)
1383 {
1384 // Get the tab widget for the page and set the new title
1385 Label l = (Label)tabsView.GetTabLabel(tabsView.GetNthPage(i));
1386 if (l.Text == name)
1387 {
1388 l.Text = newName;
1389 BioLib.Recorder.Record("TabsView.RenameTab(" + Q(name) + ", " + Q(newName) + ");");
1390 return;
1391 }
1392 }
1393 }
1394
1395 private void TabsView_FocusInEvent(object o, FocusInEventArgs args)
1396 {
1397 App.tabsView = this;
1398 }
1399
1400 private void RenameMenu_ButtonPressEvent(object o, ButtonPressEventArgs args)
1401 {
1402 TextInput ti = TextInput.Create();
1403 ti.Show();
1404 if ((int)ti.Run() == (int)ResponseType.Ok)
1405 {
1406 App.Rename(ti.Text);
1407 }
1408 BioLib.Recorder.Record("App.Rename(" + Q(ti.Text) + ");");
1409 }
1410
1411 private void TabsView_DeleteEvent(object o, DeleteEventArgs args)
1412 {
1413 foreach (var v in viewers)
1414 {
1415 foreach (var b in v.Images)
1416 {
1417 TrySaveZarrROIs(b);
1418 Images.RemoveImage(b);
1419 }
1420 v.Close();
1421 }
1422 }
1423
1424 private async void ExtractRegionMenu_ButtonPressEvent(object o, ButtonPressEventArgs args)
1425 {
1426 BioImage b = ImageView.SelectedImage.Copy();
1427 String name = System.IO.Path.GetFileNameWithoutExtension(ImageView.SelectedImage.Filename).Replace(".ome","");
1428 int c = Images.GetImageCountByName(name);
1429 b.ID = name + "-" + c + ".ome.tif";
1430 byte[] bms = null;
1431 if(b.OpenSlideBase != null)
1432 {
1433 bms = await b.OpenSlideBase.GetSliceAsync(new OpenSlideGTK.SliceInfo(b.PyramidalOrigin.X, b.PyramidalOrigin.Y, b.PyramidalSize.Width, b.PyramidalSize.Height, b.Resolution),b.Level,b.Coordinate);
1434 }
1435 else
1436 bms = await b.SlideBase.GetSlice(
1437 new SliceInfo(b.PyramidalOrigin.X, b.PyramidalOrigin.Y, b.PyramidalSize.Width, b.PyramidalSize.Height, b.Resolution, b.Coordinate),
1438 b.PyramidalOrigin, b.PyramidalSize);
1439 Point3D loc = b.Volume.Location;
1440 Point3D p = new Point3D(b.StageSizeX + b.PyramidalOrigin.X, b.StageSizeY + b.PyramidalOrigin.Y, b.StageSizeZ + b.SizeZ);
1441 Resolution res = new Resolution(b.PyramidalSize.Width, b.PyramidalSize.Height, b.Buffers[0].PixelFormat,b.PhysicalSizeX,b.PhysicalSizeY,b.PhysicalSizeZ,p.X,p.Y,p.Z);
1442 b.Resolutions.Clear();
1443 b.Resolutions.Add(res);
1444 b.Buffers.Clear();
1445 b.Buffers.Add(new Bitmap((int)b.PyramidalOrigin.X, (int)b.PyramidalOrigin.Y,PixelFormat.Format32bppArgb));
1446 Images.AddImage(b);
1447 AddTab(b);
1448 }
1449
1450 private void ToShortMenu_ButtonPressEvent(object o, ButtonPressEventArgs args)
1451 {
1452 ImageView.SelectedImage.ToShort();
1453 }
1454
1455 private void ToFloatMenu_ButtonPressEvent(object o, ButtonPressEventArgs args)
1456 {
1457 ImageView.SelectedImage.ToFloat();
1458 }
1459
1460 private void UpdateMenu_ButtonPressEvent(object o, ButtonPressEventArgs args)
1461 {
1462 App.updater.Show();
1463 }
1464
1465 private void PlateToolMenu_ButtonPressEvent(object o, ButtonPressEventArgs args)
1466 {
1467 PlateTool pl = PlateTool.Create();
1468 BioLib.Recorder.Record("App.viewer.Mode = ImageView.ViewMode.Emission;");
1469 }
1470
1471 private void ExportROIQuPath_ButtonPressEvent(object o, ButtonPressEventArgs args)
1472 {
1473 Gtk.FileChooserDialog filechooser =
1474 new Gtk.FileChooserDialog("Choose QuPath ROI(.geojson) file to save to.",
1475 this,
1476 FileChooserAction.Save,
1477 "Cancel", ResponseType.Cancel,
1478 "Open", ResponseType.Accept);
1479 filechooser.SelectMultiple = true;
1480 if (filechooser.Run() != (int)ResponseType.Accept)
1481 return;
1482 QuPath.SaveROI(filechooser.Filename, ImageView.SelectedImage);
1483 filechooser.Hide();
1484 BioLib.Recorder.Record("BioImage.ExportROIsCSV(" + Q(filechooser.Filename) + ", ImageView.SelectedImage.Annotations);");
1485 }
1486
1487 private void ImportROIQuPath_ButtonPressEvent(object o, ButtonPressEventArgs args)
1488 {
1489 Gtk.FileChooserDialog filechooser =
1490 new Gtk.FileChooserDialog("Choose QuPath ROI(.geojson) file to open",
1491 this,
1492 FileChooserAction.Open,
1493 "Cancel", ResponseType.Cancel,
1494 "Open", ResponseType.Accept);
1495 filechooser.SelectMultiple = true;
1496 if (filechooser.Run() != (int)ResponseType.Accept)
1497 return;
1498 foreach (string item in filechooser.Filenames)
1499 {
1500 ROI[] rois = QuPath.ReadROI(item,ImageView.SelectedImage);
1501 foreach (ROI r in rois)
1502 {
1503 ImageView.SelectedImage.Annotations.Add(r);
1504 }
1505 }
1506 filechooser.Hide();
1507 BioLib.Recorder.Record("QuPath.ReadROI(" + StrArray(filechooser.Filenames) + ", ImageView.SelectedImage);");
1508 }
1509
1510 private void SearchMenu_ButtonPressEvent(object o, ButtonPressEventArgs args)
1511 {
1512 Search search = Search.Create();
1513 search.Show();
1514 BioLib.Recorder.Record("App.viewer.Mode = ImageView.ViewMode.Raw;");
1515 }
1516
1517 private void TabsView_ButtonPressEvent(object o, ButtonPressEventArgs args)
1518 {
1519 contextMenu.Popup();
1520 }
1521
1522 private void TabDuplicate_ButtonPressEvent(object o, ButtonPressEventArgs args)
1523 {
1524 List<BioImage> bm = new List<BioImage>();
1525 foreach (BioImage item in App.viewer.Images)
1526 {
1527 bm.Add(item.Copy());
1528 }
1529 for (int i = 1; i < bm.Count; i++)
1530 {
1531 Images.AddImage(bm[i]);
1532 AddTab(bm[i]);
1533 }
1534 BioLib.Recorder.Record("App.viewer.Images.Count;");
1535 }
1536
1537 private void TabClose_ButtonPressEvent(object o, ButtonPressEventArgs args)
1538 {
1539 if (App.viewer == null) return;
1540 ImageView v = App.viewer;
1541 foreach (BioImage item in v.Images)
1542 {
1543 TrySaveZarrROIs(item);
1544 Images.RemoveImage(item);
1545 RemoveTab(item.Filename);
1546 item.Dispose();
1547 }
1548 RemoveViewer(v);
1549 v.Hide();
1550 v.Destroy();
1551 BioLib.Recorder.Record("App.viewer.Images.Count;");
1552 }
1553
1554 private void RunSAMMenu_ButtonPressEvent(object o, ButtonPressEventArgs args)
1555 {
1556 SAMTool sam = SAMTool.Create(false);
1557 if(sam.init)
1558 sam.Show();
1559 App.samTool = sam;
1560 }
1561
1562 private async void SavePyramidalMenu_ButtonPressEvent(object o, ButtonPressEventArgs args)
1563 {
1564 Gtk.FileChooserDialog filechooser =
1565 new Gtk.FileChooserDialog("Set Pyramidal filename to save",
1566 this,
1567 FileChooserAction.Save,
1568 "Cancel", ResponseType.Cancel,
1569 "Save", ResponseType.Accept);
1570 if (filechooser.Run() != (int)ResponseType.Accept)
1571 return;
1572 filechooser.Hide();
1573 ComboPicker picker = ComboPicker.Create(typeof(NetVips.Enums.ForeignTiffCompression));
1574 picker.Title = "Select Compression";
1575 picker.Show();
1576 if(picker.Run() != (int)ResponseType.Ok)
1577 return;
1578 NumberPicker numpicker = NumberPicker.Create(0,100,0);
1579 numpicker.Title = "Select Compression Level in %";
1580 if (numpicker.Run() != (int)ResponseType.Ok)
1581 return;
1582 await BioImage.SavePyramidalAsync(App.viewer.Images.ToArray(), filechooser.Filename, (NetVips.Enums.ForeignTiffCompression)picker.SelectedIndex, (int)numpicker.SelectedValue);
1583 BioLib.Recorder.Record("BioImage.SavePyramidalAsync(App.viewer.Images.ToArray(), " + Q(filechooser.Filename) + ", (NetVips.Enums.ForeignTiffCompression)" + picker.SelectedIndex + ", " + (int)numpicker.SelectedValue + ");");
1584 }
1585
1586 private void FocusMenu_ButtonPressEvent(object o, ButtonPressEventArgs args)
1587 {
1588 if (ImageView.SelectedImage == null) return;
1589 ZCT co = App.viewer.GetCoordinate();
1590 int f = BioImage.FindFocus(ImageView.SelectedImage, co.C, co.T);
1591 ZCT z = ImageView.SelectedImage.Buffers[f].Coordinate;
1592 App.viewer.SetCoordinate(z.Z, z.C, z.T);
1593 }
1594
1600 private void ScriptRecorderMenu_ButtonPressEvent(object o, ButtonPressEventArgs args)
1601 {
1602 App.recorder = Recorder.Create();
1603 App.recorder.Show();
1604 App.recorder.Present();
1605 BioLib.Recorder.Record("App.viewer.Mode = ImageView.ViewMode.Filtered;");
1606 }
1607
1614 private void ExportROIsFromImageJMenu_ButtonPressEvent(object o, ButtonPressEventArgs args)
1615 {
1616 Gtk.FileChooserDialog filechooser =
1617 new Gtk.FileChooserDialog("Set ROI filename to save",
1618 this,
1619 FileChooserAction.Save,
1620 "Cancel", ResponseType.Cancel,
1621 "Save", ResponseType.Accept);
1622 if (filechooser.Run() != (int)ResponseType.Accept)
1623 return;
1624 int i = 1;
1625 foreach (ROI item in ImageView.SelectedImage.Annotations)
1626 {
1627 string f = filechooser.Filename;
1628 if (!f.EndsWith(".roi"))
1629 f += i + ".roi";
1630 else
1631 {
1632 f = f.Replace(".roi","") + i + ".roi";
1633 }
1634 Fiji.RoiEncoder.save(ImageView.SelectedImage, item, f);
1635 i++;
1636 }
1637 filechooser.Hide();
1638 BioLib.Recorder.Record("Fiji.RoiEncoder.save(ImageView.SelectedImage, item, " + Q(filechooser.Filename) + ");");
1639 }
1640
1648 private void ImportROIsFromImageJMenu_ButtonPressEvent(object o, ButtonPressEventArgs args)
1649 {
1650 Gtk.FileChooserDialog filechooser =
1651 new Gtk.FileChooserDialog("Choose ROI file to open",
1652 this,
1653 FileChooserAction.Open,
1654 "Cancel", ResponseType.Cancel,
1655 "Open", ResponseType.Accept);
1656 filechooser.SelectMultiple = true;
1657 if (filechooser.Run() != (int)ResponseType.Accept)
1658 return;
1659 foreach (string item in filechooser.Filenames)
1660 {
1661 ROI roi = Fiji.RoiDecoder.open(item, ImageView.SelectedImage.PhysicalSizeX, ImageView.SelectedImage.PhysicalSizeY, ImageView.SelectedImage.Volume.Location.X, ImageView.SelectedImage.Volume.Location.Y);
1662 ImageView.SelectedImage.Annotations.Add(roi);
1663 }
1664 filechooser.Hide();
1665 BioLib.Recorder.Record("Fiji.RoiDecoder.open(" + StrArray(filechooser.Filenames) + ", ImageView.SelectedImage.PhysicalSizeX, ImageView.SelectedImage.PhysicalSizeY, ImageView.SelectedImage.Volume.Location.X, ImageView.SelectedImage.Volume.Location.Y);");
1666 }
1667
1674 private void TabsView_WindowStateEvent(object o, WindowStateEventArgs args)
1675 {
1676 if ((args.Event.ChangedMask & Gdk.WindowState.Iconified) != 0)
1677 {
1678 foreach (ImageView v in viewers)
1679 {
1680 v.Hide();
1681 }
1682 }
1683
1684 else if ((args.Event.ChangedMask & Gdk.WindowState.Maximized) == 0 && (args.Event.ChangedMask & Gdk.WindowState.Iconified) == 0)
1685 {
1686 foreach (ImageView v in viewers)
1687 {
1688 // Only re-present viewers that still have images.
1689 // A viewer with no images has been closed/destroyed and
1690 // calling Present() on it would resurrect an empty window.
1691 if(!v.Visible && v.Images.Count > 0)
1692 v.Present();
1693 }
1694 }
1695
1696 }
1697
1702 private void TabsView_SwitchPage(object o, SwitchPageArgs args)
1703 {
1704 if(viewers.Count == 0) return;
1705 viewers[(int)args.PageNum].Present();
1706 App.viewer = viewers[(int)args.PageNum];
1707 ImageView.SelectedImage = App.viewer.Images[0];
1708 App.roiManager?.UpdateAnnotationList();
1709 }
1710
1715 private void EmissionMenu_ButtonPressEvent(object o, ButtonPressEventArgs args)
1716 {
1717 if (emissionMenu.Active)
1718 emissionMenu.Active = false;
1719 else
1720 emissionMenu.Active = true;
1721 filteredMenu.Active = false;
1722 rawMenu.Active = false;
1723 rgbMenu.Active = false;
1724 App.viewer.Mode = ImageView.ViewMode.Emission;
1725 BioLib.Recorder.Record("App.viewer.Mode = ImageView.ViewMode.RGBImage;");
1726 }
1732 private void RawMenu_ButtonPressEvent(object o, ButtonPressEventArgs args)
1733 {
1734 if (rawMenu.Active)
1735 rawMenu.Active = false;
1736 else
1737 rawMenu.Active = true;
1738 filteredMenu.Active = false;
1739 rgbMenu.Active = false;
1740 emissionMenu.Active = false;
1741 App.viewer.Mode = ImageView.ViewMode.Raw;
1742 BioLib.Recorder.Record("PlateTool.Create();");
1743 }
1750 private void FilteredMenu_ButtonPressEvent(object o, ButtonPressEventArgs args)
1751 {
1752 if (filteredMenu.Active)
1753 filteredMenu.Active = false;
1754 else
1755 filteredMenu.Active = true;
1756 rgbMenu.Active = false;
1757 rawMenu.Active = false;
1758 emissionMenu.Active = false;
1759 App.viewer.Mode = ImageView.ViewMode.Filtered;
1760 BioLib.Recorder.Record("App.viewer.Mode = ImageView.ViewMode.Filtered;");
1761 }
1767 private void RgbMenu_ButtonPressEvent(object o, ButtonPressEventArgs args)
1768 {
1769 if (rgbMenu.Active)
1770 rgbMenu.Active = false;
1771 else
1772 rgbMenu.Active = true;
1773 filteredMenu.Active = false;
1774 rawMenu.Active = false;
1775 emissionMenu.Active = false;
1776 App.viewer.Mode = ImageView.ViewMode.RGBImage;
1777 BioLib.Recorder.Record("App.viewer.Mode = ImageView.ViewMode.RGBImage;");
1778 }
1783 public void SetTab(int i)
1784 {
1785 tabsView.Page = i;
1786 BioLib.Recorder.Record("tabsView.Page = " + i + ";");
1787 }
1791 public void AddTab(BioImage im, string? sourcePath = null)
1792 {
1793
1794 try
1795 {
1796 if (im == null)
1797 return;
1798 for (int vi = 0; vi < viewers.Count; vi++)
1799 {
1800 for (int i = 0; i < viewers[vi].Images.Count; i++)
1801 {
1802 if (viewers[vi].Images[i].Filename == im.Filename)
1803 {
1804 RememberRecentImage(sourcePath ?? im.SourceFile);
1805 return;
1806 }
1807 }
1808 }
1809 TryLoadZarrROIs(im);
1810 ImageView v = ImageView.Create(im);
1811 v.RefreshZarrLabelOverlays(sourcePath ?? im.file);
1812 viewers.Add(v);
1813 Label dummy = new Gtk.Label(System.IO.Path.GetDirectoryName(im.file.Replace("\\", "/")) + "/" + im.Filename);
1814 dummy.Text = im.file.Replace("\\", "/") + "/" + im.Filename;
1815 dummy.Visible = false;
1816 Label l = new Gtk.Label(System.IO.Path.GetFileName(im.Filename));
1817 l.Text = System.IO.Path.GetFileName(im.Filename);
1818 tabsView.AppendPage(dummy, l);
1819 Console.WriteLine("New tab added for " + im.Filename);
1820 App.nodeView.UpdateItems();
1821 tabsView.ShowAll();
1822 v.ShowAll();
1823 RememberRecentImage(sourcePath ?? im.SourceFile);
1824 BioLib.Recorder.Record("AddTab(" + (im?.file ?? string.Empty) + ", " + Q(sourcePath ?? string.Empty) + ");");
1825 GLib.Timeout.Add(16, () =>
1826 {
1827 if (v.View == null || !v.View.IsRealized || v.View.AllocatedWidth <= 1 || v.View.AllocatedHeight <= 1)
1828 return true;
1829
1830 App.viewer = v;
1831 v.GoToImage();
1832 v.UpdateView();
1833 if (!ImageView.MacOS)
1834 v.glArea?.RequestRedraw();
1835 else
1836 v.View.QueueDraw();
1837 return false;
1838 });
1839 }
1840 catch (Exception e)
1841 {
1842 BioLib.Recorder.Record("AddTab(" + (im?.file ?? string.Empty) + ", " + Q(sourcePath ?? string.Empty) + ");");
1843 // Execute on UI thread
1844 Gtk.Application.Invoke((sender, args) =>
1845 {
1846 if (im == null)
1847 return;
1848 for (int vi = 0; vi < viewers.Count; vi++)
1849 {
1850 for (int i = 0; i < viewers[vi].Images.Count; i++)
1851 {
1852 if (viewers[vi].Images[i].Filename == im.Filename)
1853 {
1854 RememberRecentImage(sourcePath ?? im.SourceFile);
1855 return;
1856 }
1857 }
1858 }
1859 ImageView v = ImageView.Create(im);
1860 viewers.Add(v);
1861 v.RefreshZarrLabelOverlays(sourcePath ?? im.file);
1862 Label dummy = new Gtk.Label(System.IO.Path.GetDirectoryName(im.file.Replace("\\", "/")) + "/" + im.Filename);
1863 dummy.Text = im.file.Replace("\\", "/") + "/" + im.Filename;
1864 dummy.Visible = false;
1865 Label l = new Gtk.Label(System.IO.Path.GetFileName(im.Filename));
1866 l.Text = System.IO.Path.GetFileName(im.Filename);
1867 tabsView.AppendPage(dummy, l);
1868 Console.WriteLine("New tab added for " + im.Filename);
1869 App.nodeView.UpdateItems();
1870 tabsView.ShowAll();
1871 v.ShowAll();
1872 RememberRecentImage(sourcePath ?? im.SourceFile);
1873 GLib.Timeout.Add(16, () =>
1874 {
1875 if (v.View == null || !v.View.IsRealized || v.View.AllocatedWidth <= 1 || v.View.AllocatedHeight <= 1)
1876 return true;
1877
1878 App.viewer = v;
1879 v.GoToImage();
1880 v.UpdateView();
1881 if (!ImageView.MacOS)
1882 v.glArea?.RequestRedraw();
1883 else
1884 v.View.QueueDraw();
1885 return false;
1886 });
1887 });
1888 }
1889 }
1890
1891 private static void TryLoadZarrROIs(BioImage image)
1892 {
1893 try
1894 {
1895 string zarrDir = image.file;
1896 if (string.IsNullOrWhiteSpace(zarrDir))
1897 return;
1898
1899 if (!Directory.Exists(zarrDir) && !zarrDir.EndsWith(".zarr", StringComparison.OrdinalIgnoreCase))
1900 return;
1901
1902 var rois = BioLib.Zarr.LoadROIs(zarrDir);
1903 if (rois.Count > 0)
1904 {
1905 image.Annotations.AddRange(rois.Where(r =>
1906 r != null &&
1907 (string.IsNullOrWhiteSpace(r.roiID) ||
1908 !r.roiID.StartsWith("zarr-label:", StringComparison.OrdinalIgnoreCase))));
1909 }
1910 }
1911 catch (Exception ex)
1912 {
1913 Console.WriteLine($"Load Zarr ROIs failed: {ex.Message}");
1914 }
1915 }
1916
1917 private static void TrySaveZarrROIs(BioImage image, string? overrideDir = null)
1918 {
1919 try
1920 {
1921 if (image == null)
1922 return;
1923
1924 string zarrDir = overrideDir ?? image.file;
1925 if (string.IsNullOrWhiteSpace(zarrDir))
1926 return;
1927
1928 if (!Directory.Exists(zarrDir) && !zarrDir.EndsWith(".zarr", StringComparison.OrdinalIgnoreCase))
1929 return;
1930
1931 BioLib.Zarr.SaveROIs(image, zarrDir);
1932 }
1933 catch (Exception ex)
1934 {
1935 Console.WriteLine($"Save Zarr ROIs failed: {ex.Message}");
1936 }
1937 }
1938
1939 private static void MergeZarrLabelOverlaysIntoAnnotations(BioImage image)
1940 {
1941 if (image == null || App.viewer == null)
1942 return;
1943
1944 var overlays = App.viewer.GetZarrLabelOverlaysForImage(image);
1945 if (overlays == null || overlays.Count == 0)
1946 return;
1947
1948 foreach (var overlay in overlays)
1949 {
1950 if (overlay == null)
1951 continue;
1952
1953 if (string.IsNullOrWhiteSpace(overlay.roiID) ||
1954 !overlay.roiID.StartsWith("zarr-label:", StringComparison.OrdinalIgnoreCase))
1955 continue;
1956
1957 bool exists = image.Annotations.Any(a => a != null && a.roiID == overlay.roiID);
1958 if (exists)
1959 continue;
1960
1961 image.Annotations.Add(overlay);
1962 }
1963 }
1964
1965 private static void TrySaveZarrLabelOverlays(BioImage image, string? overrideDir = null)
1966 {
1967 try
1968 {
1969 if (image == null)
1970 return;
1971
1972 string zarrDir = overrideDir ?? image.file;
1973 if (string.IsNullOrWhiteSpace(zarrDir))
1974 return;
1975
1976 if (!Directory.Exists(zarrDir) && !zarrDir.EndsWith(".zarr", StringComparison.OrdinalIgnoreCase))
1977 return;
1978
1979 if (image.Annotations == null || image.Annotations.Count == 0)
1980 return;
1981
1982 BioLib.Zarr.SaveLabelOverlays(image, image.Annotations, zarrDir);
1983 }
1984 catch (Exception ex)
1985 {
1986 Console.WriteLine($"Save Zarr label overlays failed: {ex.Message}");
1987 }
1988 }
1989
1990 Gtk.FileChooserDialog filechooser;
1998 protected async void openImagesMenuClick(object sender, EventArgs a)
1999 {
2000 filechooser = new FileChooserDialog("Choose file to open", this, FileChooserAction.Open, "Cancel", ResponseType.Cancel, "OK", ResponseType.Accept);
2001 filechooser.SelectMultiple = true;
2002 if (filechooser.Run() != (int)ResponseType.Accept)
2003 return;
2004 string[] sts = filechooser.Filenames;
2005 filechooser.Hide();
2006 foreach (string item in sts)
2007 {
2008 StartProgress("Open File", "Opening");
2009 try
2010 {
2011 await FlushGtkEventsAsync();
2012 await OpenPyramidRootsAsTabsAsync(item);
2013 }
2014 finally
2015 {
2016 StopProgress();
2017 }
2018 }
2019 BioLib.Recorder.Record("BioImage.OpenAsync(" + StrArray(sts) + ", false, true, true);");
2020 }
2028 protected async void openOMEImagesMenuClick(object sender, EventArgs a)
2029 {
2030 filechooser = new FileChooserDialog("Choose OME file to open", this, FileChooserAction.Open, "Cancel", ResponseType.Cancel, "OK", ResponseType.Accept);
2031 filechooser.SelectMultiple = true;
2032 if (filechooser.Run() != (int)ResponseType.Accept)
2033 return;
2034 filechooser.Hide();
2035 string[] sts = filechooser.Filenames;
2036 foreach (string item in filechooser.Filenames)
2037 {
2038 StartProgress("Open OME", "Opening");
2039 await OpenPyramidRootsAsTabsAsync(item);
2040 StopProgress();
2041 }
2042 BioLib.Recorder.Record("BioImage.OpenAsync(" + StrArray(sts) + ", true, true, true);");
2043 }
2051 protected async void openOMESeriesMenuClick(object sender, EventArgs a)
2052 {
2053 filechooser = new FileChooserDialog("Choose OME Series file to open.", this, FileChooserAction.Open, "Cancel", ResponseType.Cancel, "OK", ResponseType.Accept);
2054 filechooser.SelectMultiple = true;
2055 if (filechooser.Run() != (int)ResponseType.Accept)
2056 return;
2057 filechooser.Hide();
2058 string[] sts = filechooser.Filenames;
2059 foreach (string item in filechooser.Filenames)
2060 {
2061 StartProgress("Open OME", "Opening");
2062 await OpenPyramidRootsAsTabsAsync(item);
2063 }
2064 tabsView.ShowAll();
2065 BioLib.Recorder.Record("BioImage.OpenAsync(" + StrArray(sts) + ", true, true, true);");
2066
2067 }
2075 protected async void openSeriesMenuClick(object sender, EventArgs a)
2076 {
2077 filechooser = new FileChooserDialog("Choose series file to open", this, FileChooserAction.Open, "Cancel", ResponseType.Cancel, "OK", ResponseType.Accept);
2078 filechooser.SelectMultiple = true;
2079 if (filechooser.Run() != (int)ResponseType.Accept)
2080 return;
2081 filechooser.Hide();
2082 string[] sts = filechooser.Filenames;
2083 foreach (string item in filechooser.Filenames)
2084 {
2085 StartProgress("Open OME Series", "Opening");
2086 await OpenPyramidRootsAsTabsAsync(item);
2087 StopProgress();
2088 }
2089 tabsView.Show();
2090 BioLib.Recorder.Record("BioImage.ImportROIsCSV(" + StrArray(sts) + ");");
2091 }
2092 private async void openQuPathProjectMenuClick(object sender, EventArgs a)
2093 {
2094 filechooser = new FileChooserDialog("Choose QuPath project file to open", this, FileChooserAction.Open, "Cancel", ResponseType.Cancel, "OK", ResponseType.Accept);
2095 filechooser.SelectMultiple = true;
2096 if (filechooser.Run() != (int)ResponseType.Accept)
2097 return;
2098 filechooser.Hide();
2099 QuPath.Project pr = QuPath.OpenProject(filechooser.Filename);
2100 foreach (var item in pr.Images)
2101 {
2102 string file = item.ServerBuilder.Uri.Replace("file:/", "");
2103 BioImage bm = await BioImage.OpenFile(file);
2104 AddTab(bm, file);
2105 string[] rs = Directory.GetFiles(System.IO.Path.GetDirectoryName(file));
2106 foreach (string s in rs)
2107 {
2108 if (s.EndsWith(".geojson") && s.Contains(System.IO.Path.GetFileNameWithoutExtension(file)))
2109 {
2110 ROI[] rois = QuPath.ReadROI(s, bm);
2111 bm.Annotations.AddRange(rois);
2112 }
2113 }
2114 }
2115 BioLib.Recorder.Record("QuPath.OpenProject(" + Q(filechooser.Filename) + ");");
2116 }
2117 private void saveQuPathProject_ButtonPressEvent(object o, ButtonPressEventArgs args)
2118 {
2119 Gtk.FileChooserDialog filechooser =
2120 new Gtk.FileChooserDialog("Set QuPath project file to save.",
2121 this,
2122 FileChooserAction.Save,
2123 "Cancel", ResponseType.Cancel,
2124 "Save", ResponseType.Accept);
2125 if (filechooser.Run() != (int)ResponseType.Accept)
2126 return;
2127 List<BioImage[]> bms = new List<BioImage[]>();
2128 foreach (var item in viewers)
2129 {
2130 List<BioImage> bs = new List<BioImage>();
2131 foreach (var b in item.Images)
2132 {
2133 bs.Add(b);
2134 }
2135 bms.Add(bs.ToArray());
2136 }
2137 QuPath.Project.SaveProject(filechooser.Filename, bms);
2138 BioLib.Recorder.Record("QuPath.Project.SaveProject(" + Q(filechooser.Filename) + ", bms);");
2139 }
2147 protected async void addImagesToTabMenuClick(object sender, EventArgs a)
2148 {
2149 filechooser = new FileChooserDialog("Choose file to add to current tab.", this, FileChooserAction.Open, "Cancel", ResponseType.Cancel, "OK", ResponseType.Accept);
2150 filechooser.SelectMultiple = true;
2151 if (filechooser.Run() != (int)ResponseType.Accept)
2152 return;
2153 filechooser.Hide();
2154 string[] sts = filechooser.Filenames;
2155 foreach (string item in filechooser.Filenames)
2156 {
2157 StartProgress("Add Image to Tab", "Opening");
2158 await BioImage.OpenAsync(item, false, false, true, 0);
2159 BioImage im = Images.GetImage(item);
2160 AddTab(im, item);
2161 StopProgress();
2162 }
2163 this.ShowAll();
2164 BioLib.Recorder.Record("BioImage.OpenAsync(" + StrArray(sts) + ", false, false, true);");
2165 }
2173 protected async void addOMEImagesToTabClick(object sender, EventArgs a)
2174 {
2175 filechooser = new FileChooserDialog("Choose OME file to add to current tab.", this, FileChooserAction.Open, "Cancel", ResponseType.Cancel, "OK", ResponseType.Accept);
2176 filechooser.SelectMultiple = true;
2177 if (filechooser.Run() != (int)ResponseType.Accept)
2178 return;
2179 filechooser.Hide();
2180 string[] sts = filechooser.Filenames;
2181 foreach (string item in filechooser.Filenames)
2182 {
2183 StartProgress("Add Image to Tab", "Opening");
2184 await BioImage.OpenAsync(item, true, false, true, 0);
2185 BioImage im = Images.GetImage(item);
2186 AddTab(im, item);
2187 StopProgress();
2188 }
2189 this.Show();
2190 BioLib.Recorder.Record("BioImage.OpenAsync(" + StrArray(sts) + ", true, false, true);");
2191 }
2192 private static bool done = false;
2193 private static bool progressShown = false;
2194 private static int progressShowThreshold = 0;
2195 private static int progressDisplayOffset = 0;
2196 private static int progressRunId = 0;
2197 private static readonly Stopwatch progressStopwatch = new Stopwatch();
2198 private static Progress progress;
2199 private static string title;
2200 private static string FormatProgressTiming(double progressPercent)
2201 {
2202 TimeSpan elapsed = progressStopwatch.Elapsed;
2203 string elapsedText = FormatDuration(elapsed);
2204 string etaText = "--:--";
2205
2206 if (progressPercent > 0.01 && progressPercent < 100)
2207 {
2208 double remainingSeconds = elapsed.TotalSeconds * ((100.0 - progressPercent) / progressPercent);
2209 if (double.IsFinite(remainingSeconds) && remainingSeconds >= 0)
2210 etaText = FormatDuration(TimeSpan.FromSeconds(remainingSeconds));
2211 }
2212 else if (progressPercent >= 100)
2213 {
2214 etaText = "00:00";
2215 }
2216
2217 return $"Elapsed: {elapsedText} ETA: {etaText}";
2218 }
2219 private static string FormatDuration(TimeSpan span)
2220 {
2221 if (span.TotalHours >= 1)
2222 return span.ToString(@"hh\:mm\:ss");
2223 return span.ToString(@"mm\:ss");
2224 }
2225 private static void ProgressUpdate(int runId)
2226 {
2227 do
2228 {
2229 float displayProgress = BioImage.Progress;
2230 if (progressDisplayOffset > 0)
2231 {
2232 float normalized = (BioImage.Progress - progressDisplayOffset) * 100f / Math.Max(1, 100 - progressDisplayOffset);
2233 displayProgress = Math.Max(0, Math.Min(100, normalized));
2234 }
2235 progress.Title = title;
2236 if (!string.IsNullOrEmpty(BioImage.Status))
2237 progress.Status = BioImage.Status;
2238 if (!progressShown && BioImage.Progress >= progressShowThreshold)
2239 {
2240 progressShown = true;
2241 Gtk.Application.Invoke(delegate
2242 {
2243 if (done || runId != progressRunId)
2244 return;
2245 progress.ShowAll();
2246 });
2247 }
2248 progress.ProgressPercentValue = displayProgress;
2249 progress.Text = FormatProgressTiming(displayProgress);
2250 Thread.Sleep(250);
2251 } while (!done && runId == progressRunId);
2252 }
2253 private static void StartProgress(string titl, string status)
2254 {
2255 StartProgressInternal(titl, status, true, 0, 0);
2256 }
2257 private static void StartProgressHidden(string titl, string status)
2258 {
2259 StartProgressInternal(titl, status, false, 85, 85);
2260 }
2261 private static void StartProgressInternal(string titl, string status, bool showImmediately, int showThreshold, int displayOffset)
2262 {
2263 done = false;
2264 progressRunId++;
2265 int runId = progressRunId;
2266 progressShown = showImmediately;
2267 progressShowThreshold = showThreshold;
2268 progressDisplayOffset = displayOffset;
2269 title = titl;
2270 BioImage.Progress = 0;
2271 progress.Status = status;
2272 progress.Title = title;
2273 if (BioImage.Status == "" || BioImage.Status == null)
2274 progress.Status = status;
2275 else
2276 progress.Status = BioImage.Status;
2277 progress.ProgressPercentValue = BioImage.Progress;
2278 progressStopwatch.Restart();
2279 progress.Text = FormatProgressTiming(BioImage.Progress);
2280 if (showImmediately)
2281 progress.ShowAll();
2282 else
2283 progress.Hide();
2284 Thread th = new Thread(() => ProgressUpdate(runId));
2285 th.IsBackground = true;
2286 th.Start();
2287 }
2288
2289 private static async Task FlushGtkEventsAsync()
2290 {
2291 await Task.Yield();
2292 while (Gtk.Application.EventsPending())
2293 Gtk.Application.RunIteration(false);
2294 await Task.Delay(50);
2295 while (Gtk.Application.EventsPending())
2296 Gtk.Application.RunIteration(false);
2297 }
2298
2299 private static void StopProgress()
2300 {
2301 done = true;
2302 progressStopwatch.Stop();
2303 int runId = ++progressRunId;
2304 Gtk.Application.Invoke(delegate
2305 {
2306 if (runId != progressRunId)
2307 return;
2308 progress.Hide();
2309 });
2310 }
2311
2312 private static bool IsZarrSource(BioImage image)
2313 {
2314 if (image == null)
2315 return false;
2316
2317 string source = image.file;
2318 if (string.IsNullOrWhiteSpace(source))
2319 source = image.Filename;
2320
2321 return source.Contains(".zarr", StringComparison.OrdinalIgnoreCase) ||
2322 source.StartsWith("https://", StringComparison.OrdinalIgnoreCase) ||
2323 source.StartsWith("s3://", StringComparison.OrdinalIgnoreCase);
2324 }
2325
2326 private static async Task<(BioImage Image, bool IsTemporary)> GetOmeExportImageAsync(BioImage image)
2327 {
2328 if (image == null)
2329 return (null, false);
2330
2331 if (!IsZarrSource(image))
2332 return (image, false);
2333
2334 string source = image.file;
2335 if (string.IsNullOrWhiteSpace(source))
2336 source = image.Filename;
2337
2338 BioImage exportImage = await BioImage.OpenZarrAsync(source, 0, 0, image.ID).ConfigureAwait(false);
2339 return (exportImage, true);
2340 }
2341
2342 private static void ReleaseTemporaryExportImage(BioImage image)
2343 {
2344 if (image == null)
2345 return;
2346
2347 Images.images.Remove(image);
2348 image.Dispose();
2349 }
2350
2358 protected async void saveSelectedTiffClick(object sender, EventArgs a)
2359 {
2360 filechooser = new FileChooserDialog("Save Selected Image To TIFF", this, FileChooserAction.Save, "Cancel", ResponseType.Cancel, "OK", ResponseType.Accept);
2361 if (filechooser.Run() != (int)ResponseType.Accept)
2362 return;
2363 filechooser.Hide();
2364 StartProgress("Save Selected Tiff","Saving");
2365 await BioImage.SaveAsync(filechooser.Filename, ImageView.SelectedImage.Filename, 0, false);
2366 StopProgress();
2367 }
2374 protected async void saveSelectedOMEClick(object sender, EventArgs a)
2375 {
2376 filechooser = new FileChooserDialog("Save Selected OME Image to File", this, FileChooserAction.Save, "Cancel", ResponseType.Cancel, "OK", ResponseType.Accept);
2377 if (filechooser.Run() != (int)ResponseType.Accept)
2378 return;
2379 filechooser.Hide();
2380 StartProgress("Save Selected OME", "Saving");
2381 var (exportImage, isTemporary) = await GetOmeExportImageAsync(ImageView.SelectedImage);
2382 try
2383 {
2384 PreserveSourceAnnotations(ImageView.SelectedImage, exportImage);
2385 NormalizeMasksForOmeExport(exportImage);
2386 await Task.Run(() => BioImage.SaveOMESeries(new[] { exportImage }, filechooser.Filename, BioImage.Planes));
2387 }
2388 finally
2389 {
2390 if (isTemporary)
2391 ReleaseTemporaryExportImage(exportImage);
2392 }
2393 StopProgress();
2394 }
2401 protected async void saveTabOMEClick(object sender, EventArgs a)
2402 {
2403 filechooser = new FileChooserDialog("Save Tab as an OME image.", this, FileChooserAction.Save, "Cancel", ResponseType.Cancel, "OK", ResponseType.Accept);
2404 if (filechooser.Run() != (int)ResponseType.Accept)
2405 return;
2406 filechooser.Hide();
2407 StartProgress("Save Selected OME", "Saving");
2408 BioImage[] exportImages = new BioImage[App.viewer.Images.Count];
2409 List<BioImage> temporaryImages = new List<BioImage>();
2410 try
2411 {
2412 for (int i = 0; i < App.viewer.Images.Count; i++)
2413 {
2414 BioImage sourceImage = App.viewer.Images[i];
2415 var (exportImage, isTemporary) = await GetOmeExportImageAsync(sourceImage);
2416 PreserveSourceAnnotations(sourceImage, exportImage);
2417 NormalizeMasksForOmeExport(exportImage);
2418 exportImages[i] = exportImage;
2419 if (isTemporary)
2420 temporaryImages.Add(exportImage);
2421 }
2422
2423 await Task.Run(() => BioImage.SaveOMESeries(exportImages, filechooser.Filename, true));
2424 }
2425 finally
2426 {
2427 foreach (BioImage temp in temporaryImages)
2428 ReleaseTemporaryExportImage(temp);
2429 }
2430 StopProgress();
2431 }
2438 protected async void saveTabTiffMenuClick(object sender, EventArgs a)
2439 {
2440 filechooser = new FileChooserDialog("Save Tab as an image.", this, FileChooserAction.Save, "Cancel", ResponseType.Cancel, "OK", ResponseType.Accept);
2441 if (filechooser.Run() != (int)ResponseType.Accept)
2442 return;
2443 filechooser.Hide();
2444 StartProgress("Save Selected Tiff", "Saving");
2445 await BioImage.SaveSeriesAsync(App.viewer.Images.ToArray(), filechooser.Filename, false);
2446 StopProgress();
2447 }
2452 protected async void saveSeriesMenuClick(object sender, EventArgs a)
2453 {
2454 filechooser = new FileChooserDialog("Save TIFF series.", this, FileChooserAction.Save, "Cancel", ResponseType.Cancel, "OK", ResponseType.Accept);
2455 if (filechooser.Run() != (int)ResponseType.Accept)
2456 return;
2457 filechooser.Hide();
2458 StartProgress("Save Selected Tiff", "Saving");
2459 await BioImage.SaveSeriesAsync(App.viewer.Images.ToArray(), filechooser.Filename, true);
2460 StopProgress();
2461 }
2466 protected void imagesToStackClick(object sender, EventArgs a)
2467 {
2468 Gtk.FileChooserDialog filechooser =
2469 new Gtk.FileChooserDialog("Choose images to convert to stack.",
2470 this,
2471 FileChooserAction.Save,
2472 "Cancel", ResponseType.Cancel,
2473 "Save", ResponseType.Accept);
2474 filechooser.SelectMultiple = true;
2475 if (filechooser.Run() != (int)ResponseType.Accept)
2476 return;
2477 filechooser.Hide();
2478 string[] sts = filechooser.Filenames;
2479 BioImage b = BioImage.ImagesToStack(sts, true);
2480 AddTab(b);
2481 }
2482
2487 protected void toolsMenuClick(object sender, EventArgs a)
2488 {
2489 if(App.tools == null)
2490 App.tools = Tools.Create();
2491 App.tools.Show();
2492 App.tools.Present();
2493 }
2498 protected void setToolMenuClick(object sender, EventArgs a)
2499 {
2500 App.setTool = SetTool.Create();
2501 App.setTool.Show();
2502 App.setTool.Present();
2503 }
2504
2509 protected void roiManagerMenuClick(object sender, EventArgs a)
2510 {
2511 App.roiManager = ROIManager.Create();
2512 App.roiManager.Show();
2513 App.roiManager.Present();
2514 }
2515
2520 protected void exportROIsToCSVMenuClick(object sender, EventArgs a)
2521 {
2522 Gtk.FileChooserDialog filechooser = new Gtk.FileChooserDialog("Set filename for export.",
2523 this,
2524 FileChooserAction.Save,
2525 "Cancel", ResponseType.Cancel,
2526 "Ok", ResponseType.Ok);
2527 filechooser.SelectMultiple = false;
2528 if (filechooser.Run() != (int)ResponseType.Ok)
2529 return;
2530 BioImage.ExportROIsCSV(filechooser.Filename, ImageView.SelectedImage.Annotations);
2531 filechooser.Hide();
2532 BioLib.Recorder.Record("BioImage.ExportROIsCSV(" + Q(filechooser.Filename) + ", ImageView.SelectedImage.Annotations);");
2533 }
2538 protected void importROIsFromCSVMenuClick(object sender, EventArgs a)
2539 {
2540 Gtk.FileChooserDialog filechooser = new Gtk.FileChooserDialog("Choose the file to open.",
2541 this,
2542 FileChooserAction.Open,
2543 "Cancel", ResponseType.Cancel,
2544 "Ok", ResponseType.Ok);
2545 filechooser.SelectMultiple = true;
2546 if (filechooser.Run() != (int)ResponseType.Ok)
2547 return;
2548 string[] sts = filechooser.Filenames;
2549 foreach (string item in filechooser.Filenames)
2550 {
2551 ImageView.SelectedImage.Annotations.AddRange(BioImage.ImportROIsCSV(item));
2552 }
2553 filechooser.Hide();
2554 BioLib.Recorder.Record("BioImage.ImportROIsCSV(" + StrArray(sts) + ");");
2555 }
2561 protected void exportROIsOfFolderOfImagesMenuClick(object sender, EventArgs a)
2562 {
2563 Gtk.FileChooserDialog folderchooser = new Gtk.FileChooserDialog("Set folder of images for export.",
2564 this,
2565 FileChooserAction.SelectFolder,
2566 "Cancel", ResponseType.Cancel,
2567 "Ok", ResponseType.Ok);
2568 folderchooser.SelectMultiple = true;
2569 if (folderchooser.Run() != (int)ResponseType.Ok)
2570 return;
2571 string folder = folderchooser.Filename;
2572 folderchooser.Destroy();
2573 Gtk.FileChooserDialog filechooser = new Gtk.FileChooserDialog("Set filename for CSV to export.",
2574 this,
2575 FileChooserAction.Save,
2576 "Cancel", ResponseType.Cancel,
2577 "Ok", ResponseType.Ok);
2578 filechooser.SelectMultiple = false;
2579 if (filechooser.Run() != (int)ResponseType.Ok)
2580 return;
2581 string file = filechooser.Filename;
2582 filechooser.Hide();
2583 BioImage.ExportROIFolder(folder, file);
2584 BioLib.Recorder.Record("BioImage.ExportROIFolder(" + Q(folder) + ", " + Q(file) + ");");
2585 }
2586
2591 protected void autoThresholdAllMenuClick(object sender, EventArgs a)
2592 {
2593 BioImage.AutoThreshold(ImageView.SelectedImage, true);
2594 if (ImageView.SelectedImage.bitsPerPixel > 8)
2595 ImageView.SelectedImage.StackThreshold(true);
2596 else
2597 ImageView.SelectedImage.StackThreshold(false);
2598 App.viewer.RefreshPyramidalTiles();
2599 App.viewer.UpdateImage();
2600 App.viewer.UpdateView();
2601 }
2606 protected void channelsToolMenuClick(object sender, EventArgs a)
2607 {
2608 if (App.viewer == null)
2609 return;
2610 App.channelsTool = ChannelsTool.Create();
2611 App.channelsTool.Show();
2612 App.channelsTool.Present();
2613 }
2618 protected void switchRedBlueMenuClick(object sender, EventArgs a)
2619 {
2620 foreach (AForge.Bitmap bf in ImageView.SelectedImage.Buffers)
2621 {
2622 bf.SwitchRedBlue();
2623 }
2624 App.viewer.UpdateImage();
2625 App.viewer.UpdateView();
2626 }
2627
2632 protected void stackToolMenuClick(object sender, EventArgs a)
2633 {
2634 App.stack = StackTools.Create();
2635 App.stack.Show();
2636 App.stack.Present();
2637 }
2638
2643 protected void to8BitMenuClick(object sender, EventArgs a)
2644 {
2645 ImageView.SelectedImage.To8Bit();
2646 }
2651 protected void to16BitMenuClick(object sender, EventArgs a)
2652 {
2653 ImageView.SelectedImage.To16Bit();
2654 }
2659 protected void to24BitMenuClick(object sender, EventArgs a)
2660 {
2661 ImageView.SelectedImage.To24Bit();
2662 }
2667 protected void to32BitMenuClick(object sender, EventArgs a)
2668 {
2669 ImageView.SelectedImage.To32Bit();
2670 }
2675 protected void to48BitMenuClick(object sender, EventArgs a)
2676 {
2677 ImageView.SelectedImage.To48Bit();
2678 }
2679
2684 protected void filtersMenuClick(object sender, EventArgs a)
2685 {
2686 App.filters = FiltersView.Create();
2687 App.filters.Show();
2688 App.filters.Present();
2689 }
2690
2695 protected void functionsToolMenuClick(object sender, EventArgs a)
2696 {
2697 App.funcs = Functions.Create();
2698 App.funcs.Show();
2699 App.funcs.Present();
2700 }
2705 protected void consoleMenuClick(object sender, EventArgs a)
2706 {
2707 App.console = BioConsole.Create();
2708 App.console.Show();
2709 App.console.Present();
2710 }
2715 protected void scriptRunnerMenuClick(object sender, EventArgs a)
2716 {
2717 App.scripting = Scripting.Create();
2718 App.scripting.Show();
2719 App.scripting.Present();
2720 }
2721
2726 protected void AboutClick(object sender, EventArgs a)
2727 {
2728 App.about = About.Create();
2729 App.about.Show();
2730 App.about.Present();
2731 }
2732
2736 public void RemoveTab(string tabName)
2737 {
2738 string shortName = System.IO.Path.GetFileName(tabName);
2739 for (int i = tabsView.NPages - 1; i >= 0; i--)
2740 {
2741 Widget page = tabsView.GetNthPage(i);
2742 // Check the visible tab label (the one the user sees)
2743 Widget tabLabelWidget = tabsView.GetTabLabel(page);
2744 string tabText = null;
2745 if (tabLabelWidget is Gtk.Label tl)
2746 tabText = tl.Text;
2747
2748 // Also check the page content widget (a hidden dummy Label
2749 // whose Text holds the full path)
2750 string pageText = null;
2751 if (page is Gtk.Label pl)
2752 pageText = pl.Text;
2753
2754 bool match = (tabText != null && (tabText == tabName || tabText == shortName))
2755 || (pageText != null && (pageText == tabName
2756 || System.IO.Path.GetFileName(pageText) == shortName));
2757
2758 if (match)
2759 {
2760 tabsView.RemovePage(i);
2761 App.nodeView.UpdateItems();
2762 return;
2763 }
2764 }
2765 }
2769 public async void Open(string file)
2770 {
2771 await OpenPyramidRootsAsViewsAsync(file, true);
2772 }
2773 #endregion
2774
2775 }
2776}
static About Create()
Definition About.cs:33
static BioConsole Create()
Definition BioConsole.cs:52
static void InitializeMainMenu()
Definition Functions.cs:611
static Functions Create()
Default Shared Constructor.
Definition Functions.cs:72
void UpdateView(bool updateImages=true)
It updates the view.
static ImageView Create(BioImage bm)
Definition ImageView.cs:585
void GoToImage()
This function is used to go to the image at the specified index.
static Scripting Create()
Definition Scripting.cs:60
static SetTool Create()
Definition SetTool.cs:32
void RequestRedraw()
Request a redraw of the GLArea.
static StackTools Create()
Default Shared Constructor.
Definition StackTools.cs:48
void to32BitMenuClick(object sender, EventArgs a)
Definition TabsView.cs:2667
void SetTab(int i)
Sets the current tab index.
Definition TabsView.cs:1783
async void addImagesToTabMenuClick(object sender, EventArgs a)
Definition TabsView.cs:2147
void AboutClick(object sender, EventArgs a)
Definition TabsView.cs:2726
async void Open(string file)
Definition TabsView.cs:2769
void exportROIsOfFolderOfImagesMenuClick(object sender, EventArgs a)
Definition TabsView.cs:2561
async void saveSelectedTiffClick(object sender, EventArgs a)
Definition TabsView.cs:2358
void exportROIsToCSVMenuClick(object sender, EventArgs a)
Definition TabsView.cs:2520
void to8BitMenuClick(object sender, EventArgs a)
Definition TabsView.cs:2643
static bool SetImageJPath()
Definition TabsView.cs:634
async void saveSelectedOMEClick(object sender, EventArgs a)
Definition TabsView.cs:2374
void autoThresholdAllMenuClick(object sender, EventArgs a)
Definition TabsView.cs:2591
void AddTab(BioImage im, string? sourcePath=null)
Definition TabsView.cs:1791
void SetupHandlers()
This function sets up the event handlers for the menu items.
Definition TabsView.cs:1033
void to48BitMenuClick(object sender, EventArgs a)
Definition TabsView.cs:2675
async void openOMESeriesMenuClick(object sender, EventArgs a)
Definition TabsView.cs:2051
void to16BitMenuClick(object sender, EventArgs a)
Definition TabsView.cs:2651
async void saveTabOMEClick(object sender, EventArgs a)
Definition TabsView.cs:2401
async void openSeriesMenuClick(object sender, EventArgs a)
Definition TabsView.cs:2075
void filtersMenuClick(object sender, EventArgs a)
Definition TabsView.cs:2684
void functionsToolMenuClick(object sender, EventArgs a)
Definition TabsView.cs:2695
async void openImagesMenuClick(object sender, EventArgs a)
Definition TabsView.cs:1998
void scriptRunnerMenuClick(object sender, EventArgs a)
Definition TabsView.cs:2715
void toolsMenuClick(object sender, EventArgs a)
Definition TabsView.cs:2487
void stackToolMenuClick(object sender, EventArgs a)
Definition TabsView.cs:2632
async void openOMEImagesMenuClick(object sender, EventArgs a)
Definition TabsView.cs:2028
static TabsView Create()
Definition TabsView.cs:545
void channelsToolMenuClick(object sender, EventArgs a)
Definition TabsView.cs:2606
void imagesToStackClick(object sender, EventArgs a)
Definition TabsView.cs:2466
async void saveSeriesMenuClick(object sender, EventArgs a)
Definition TabsView.cs:2452
void to24BitMenuClick(object sender, EventArgs a)
Definition TabsView.cs:2659
async void addOMEImagesToTabClick(object sender, EventArgs a)
Definition TabsView.cs:2173
void switchRedBlueMenuClick(object sender, EventArgs a)
Definition TabsView.cs:2618
void consoleMenuClick(object sender, EventArgs a)
Definition TabsView.cs:2705
void importROIsFromCSVMenuClick(object sender, EventArgs a)
Definition TabsView.cs:2538
async void saveTabTiffMenuClick(object sender, EventArgs a)
Definition TabsView.cs:2438
void RemoveTab(string tabName)
Definition TabsView.cs:2736
void setToolMenuClick(object sender, EventArgs a)
Definition TabsView.cs:2498
void roiManagerMenuClick(object sender, EventArgs a)
Definition TabsView.cs:2509
Implementations for all tool types defined in Tools.cs.
Definition Tools.cs:23
static Tools Create()
Definition Tools.cs:36
Definition ML.cs:17
Definition Zarr.cs:20