lazylabel-gui 1.1.8__py3-none-any.whl → 1.1.9__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- lazylabel/ui/control_panel.py +885 -861
- lazylabel/ui/main_window.py +3085 -3007
- lazylabel/ui/widgets/__init__.py +2 -0
- lazylabel/ui/widgets/fft_threshold_widget.py +392 -0
- {lazylabel_gui-1.1.8.dist-info → lazylabel_gui-1.1.9.dist-info}/METADATA +1 -1
- {lazylabel_gui-1.1.8.dist-info → lazylabel_gui-1.1.9.dist-info}/RECORD +10 -9
- {lazylabel_gui-1.1.8.dist-info → lazylabel_gui-1.1.9.dist-info}/WHEEL +0 -0
- {lazylabel_gui-1.1.8.dist-info → lazylabel_gui-1.1.9.dist-info}/entry_points.txt +0 -0
- {lazylabel_gui-1.1.8.dist-info → lazylabel_gui-1.1.9.dist-info}/licenses/LICENSE +0 -0
- {lazylabel_gui-1.1.8.dist-info → lazylabel_gui-1.1.9.dist-info}/top_level.txt +0 -0
lazylabel/ui/control_panel.py
CHANGED
@@ -1,861 +1,885 @@
|
|
1
|
-
"""Left control panel with mode controls and settings."""
|
2
|
-
|
3
|
-
from PyQt6.QtCore import Qt, pyqtSignal
|
4
|
-
from PyQt6.QtWidgets import (
|
5
|
-
QFrame,
|
6
|
-
QHBoxLayout,
|
7
|
-
QLabel,
|
8
|
-
QPushButton,
|
9
|
-
QScrollArea,
|
10
|
-
QTabWidget,
|
11
|
-
QVBoxLayout,
|
12
|
-
QWidget,
|
13
|
-
)
|
14
|
-
|
15
|
-
from .widgets import (
|
16
|
-
AdjustmentsWidget,
|
17
|
-
BorderCropWidget,
|
18
|
-
ChannelThresholdWidget,
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
self.
|
32
|
-
|
33
|
-
|
34
|
-
layout
|
35
|
-
layout.
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
header_layout
|
40
|
-
|
41
|
-
|
42
|
-
self.toggle_button
|
43
|
-
self.toggle_button.
|
44
|
-
self.toggle_button.
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
font-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
title_label
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
font-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
header_layout.addWidget(
|
83
|
-
header_layout.
|
84
|
-
|
85
|
-
|
86
|
-
self.header_widget
|
87
|
-
self.header_widget.
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
border:
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
layout.addWidget(
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
self.
|
108
|
-
self.
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
self.
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
border
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
layout
|
130
|
-
layout.
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
title_label
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
font-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
#
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
self.
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
layout.
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
self.
|
211
|
-
self.
|
212
|
-
self.
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
self.btn_popout
|
223
|
-
|
224
|
-
|
225
|
-
|
226
|
-
|
227
|
-
|
228
|
-
|
229
|
-
|
230
|
-
|
231
|
-
|
232
|
-
|
233
|
-
|
234
|
-
|
235
|
-
|
236
|
-
|
237
|
-
|
238
|
-
|
239
|
-
|
240
|
-
|
241
|
-
|
242
|
-
|
243
|
-
layout.
|
244
|
-
|
245
|
-
#
|
246
|
-
|
247
|
-
|
248
|
-
|
249
|
-
|
250
|
-
|
251
|
-
|
252
|
-
|
253
|
-
|
254
|
-
|
255
|
-
|
256
|
-
|
257
|
-
|
258
|
-
|
259
|
-
|
260
|
-
|
261
|
-
|
262
|
-
|
263
|
-
|
264
|
-
|
265
|
-
|
266
|
-
|
267
|
-
|
268
|
-
|
269
|
-
|
270
|
-
|
271
|
-
|
272
|
-
|
273
|
-
|
274
|
-
|
275
|
-
|
276
|
-
|
277
|
-
|
278
|
-
}
|
279
|
-
|
280
|
-
|
281
|
-
|
282
|
-
|
283
|
-
|
284
|
-
|
285
|
-
|
286
|
-
#
|
287
|
-
|
288
|
-
self.tab_widget.addTab(
|
289
|
-
|
290
|
-
|
291
|
-
|
292
|
-
|
293
|
-
|
294
|
-
self.
|
295
|
-
|
296
|
-
|
297
|
-
|
298
|
-
|
299
|
-
|
300
|
-
|
301
|
-
|
302
|
-
|
303
|
-
|
304
|
-
|
305
|
-
|
306
|
-
|
307
|
-
|
308
|
-
|
309
|
-
|
310
|
-
|
311
|
-
|
312
|
-
|
313
|
-
|
314
|
-
|
315
|
-
|
316
|
-
|
317
|
-
#
|
318
|
-
|
319
|
-
|
320
|
-
|
321
|
-
|
322
|
-
|
323
|
-
)
|
324
|
-
|
325
|
-
self.btn_sam_mode.
|
326
|
-
|
327
|
-
|
328
|
-
|
329
|
-
)
|
330
|
-
|
331
|
-
|
332
|
-
|
333
|
-
|
334
|
-
|
335
|
-
|
336
|
-
|
337
|
-
|
338
|
-
|
339
|
-
|
340
|
-
|
341
|
-
|
342
|
-
)
|
343
|
-
|
344
|
-
|
345
|
-
|
346
|
-
|
347
|
-
)
|
348
|
-
|
349
|
-
|
350
|
-
|
351
|
-
|
352
|
-
|
353
|
-
|
354
|
-
|
355
|
-
|
356
|
-
|
357
|
-
|
358
|
-
|
359
|
-
|
360
|
-
|
361
|
-
|
362
|
-
)
|
363
|
-
|
364
|
-
|
365
|
-
|
366
|
-
|
367
|
-
)
|
368
|
-
|
369
|
-
|
370
|
-
|
371
|
-
|
372
|
-
|
373
|
-
|
374
|
-
|
375
|
-
|
376
|
-
|
377
|
-
|
378
|
-
|
379
|
-
|
380
|
-
|
381
|
-
|
382
|
-
button
|
383
|
-
|
384
|
-
|
385
|
-
|
386
|
-
|
387
|
-
|
388
|
-
|
389
|
-
|
390
|
-
|
391
|
-
|
392
|
-
|
393
|
-
|
394
|
-
|
395
|
-
|
396
|
-
}
|
397
|
-
QPushButton:
|
398
|
-
background-color: rgba(
|
399
|
-
|
400
|
-
|
401
|
-
|
402
|
-
|
403
|
-
|
404
|
-
|
405
|
-
|
406
|
-
|
407
|
-
|
408
|
-
|
409
|
-
}
|
410
|
-
|
411
|
-
|
412
|
-
|
413
|
-
|
414
|
-
|
415
|
-
|
416
|
-
|
417
|
-
|
418
|
-
|
419
|
-
|
420
|
-
|
421
|
-
|
422
|
-
|
423
|
-
|
424
|
-
|
425
|
-
|
426
|
-
|
427
|
-
button
|
428
|
-
|
429
|
-
|
430
|
-
|
431
|
-
|
432
|
-
|
433
|
-
|
434
|
-
|
435
|
-
|
436
|
-
|
437
|
-
|
438
|
-
|
439
|
-
|
440
|
-
|
441
|
-
}
|
442
|
-
QPushButton:
|
443
|
-
background-color: rgba(
|
444
|
-
|
445
|
-
|
446
|
-
|
447
|
-
|
448
|
-
|
449
|
-
|
450
|
-
|
451
|
-
|
452
|
-
|
453
|
-
|
454
|
-
}
|
455
|
-
|
456
|
-
|
457
|
-
|
458
|
-
|
459
|
-
|
460
|
-
|
461
|
-
return
|
462
|
-
|
463
|
-
|
464
|
-
|
465
|
-
|
466
|
-
|
467
|
-
|
468
|
-
|
469
|
-
|
470
|
-
|
471
|
-
|
472
|
-
|
473
|
-
|
474
|
-
|
475
|
-
}
|
476
|
-
QPushButton:
|
477
|
-
background-color: rgba(
|
478
|
-
|
479
|
-
|
480
|
-
|
481
|
-
|
482
|
-
|
483
|
-
|
484
|
-
|
485
|
-
|
486
|
-
|
487
|
-
|
488
|
-
|
489
|
-
|
490
|
-
|
491
|
-
|
492
|
-
|
493
|
-
|
494
|
-
|
495
|
-
|
496
|
-
|
497
|
-
|
498
|
-
|
499
|
-
|
500
|
-
|
501
|
-
|
502
|
-
|
503
|
-
|
504
|
-
|
505
|
-
|
506
|
-
|
507
|
-
|
508
|
-
|
509
|
-
scroll
|
510
|
-
scroll
|
511
|
-
|
512
|
-
|
513
|
-
|
514
|
-
|
515
|
-
|
516
|
-
|
517
|
-
|
518
|
-
|
519
|
-
|
520
|
-
|
521
|
-
|
522
|
-
|
523
|
-
border-radius: 4px;
|
524
|
-
|
525
|
-
|
526
|
-
|
527
|
-
|
528
|
-
|
529
|
-
|
530
|
-
|
531
|
-
|
532
|
-
|
533
|
-
|
534
|
-
|
535
|
-
|
536
|
-
|
537
|
-
|
538
|
-
|
539
|
-
layout.
|
540
|
-
|
541
|
-
# AI
|
542
|
-
|
543
|
-
|
544
|
-
|
545
|
-
|
546
|
-
|
547
|
-
|
548
|
-
|
549
|
-
|
550
|
-
|
551
|
-
|
552
|
-
|
553
|
-
|
554
|
-
|
555
|
-
|
556
|
-
|
557
|
-
|
558
|
-
|
559
|
-
|
560
|
-
|
561
|
-
|
562
|
-
|
563
|
-
|
564
|
-
|
565
|
-
tab_widget
|
566
|
-
|
567
|
-
|
568
|
-
|
569
|
-
|
570
|
-
|
571
|
-
scroll
|
572
|
-
scroll
|
573
|
-
|
574
|
-
|
575
|
-
|
576
|
-
|
577
|
-
|
578
|
-
|
579
|
-
|
580
|
-
|
581
|
-
|
582
|
-
|
583
|
-
|
584
|
-
|
585
|
-
border-radius: 4px;
|
586
|
-
|
587
|
-
|
588
|
-
|
589
|
-
|
590
|
-
|
591
|
-
|
592
|
-
|
593
|
-
|
594
|
-
|
595
|
-
|
596
|
-
|
597
|
-
|
598
|
-
|
599
|
-
|
600
|
-
|
601
|
-
layout.
|
602
|
-
|
603
|
-
#
|
604
|
-
|
605
|
-
|
606
|
-
|
607
|
-
|
608
|
-
|
609
|
-
|
610
|
-
|
611
|
-
|
612
|
-
|
613
|
-
|
614
|
-
|
615
|
-
|
616
|
-
|
617
|
-
|
618
|
-
|
619
|
-
|
620
|
-
|
621
|
-
|
622
|
-
|
623
|
-
|
624
|
-
|
625
|
-
|
626
|
-
|
627
|
-
|
628
|
-
|
629
|
-
|
630
|
-
|
631
|
-
|
632
|
-
|
633
|
-
|
634
|
-
|
635
|
-
|
636
|
-
|
637
|
-
self.
|
638
|
-
self.
|
639
|
-
|
640
|
-
|
641
|
-
self.
|
642
|
-
|
643
|
-
|
644
|
-
|
645
|
-
|
646
|
-
)
|
647
|
-
self.
|
648
|
-
self.
|
649
|
-
|
650
|
-
|
651
|
-
self.
|
652
|
-
|
653
|
-
|
654
|
-
self.adjustments_widget.
|
655
|
-
self.
|
656
|
-
)
|
657
|
-
self.adjustments_widget.
|
658
|
-
|
659
|
-
|
660
|
-
|
661
|
-
|
662
|
-
self.
|
663
|
-
|
664
|
-
|
665
|
-
|
666
|
-
|
667
|
-
self.
|
668
|
-
|
669
|
-
|
670
|
-
|
671
|
-
#
|
672
|
-
self.
|
673
|
-
self.
|
674
|
-
)
|
675
|
-
|
676
|
-
|
677
|
-
|
678
|
-
self.
|
679
|
-
self.
|
680
|
-
|
681
|
-
|
682
|
-
|
683
|
-
|
684
|
-
|
685
|
-
|
686
|
-
|
687
|
-
|
688
|
-
|
689
|
-
|
690
|
-
|
691
|
-
def
|
692
|
-
"""Handle
|
693
|
-
self._set_active_mode_button(self.
|
694
|
-
self.
|
695
|
-
|
696
|
-
def
|
697
|
-
"""Handle
|
698
|
-
|
699
|
-
self.
|
700
|
-
|
701
|
-
def
|
702
|
-
"""
|
703
|
-
|
704
|
-
|
705
|
-
|
706
|
-
|
707
|
-
|
708
|
-
|
709
|
-
|
710
|
-
|
711
|
-
|
712
|
-
|
713
|
-
|
714
|
-
|
715
|
-
|
716
|
-
|
717
|
-
|
718
|
-
|
719
|
-
|
720
|
-
|
721
|
-
self.
|
722
|
-
|
723
|
-
|
724
|
-
|
725
|
-
|
726
|
-
|
727
|
-
|
728
|
-
|
729
|
-
|
730
|
-
self.
|
731
|
-
|
732
|
-
|
733
|
-
def
|
734
|
-
"""
|
735
|
-
|
736
|
-
|
737
|
-
|
738
|
-
|
739
|
-
|
740
|
-
|
741
|
-
|
742
|
-
|
743
|
-
|
744
|
-
|
745
|
-
|
746
|
-
|
747
|
-
|
748
|
-
|
749
|
-
|
750
|
-
|
751
|
-
|
752
|
-
|
753
|
-
|
754
|
-
|
755
|
-
|
756
|
-
|
757
|
-
|
758
|
-
|
759
|
-
|
760
|
-
|
761
|
-
|
762
|
-
|
763
|
-
|
764
|
-
|
765
|
-
|
766
|
-
|
767
|
-
|
768
|
-
|
769
|
-
|
770
|
-
|
771
|
-
|
772
|
-
|
773
|
-
|
774
|
-
|
775
|
-
|
776
|
-
|
777
|
-
|
778
|
-
|
779
|
-
|
780
|
-
|
781
|
-
|
782
|
-
|
783
|
-
|
784
|
-
|
785
|
-
|
786
|
-
|
787
|
-
|
788
|
-
|
789
|
-
|
790
|
-
|
791
|
-
|
792
|
-
|
793
|
-
|
794
|
-
|
795
|
-
|
796
|
-
|
797
|
-
|
798
|
-
|
799
|
-
|
800
|
-
|
801
|
-
|
802
|
-
|
803
|
-
|
804
|
-
|
805
|
-
|
806
|
-
|
807
|
-
|
808
|
-
|
809
|
-
|
810
|
-
|
811
|
-
|
812
|
-
|
813
|
-
|
814
|
-
|
815
|
-
|
816
|
-
|
817
|
-
|
818
|
-
|
819
|
-
|
820
|
-
|
821
|
-
|
822
|
-
|
823
|
-
|
824
|
-
|
825
|
-
|
826
|
-
|
827
|
-
|
828
|
-
|
829
|
-
|
830
|
-
|
831
|
-
|
832
|
-
|
833
|
-
|
834
|
-
|
835
|
-
|
836
|
-
|
837
|
-
|
838
|
-
|
839
|
-
|
840
|
-
|
841
|
-
|
842
|
-
|
843
|
-
|
844
|
-
|
845
|
-
|
846
|
-
|
847
|
-
|
848
|
-
|
849
|
-
|
850
|
-
|
851
|
-
|
852
|
-
|
853
|
-
|
854
|
-
|
855
|
-
|
856
|
-
|
857
|
-
|
858
|
-
|
859
|
-
|
860
|
-
|
861
|
-
|
1
|
+
"""Left control panel with mode controls and settings."""
|
2
|
+
|
3
|
+
from PyQt6.QtCore import Qt, pyqtSignal
|
4
|
+
from PyQt6.QtWidgets import (
|
5
|
+
QFrame,
|
6
|
+
QHBoxLayout,
|
7
|
+
QLabel,
|
8
|
+
QPushButton,
|
9
|
+
QScrollArea,
|
10
|
+
QTabWidget,
|
11
|
+
QVBoxLayout,
|
12
|
+
QWidget,
|
13
|
+
)
|
14
|
+
|
15
|
+
from .widgets import (
|
16
|
+
AdjustmentsWidget,
|
17
|
+
BorderCropWidget,
|
18
|
+
ChannelThresholdWidget,
|
19
|
+
FFTThresholdWidget,
|
20
|
+
FragmentThresholdWidget,
|
21
|
+
ModelSelectionWidget,
|
22
|
+
SettingsWidget,
|
23
|
+
)
|
24
|
+
|
25
|
+
|
26
|
+
class SimpleCollapsible(QWidget):
|
27
|
+
"""A simple collapsible widget for use within tabs."""
|
28
|
+
|
29
|
+
def __init__(self, title: str, content_widget: QWidget, parent=None):
|
30
|
+
super().__init__(parent)
|
31
|
+
self.content_widget = content_widget
|
32
|
+
self.is_collapsed = False
|
33
|
+
|
34
|
+
layout = QVBoxLayout(self)
|
35
|
+
layout.setContentsMargins(0, 0, 0, 0)
|
36
|
+
layout.setSpacing(2)
|
37
|
+
|
38
|
+
# Header with toggle button
|
39
|
+
header_layout = QHBoxLayout()
|
40
|
+
header_layout.setContentsMargins(2, 2, 2, 2)
|
41
|
+
|
42
|
+
self.toggle_button = QPushButton("▼")
|
43
|
+
self.toggle_button.setMaximumWidth(16)
|
44
|
+
self.toggle_button.setMaximumHeight(16)
|
45
|
+
self.toggle_button.setStyleSheet(
|
46
|
+
"""
|
47
|
+
QPushButton {
|
48
|
+
border: 1px solid rgba(120, 120, 120, 0.5);
|
49
|
+
background: rgba(70, 70, 70, 0.6);
|
50
|
+
color: #E0E0E0;
|
51
|
+
font-size: 10px;
|
52
|
+
font-weight: bold;
|
53
|
+
border-radius: 2px;
|
54
|
+
}
|
55
|
+
QPushButton:hover {
|
56
|
+
background: rgba(90, 90, 90, 0.8);
|
57
|
+
border: 1px solid rgba(140, 140, 140, 0.8);
|
58
|
+
color: #FFF;
|
59
|
+
}
|
60
|
+
QPushButton:pressed {
|
61
|
+
background: rgba(50, 50, 50, 0.8);
|
62
|
+
border: 1px solid rgba(100, 100, 100, 0.6);
|
63
|
+
}
|
64
|
+
"""
|
65
|
+
)
|
66
|
+
self.toggle_button.clicked.connect(self.toggle_collapse)
|
67
|
+
|
68
|
+
title_label = QLabel(title)
|
69
|
+
title_label.setStyleSheet(
|
70
|
+
"""
|
71
|
+
QLabel {
|
72
|
+
color: #E0E0E0;
|
73
|
+
font-weight: bold;
|
74
|
+
font-size: 11px;
|
75
|
+
background: transparent;
|
76
|
+
border: none;
|
77
|
+
padding: 2px;
|
78
|
+
}
|
79
|
+
"""
|
80
|
+
)
|
81
|
+
|
82
|
+
header_layout.addWidget(self.toggle_button)
|
83
|
+
header_layout.addWidget(title_label)
|
84
|
+
header_layout.addStretch()
|
85
|
+
|
86
|
+
self.header_widget = QWidget()
|
87
|
+
self.header_widget.setLayout(header_layout)
|
88
|
+
self.header_widget.setStyleSheet(
|
89
|
+
"""
|
90
|
+
QWidget {
|
91
|
+
background-color: rgba(60, 60, 60, 0.3);
|
92
|
+
border-radius: 3px;
|
93
|
+
border: 1px solid rgba(80, 80, 80, 0.4);
|
94
|
+
}
|
95
|
+
"""
|
96
|
+
)
|
97
|
+
self.header_widget.setFixedHeight(20)
|
98
|
+
|
99
|
+
layout.addWidget(self.header_widget)
|
100
|
+
layout.addWidget(content_widget)
|
101
|
+
|
102
|
+
# Add some spacing below content
|
103
|
+
layout.addSpacing(4)
|
104
|
+
|
105
|
+
def toggle_collapse(self):
|
106
|
+
"""Toggle the collapsed state."""
|
107
|
+
self.is_collapsed = not self.is_collapsed
|
108
|
+
self.content_widget.setVisible(not self.is_collapsed)
|
109
|
+
self.toggle_button.setText("▶" if self.is_collapsed else "▼")
|
110
|
+
|
111
|
+
|
112
|
+
class ProfessionalCard(QFrame):
|
113
|
+
"""A professional-looking card widget for containing controls."""
|
114
|
+
|
115
|
+
def __init__(self, title: str = "", parent=None):
|
116
|
+
super().__init__(parent)
|
117
|
+
self.setFrameStyle(QFrame.Shape.Box)
|
118
|
+
self.setStyleSheet(
|
119
|
+
"""
|
120
|
+
QFrame {
|
121
|
+
background-color: rgba(40, 40, 40, 0.8);
|
122
|
+
border: 1px solid rgba(80, 80, 80, 0.6);
|
123
|
+
border-radius: 8px;
|
124
|
+
margin: 2px;
|
125
|
+
}
|
126
|
+
"""
|
127
|
+
)
|
128
|
+
|
129
|
+
layout = QVBoxLayout(self)
|
130
|
+
layout.setContentsMargins(8, 8, 8, 8)
|
131
|
+
layout.setSpacing(6)
|
132
|
+
|
133
|
+
if title:
|
134
|
+
title_label = QLabel(title)
|
135
|
+
title_label.setStyleSheet(
|
136
|
+
"""
|
137
|
+
QLabel {
|
138
|
+
color: #E0E0E0;
|
139
|
+
font-weight: bold;
|
140
|
+
font-size: 11px;
|
141
|
+
border: none;
|
142
|
+
background: transparent;
|
143
|
+
padding: 0px;
|
144
|
+
margin-bottom: 4px;
|
145
|
+
}
|
146
|
+
"""
|
147
|
+
)
|
148
|
+
layout.addWidget(title_label)
|
149
|
+
|
150
|
+
self.content_layout = layout
|
151
|
+
|
152
|
+
def addWidget(self, widget):
|
153
|
+
"""Add a widget to the card."""
|
154
|
+
self.content_layout.addWidget(widget)
|
155
|
+
|
156
|
+
def addLayout(self, layout):
|
157
|
+
"""Add a layout to the card."""
|
158
|
+
self.content_layout.addLayout(layout)
|
159
|
+
|
160
|
+
|
161
|
+
class ControlPanel(QWidget):
|
162
|
+
"""Left control panel with mode controls and settings."""
|
163
|
+
|
164
|
+
# Signals
|
165
|
+
sam_mode_requested = pyqtSignal()
|
166
|
+
polygon_mode_requested = pyqtSignal()
|
167
|
+
bbox_mode_requested = pyqtSignal()
|
168
|
+
selection_mode_requested = pyqtSignal()
|
169
|
+
edit_mode_requested = pyqtSignal()
|
170
|
+
clear_points_requested = pyqtSignal()
|
171
|
+
fit_view_requested = pyqtSignal()
|
172
|
+
browse_models_requested = pyqtSignal()
|
173
|
+
refresh_models_requested = pyqtSignal()
|
174
|
+
model_selected = pyqtSignal(str)
|
175
|
+
annotation_size_changed = pyqtSignal(int)
|
176
|
+
pan_speed_changed = pyqtSignal(int)
|
177
|
+
join_threshold_changed = pyqtSignal(int)
|
178
|
+
fragment_threshold_changed = pyqtSignal(int)
|
179
|
+
brightness_changed = pyqtSignal(int)
|
180
|
+
contrast_changed = pyqtSignal(int)
|
181
|
+
gamma_changed = pyqtSignal(int)
|
182
|
+
reset_adjustments_requested = pyqtSignal()
|
183
|
+
image_adjustment_changed = pyqtSignal()
|
184
|
+
hotkeys_requested = pyqtSignal()
|
185
|
+
pop_out_requested = pyqtSignal()
|
186
|
+
settings_changed = pyqtSignal()
|
187
|
+
# Border crop signals
|
188
|
+
crop_draw_requested = pyqtSignal()
|
189
|
+
crop_clear_requested = pyqtSignal()
|
190
|
+
crop_applied = pyqtSignal(int, int, int, int) # x1, y1, x2, y2
|
191
|
+
# Channel threshold signals
|
192
|
+
channel_threshold_changed = pyqtSignal()
|
193
|
+
# FFT threshold signals
|
194
|
+
fft_threshold_changed = pyqtSignal()
|
195
|
+
|
196
|
+
def __init__(self, parent=None):
|
197
|
+
super().__init__(parent)
|
198
|
+
self.setMinimumWidth(260) # Slightly wider for better layout
|
199
|
+
self.preferred_width = 280
|
200
|
+
self._setup_ui()
|
201
|
+
self._connect_signals()
|
202
|
+
|
203
|
+
def _setup_ui(self):
|
204
|
+
"""Setup the professional UI layout."""
|
205
|
+
layout = QVBoxLayout(self)
|
206
|
+
layout.setContentsMargins(8, 8, 8, 8)
|
207
|
+
layout.setSpacing(8)
|
208
|
+
|
209
|
+
# Create widgets first
|
210
|
+
self.model_widget = ModelSelectionWidget()
|
211
|
+
self.crop_widget = BorderCropWidget()
|
212
|
+
self.channel_threshold_widget = ChannelThresholdWidget()
|
213
|
+
self.fft_threshold_widget = FFTThresholdWidget()
|
214
|
+
self.settings_widget = SettingsWidget()
|
215
|
+
self.adjustments_widget = AdjustmentsWidget()
|
216
|
+
self.fragment_widget = FragmentThresholdWidget()
|
217
|
+
|
218
|
+
# Top header with pop-out button
|
219
|
+
header_layout = QHBoxLayout()
|
220
|
+
header_layout.addStretch()
|
221
|
+
|
222
|
+
self.btn_popout = QPushButton("⋯")
|
223
|
+
self.btn_popout.setToolTip("Pop out panel to separate window")
|
224
|
+
self.btn_popout.setMaximumWidth(30)
|
225
|
+
self.btn_popout.setMaximumHeight(25)
|
226
|
+
self.btn_popout.setStyleSheet(
|
227
|
+
"""
|
228
|
+
QPushButton {
|
229
|
+
background-color: rgba(60, 60, 60, 0.8);
|
230
|
+
border: 1px solid rgba(80, 80, 80, 0.6);
|
231
|
+
border-radius: 4px;
|
232
|
+
color: #E0E0E0;
|
233
|
+
}
|
234
|
+
QPushButton:hover {
|
235
|
+
background-color: rgba(80, 80, 80, 0.9);
|
236
|
+
}
|
237
|
+
QPushButton:pressed {
|
238
|
+
background-color: rgba(40, 40, 40, 0.9);
|
239
|
+
}
|
240
|
+
"""
|
241
|
+
)
|
242
|
+
header_layout.addWidget(self.btn_popout)
|
243
|
+
layout.addLayout(header_layout)
|
244
|
+
|
245
|
+
# Fixed Mode Controls Section (Always Visible)
|
246
|
+
mode_card = self._create_mode_card()
|
247
|
+
layout.addWidget(mode_card)
|
248
|
+
|
249
|
+
# Tabbed Interface for Everything Else
|
250
|
+
self.tab_widget = QTabWidget()
|
251
|
+
self.tab_widget.setStyleSheet(
|
252
|
+
"""
|
253
|
+
QTabWidget::pane {
|
254
|
+
border: 1px solid rgba(80, 80, 80, 0.6);
|
255
|
+
border-radius: 6px;
|
256
|
+
background-color: rgba(35, 35, 35, 0.9);
|
257
|
+
margin-top: 2px;
|
258
|
+
}
|
259
|
+
QTabWidget::tab-bar {
|
260
|
+
alignment: center;
|
261
|
+
}
|
262
|
+
QTabBar::tab {
|
263
|
+
background-color: rgba(50, 50, 50, 0.8);
|
264
|
+
border: 1px solid rgba(70, 70, 70, 0.6);
|
265
|
+
border-bottom: none;
|
266
|
+
border-radius: 4px 4px 0 0;
|
267
|
+
padding: 4px 8px;
|
268
|
+
margin-right: 1px;
|
269
|
+
color: #B0B0B0;
|
270
|
+
font-size: 9px;
|
271
|
+
min-width: 45px;
|
272
|
+
max-width: 80px;
|
273
|
+
}
|
274
|
+
QTabBar::tab:selected {
|
275
|
+
background-color: rgba(70, 70, 70, 0.9);
|
276
|
+
color: #E0E0E0;
|
277
|
+
font-weight: bold;
|
278
|
+
}
|
279
|
+
QTabBar::tab:hover:!selected {
|
280
|
+
background-color: rgba(60, 60, 60, 0.8);
|
281
|
+
color: #D0D0D0;
|
282
|
+
}
|
283
|
+
"""
|
284
|
+
)
|
285
|
+
|
286
|
+
# AI & Settings Tab
|
287
|
+
ai_tab = self._create_ai_tab()
|
288
|
+
self.tab_widget.addTab(ai_tab, "🤖 AI")
|
289
|
+
|
290
|
+
# Processing & Adjustments Tab
|
291
|
+
processing_tab = self._create_processing_tab()
|
292
|
+
self.tab_widget.addTab(processing_tab, "🛠️ Tools")
|
293
|
+
|
294
|
+
layout.addWidget(self.tab_widget, 1)
|
295
|
+
|
296
|
+
# Status label at bottom
|
297
|
+
self.notification_label = QLabel("")
|
298
|
+
self.notification_label.setStyleSheet(
|
299
|
+
"""
|
300
|
+
QLabel {
|
301
|
+
color: #FFA500;
|
302
|
+
font-style: italic;
|
303
|
+
font-size: 9px;
|
304
|
+
background: transparent;
|
305
|
+
border: none;
|
306
|
+
padding: 4px;
|
307
|
+
}
|
308
|
+
"""
|
309
|
+
)
|
310
|
+
self.notification_label.setWordWrap(True)
|
311
|
+
layout.addWidget(self.notification_label)
|
312
|
+
|
313
|
+
def _create_mode_card(self):
|
314
|
+
"""Create the fixed mode controls card."""
|
315
|
+
mode_card = ProfessionalCard("Mode Controls")
|
316
|
+
|
317
|
+
# Mode buttons in a clean grid
|
318
|
+
buttons_layout = QVBoxLayout()
|
319
|
+
buttons_layout.setSpacing(4)
|
320
|
+
|
321
|
+
# First row: AI and Polygon
|
322
|
+
row1_layout = QHBoxLayout()
|
323
|
+
row1_layout.setSpacing(4)
|
324
|
+
|
325
|
+
self.btn_sam_mode = self._create_mode_button(
|
326
|
+
"AI", "1", "Switch to AI Mode for AI segmentation"
|
327
|
+
)
|
328
|
+
self.btn_sam_mode.setCheckable(True)
|
329
|
+
self.btn_sam_mode.setChecked(True) # Default mode
|
330
|
+
|
331
|
+
self.btn_polygon_mode = self._create_mode_button(
|
332
|
+
"Poly", "2", "Switch to Polygon Drawing Mode"
|
333
|
+
)
|
334
|
+
self.btn_polygon_mode.setCheckable(True)
|
335
|
+
|
336
|
+
row1_layout.addWidget(self.btn_sam_mode)
|
337
|
+
row1_layout.addWidget(self.btn_polygon_mode)
|
338
|
+
buttons_layout.addLayout(row1_layout)
|
339
|
+
|
340
|
+
# Second row: BBox and Selection
|
341
|
+
row2_layout = QHBoxLayout()
|
342
|
+
row2_layout.setSpacing(4)
|
343
|
+
|
344
|
+
self.btn_bbox_mode = self._create_mode_button(
|
345
|
+
"Box", "3", "Switch to Bounding Box Drawing Mode"
|
346
|
+
)
|
347
|
+
self.btn_bbox_mode.setCheckable(True)
|
348
|
+
|
349
|
+
self.btn_selection_mode = self._create_mode_button(
|
350
|
+
"Select", "E", "Toggle segment selection"
|
351
|
+
)
|
352
|
+
self.btn_selection_mode.setCheckable(True)
|
353
|
+
|
354
|
+
row2_layout.addWidget(self.btn_bbox_mode)
|
355
|
+
row2_layout.addWidget(self.btn_selection_mode)
|
356
|
+
buttons_layout.addLayout(row2_layout)
|
357
|
+
|
358
|
+
mode_card.addLayout(buttons_layout)
|
359
|
+
|
360
|
+
# Bottom utility row: Edit and Hotkeys
|
361
|
+
utility_layout = QHBoxLayout()
|
362
|
+
utility_layout.setSpacing(4)
|
363
|
+
|
364
|
+
self.btn_edit_mode = self._create_utility_button(
|
365
|
+
"Edit", "R", "Edit segments and polygons"
|
366
|
+
)
|
367
|
+
self.btn_edit_mode.setCheckable(True) # Make edit button checkable
|
368
|
+
|
369
|
+
self.btn_hotkeys = self._create_utility_button(
|
370
|
+
"⌨️ Hotkeys", "", "Configure keyboard shortcuts"
|
371
|
+
)
|
372
|
+
|
373
|
+
utility_layout.addWidget(self.btn_edit_mode)
|
374
|
+
utility_layout.addWidget(self.btn_hotkeys)
|
375
|
+
|
376
|
+
mode_card.addLayout(utility_layout)
|
377
|
+
|
378
|
+
return mode_card
|
379
|
+
|
380
|
+
def _create_mode_button(self, text, key, tooltip):
|
381
|
+
"""Create a professional mode button."""
|
382
|
+
button = QPushButton(f"{text} ({key})")
|
383
|
+
button.setToolTip(f"{tooltip} ({key})")
|
384
|
+
button.setFixedHeight(28)
|
385
|
+
button.setFixedWidth(75) # Fixed width for consistency
|
386
|
+
button.setStyleSheet(
|
387
|
+
"""
|
388
|
+
QPushButton {
|
389
|
+
background-color: rgba(60, 90, 120, 0.8);
|
390
|
+
border: 1px solid rgba(80, 110, 140, 0.8);
|
391
|
+
border-radius: 6px;
|
392
|
+
color: #E0E0E0;
|
393
|
+
font-weight: bold;
|
394
|
+
font-size: 10px;
|
395
|
+
padding: 4px 8px;
|
396
|
+
}
|
397
|
+
QPushButton:hover {
|
398
|
+
background-color: rgba(80, 110, 140, 0.9);
|
399
|
+
border-color: rgba(100, 130, 160, 0.9);
|
400
|
+
}
|
401
|
+
QPushButton:pressed {
|
402
|
+
background-color: rgba(40, 70, 100, 0.9);
|
403
|
+
}
|
404
|
+
QPushButton:checked {
|
405
|
+
background-color: rgba(120, 170, 220, 1.0);
|
406
|
+
border: 2px solid rgba(150, 200, 250, 1.0);
|
407
|
+
color: #FFFFFF;
|
408
|
+
font-weight: bold;
|
409
|
+
}
|
410
|
+
QPushButton:checked:hover {
|
411
|
+
background-color: rgba(140, 190, 240, 1.0);
|
412
|
+
border: 2px solid rgba(170, 220, 255, 1.0);
|
413
|
+
}
|
414
|
+
"""
|
415
|
+
)
|
416
|
+
return button
|
417
|
+
|
418
|
+
def _create_utility_button(self, text, key, tooltip):
|
419
|
+
"""Create a utility button with consistent styling."""
|
420
|
+
if key:
|
421
|
+
button_text = f"{text} ({key})"
|
422
|
+
tooltip_text = f"{tooltip} ({key})"
|
423
|
+
else:
|
424
|
+
button_text = text
|
425
|
+
tooltip_text = tooltip
|
426
|
+
|
427
|
+
button = QPushButton(button_text)
|
428
|
+
button.setToolTip(tooltip_text)
|
429
|
+
button.setFixedHeight(28)
|
430
|
+
button.setFixedWidth(75) # Fixed width for consistency
|
431
|
+
button.setStyleSheet(
|
432
|
+
"""
|
433
|
+
QPushButton {
|
434
|
+
background-color: rgba(70, 100, 130, 0.8);
|
435
|
+
border: 1px solid rgba(90, 120, 150, 0.8);
|
436
|
+
border-radius: 6px;
|
437
|
+
color: #E0E0E0;
|
438
|
+
font-weight: bold;
|
439
|
+
font-size: 10px;
|
440
|
+
padding: 4px 8px;
|
441
|
+
}
|
442
|
+
QPushButton:hover {
|
443
|
+
background-color: rgba(90, 120, 150, 0.9);
|
444
|
+
border-color: rgba(110, 140, 170, 0.9);
|
445
|
+
}
|
446
|
+
QPushButton:pressed {
|
447
|
+
background-color: rgba(50, 80, 110, 0.9);
|
448
|
+
}
|
449
|
+
QPushButton:checked {
|
450
|
+
background-color: rgba(120, 170, 220, 1.0);
|
451
|
+
border: 2px solid rgba(150, 200, 250, 1.0);
|
452
|
+
color: #FFFFFF;
|
453
|
+
font-weight: bold;
|
454
|
+
}
|
455
|
+
QPushButton:checked:hover {
|
456
|
+
background-color: rgba(140, 190, 240, 1.0);
|
457
|
+
border: 2px solid rgba(170, 220, 255, 1.0);
|
458
|
+
}
|
459
|
+
"""
|
460
|
+
)
|
461
|
+
return button
|
462
|
+
|
463
|
+
def _get_mode_sized_button_style(self):
|
464
|
+
"""Get styling for utility buttons that matches mode button size."""
|
465
|
+
return """
|
466
|
+
QPushButton {
|
467
|
+
background-color: rgba(80, 80, 80, 0.8);
|
468
|
+
border: 1px solid rgba(100, 100, 100, 0.6);
|
469
|
+
border-radius: 6px;
|
470
|
+
color: #E0E0E0;
|
471
|
+
font-weight: bold;
|
472
|
+
font-size: 10px;
|
473
|
+
padding: 4px 8px;
|
474
|
+
min-height: 22px;
|
475
|
+
}
|
476
|
+
QPushButton:hover {
|
477
|
+
background-color: rgba(100, 100, 100, 0.9);
|
478
|
+
border-color: rgba(120, 120, 120, 0.8);
|
479
|
+
}
|
480
|
+
QPushButton:pressed {
|
481
|
+
background-color: rgba(60, 60, 60, 0.9);
|
482
|
+
}
|
483
|
+
"""
|
484
|
+
|
485
|
+
def _get_utility_button_style(self):
|
486
|
+
"""Get styling for utility buttons."""
|
487
|
+
return """
|
488
|
+
QPushButton {
|
489
|
+
background-color: rgba(80, 80, 80, 0.8);
|
490
|
+
border: 1px solid rgba(100, 100, 100, 0.6);
|
491
|
+
border-radius: 5px;
|
492
|
+
color: #E0E0E0;
|
493
|
+
font-size: 10px;
|
494
|
+
padding: 4px 8px;
|
495
|
+
min-height: 22px;
|
496
|
+
}
|
497
|
+
QPushButton:hover {
|
498
|
+
background-color: rgba(100, 100, 100, 0.9);
|
499
|
+
}
|
500
|
+
QPushButton:pressed {
|
501
|
+
background-color: rgba(60, 60, 60, 0.9);
|
502
|
+
}
|
503
|
+
"""
|
504
|
+
|
505
|
+
def _create_ai_tab(self):
|
506
|
+
"""Create the AI & Settings tab."""
|
507
|
+
tab_widget = QWidget()
|
508
|
+
|
509
|
+
# Create scroll area for AI and settings
|
510
|
+
scroll = QScrollArea()
|
511
|
+
scroll.setWidgetResizable(True)
|
512
|
+
scroll.setHorizontalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAlwaysOff)
|
513
|
+
scroll.setVerticalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAsNeeded)
|
514
|
+
scroll.setStyleSheet(
|
515
|
+
"""
|
516
|
+
QScrollArea {
|
517
|
+
border: none;
|
518
|
+
background: transparent;
|
519
|
+
}
|
520
|
+
QScrollBar:vertical {
|
521
|
+
background-color: rgba(60, 60, 60, 0.5);
|
522
|
+
width: 8px;
|
523
|
+
border-radius: 4px;
|
524
|
+
}
|
525
|
+
QScrollBar::handle:vertical {
|
526
|
+
background-color: rgba(120, 120, 120, 0.7);
|
527
|
+
border-radius: 4px;
|
528
|
+
min-height: 20px;
|
529
|
+
}
|
530
|
+
QScrollBar::handle:vertical:hover {
|
531
|
+
background-color: rgba(140, 140, 140, 0.8);
|
532
|
+
}
|
533
|
+
"""
|
534
|
+
)
|
535
|
+
|
536
|
+
scroll_content = QWidget()
|
537
|
+
layout = QVBoxLayout(scroll_content)
|
538
|
+
layout.setContentsMargins(6, 6, 6, 6)
|
539
|
+
layout.setSpacing(8)
|
540
|
+
|
541
|
+
# AI Model Selection - collapsible
|
542
|
+
model_collapsible = SimpleCollapsible("AI Model Selection", self.model_widget)
|
543
|
+
layout.addWidget(model_collapsible)
|
544
|
+
|
545
|
+
# AI Fragment Filter - collapsible
|
546
|
+
fragment_collapsible = SimpleCollapsible(
|
547
|
+
"AI Fragment Filter", self.fragment_widget
|
548
|
+
)
|
549
|
+
layout.addWidget(fragment_collapsible)
|
550
|
+
|
551
|
+
# Application Settings - collapsible
|
552
|
+
settings_collapsible = SimpleCollapsible(
|
553
|
+
"Application Settings", self.settings_widget
|
554
|
+
)
|
555
|
+
layout.addWidget(settings_collapsible)
|
556
|
+
|
557
|
+
layout.addStretch()
|
558
|
+
|
559
|
+
scroll.setWidget(scroll_content)
|
560
|
+
|
561
|
+
tab_layout = QVBoxLayout(tab_widget)
|
562
|
+
tab_layout.setContentsMargins(0, 0, 0, 0)
|
563
|
+
tab_layout.addWidget(scroll)
|
564
|
+
|
565
|
+
return tab_widget
|
566
|
+
|
567
|
+
def _create_processing_tab(self):
|
568
|
+
"""Create the Processing & Tools tab."""
|
569
|
+
tab_widget = QWidget()
|
570
|
+
|
571
|
+
# Create scroll area for processing controls
|
572
|
+
scroll = QScrollArea()
|
573
|
+
scroll.setWidgetResizable(True)
|
574
|
+
scroll.setHorizontalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAlwaysOff)
|
575
|
+
scroll.setVerticalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAsNeeded)
|
576
|
+
scroll.setStyleSheet(
|
577
|
+
"""
|
578
|
+
QScrollArea {
|
579
|
+
border: none;
|
580
|
+
background: transparent;
|
581
|
+
}
|
582
|
+
QScrollBar:vertical {
|
583
|
+
background-color: rgba(60, 60, 60, 0.5);
|
584
|
+
width: 8px;
|
585
|
+
border-radius: 4px;
|
586
|
+
}
|
587
|
+
QScrollBar::handle:vertical {
|
588
|
+
background-color: rgba(120, 120, 120, 0.7);
|
589
|
+
border-radius: 4px;
|
590
|
+
min-height: 20px;
|
591
|
+
}
|
592
|
+
QScrollBar::handle:vertical:hover {
|
593
|
+
background-color: rgba(140, 140, 140, 0.8);
|
594
|
+
}
|
595
|
+
"""
|
596
|
+
)
|
597
|
+
|
598
|
+
scroll_content = QWidget()
|
599
|
+
layout = QVBoxLayout(scroll_content)
|
600
|
+
layout.setContentsMargins(6, 6, 6, 6)
|
601
|
+
layout.setSpacing(8)
|
602
|
+
|
603
|
+
# Border Crop - collapsible
|
604
|
+
crop_collapsible = SimpleCollapsible("Border Crop", self.crop_widget)
|
605
|
+
layout.addWidget(crop_collapsible)
|
606
|
+
|
607
|
+
# Channel Threshold - collapsible
|
608
|
+
threshold_collapsible = SimpleCollapsible(
|
609
|
+
"Channel Threshold", self.channel_threshold_widget
|
610
|
+
)
|
611
|
+
layout.addWidget(threshold_collapsible)
|
612
|
+
|
613
|
+
# FFT Threshold - collapsible
|
614
|
+
fft_threshold_collapsible = SimpleCollapsible(
|
615
|
+
"FFT Threshold", self.fft_threshold_widget
|
616
|
+
)
|
617
|
+
layout.addWidget(fft_threshold_collapsible)
|
618
|
+
|
619
|
+
# Image Adjustments - collapsible
|
620
|
+
adjustments_collapsible = SimpleCollapsible(
|
621
|
+
"Image Adjustments", self.adjustments_widget
|
622
|
+
)
|
623
|
+
layout.addWidget(adjustments_collapsible)
|
624
|
+
|
625
|
+
layout.addStretch()
|
626
|
+
|
627
|
+
scroll.setWidget(scroll_content)
|
628
|
+
|
629
|
+
tab_layout = QVBoxLayout(tab_widget)
|
630
|
+
tab_layout.setContentsMargins(0, 0, 0, 0)
|
631
|
+
tab_layout.addWidget(scroll)
|
632
|
+
|
633
|
+
return tab_widget
|
634
|
+
|
635
|
+
def _connect_signals(self):
|
636
|
+
"""Connect internal signals."""
|
637
|
+
self.btn_sam_mode.clicked.connect(self._on_sam_mode_clicked)
|
638
|
+
self.btn_polygon_mode.clicked.connect(self._on_polygon_mode_clicked)
|
639
|
+
self.btn_bbox_mode.clicked.connect(self._on_bbox_mode_clicked)
|
640
|
+
self.btn_selection_mode.clicked.connect(self._on_selection_mode_clicked)
|
641
|
+
self.btn_edit_mode.clicked.connect(self._on_edit_mode_clicked)
|
642
|
+
self.btn_hotkeys.clicked.connect(self.hotkeys_requested)
|
643
|
+
self.btn_popout.clicked.connect(self.pop_out_requested)
|
644
|
+
|
645
|
+
# Model widget signals
|
646
|
+
self.model_widget.browse_requested.connect(self.browse_models_requested)
|
647
|
+
self.model_widget.refresh_requested.connect(self.refresh_models_requested)
|
648
|
+
self.model_widget.model_selected.connect(self.model_selected)
|
649
|
+
|
650
|
+
# Settings widget signals
|
651
|
+
self.settings_widget.settings_changed.connect(self.settings_changed)
|
652
|
+
|
653
|
+
# Adjustments widget signals
|
654
|
+
self.adjustments_widget.annotation_size_changed.connect(
|
655
|
+
self.annotation_size_changed
|
656
|
+
)
|
657
|
+
self.adjustments_widget.pan_speed_changed.connect(self.pan_speed_changed)
|
658
|
+
self.adjustments_widget.join_threshold_changed.connect(
|
659
|
+
self.join_threshold_changed
|
660
|
+
)
|
661
|
+
self.adjustments_widget.brightness_changed.connect(self.brightness_changed)
|
662
|
+
self.adjustments_widget.contrast_changed.connect(self.contrast_changed)
|
663
|
+
self.adjustments_widget.gamma_changed.connect(self.gamma_changed)
|
664
|
+
self.adjustments_widget.reset_requested.connect(
|
665
|
+
self.reset_adjustments_requested
|
666
|
+
)
|
667
|
+
self.adjustments_widget.image_adjustment_changed.connect(
|
668
|
+
self.image_adjustment_changed
|
669
|
+
)
|
670
|
+
|
671
|
+
# Fragment threshold widget signals
|
672
|
+
self.fragment_widget.fragment_threshold_changed.connect(
|
673
|
+
self.fragment_threshold_changed
|
674
|
+
)
|
675
|
+
|
676
|
+
# Border crop signals
|
677
|
+
self.crop_widget.crop_draw_requested.connect(self.crop_draw_requested)
|
678
|
+
self.crop_widget.crop_clear_requested.connect(self.crop_clear_requested)
|
679
|
+
self.crop_widget.crop_applied.connect(self.crop_applied)
|
680
|
+
|
681
|
+
# Channel threshold signals
|
682
|
+
self.channel_threshold_widget.thresholdChanged.connect(
|
683
|
+
self.channel_threshold_changed
|
684
|
+
)
|
685
|
+
|
686
|
+
# FFT threshold signals
|
687
|
+
self.fft_threshold_widget.fft_threshold_changed.connect(
|
688
|
+
self.fft_threshold_changed
|
689
|
+
)
|
690
|
+
|
691
|
+
def _on_sam_mode_clicked(self):
|
692
|
+
"""Handle AI mode button click."""
|
693
|
+
self._set_active_mode_button(self.btn_sam_mode)
|
694
|
+
self.sam_mode_requested.emit()
|
695
|
+
|
696
|
+
def _on_polygon_mode_clicked(self):
|
697
|
+
"""Handle polygon mode button click."""
|
698
|
+
self._set_active_mode_button(self.btn_polygon_mode)
|
699
|
+
self.polygon_mode_requested.emit()
|
700
|
+
|
701
|
+
def _on_bbox_mode_clicked(self):
|
702
|
+
"""Handle bbox mode button click."""
|
703
|
+
self._set_active_mode_button(self.btn_bbox_mode)
|
704
|
+
self.bbox_mode_requested.emit()
|
705
|
+
|
706
|
+
def _on_selection_mode_clicked(self):
|
707
|
+
"""Handle selection mode button click."""
|
708
|
+
self._set_active_mode_button(self.btn_selection_mode)
|
709
|
+
self.selection_mode_requested.emit()
|
710
|
+
|
711
|
+
def _on_edit_mode_clicked(self):
|
712
|
+
"""Handle edit mode button click."""
|
713
|
+
# For now, emit the signal - the main window will handle polygon checking
|
714
|
+
self.edit_mode_requested.emit()
|
715
|
+
|
716
|
+
def _set_active_mode_button(self, active_button):
|
717
|
+
"""Set the active mode button and deactivate others."""
|
718
|
+
mode_buttons = [
|
719
|
+
self.btn_sam_mode,
|
720
|
+
self.btn_polygon_mode,
|
721
|
+
self.btn_bbox_mode,
|
722
|
+
self.btn_selection_mode,
|
723
|
+
]
|
724
|
+
|
725
|
+
# Clear all mode buttons
|
726
|
+
for button in mode_buttons:
|
727
|
+
button.setChecked(button == active_button if active_button else False)
|
728
|
+
|
729
|
+
# Clear edit button when setting mode buttons
|
730
|
+
if active_button and active_button != self.btn_edit_mode:
|
731
|
+
self.btn_edit_mode.setChecked(False)
|
732
|
+
|
733
|
+
def mouseDoubleClickEvent(self, event):
|
734
|
+
"""Handle double-click to expand collapsed panel."""
|
735
|
+
if (
|
736
|
+
self.width() < 50
|
737
|
+
and self.parent()
|
738
|
+
and hasattr(self.parent(), "_expand_left_panel")
|
739
|
+
):
|
740
|
+
self.parent()._expand_left_panel()
|
741
|
+
super().mouseDoubleClickEvent(event)
|
742
|
+
|
743
|
+
def show_notification(self, message: str, duration: int = 3000):
|
744
|
+
"""Show a notification message."""
|
745
|
+
self.notification_label.setText(message)
|
746
|
+
# Note: Timer should be handled by the caller
|
747
|
+
|
748
|
+
def clear_notification(self):
|
749
|
+
"""Clear the notification message."""
|
750
|
+
self.notification_label.clear()
|
751
|
+
|
752
|
+
def set_mode_text(self, mode: str):
|
753
|
+
"""Set the active mode by highlighting the corresponding button."""
|
754
|
+
# Map internal mode names to buttons
|
755
|
+
mode_buttons = {
|
756
|
+
"sam_points": self.btn_sam_mode,
|
757
|
+
"polygon": self.btn_polygon_mode,
|
758
|
+
"bbox": self.btn_bbox_mode,
|
759
|
+
"selection": self.btn_selection_mode,
|
760
|
+
"edit": self.btn_edit_mode,
|
761
|
+
}
|
762
|
+
|
763
|
+
active_button = mode_buttons.get(mode)
|
764
|
+
if active_button:
|
765
|
+
# Clear all mode buttons first
|
766
|
+
self._set_active_mode_button(None)
|
767
|
+
# Set edit button separately if it's edit mode
|
768
|
+
if mode == "edit":
|
769
|
+
self.btn_edit_mode.setChecked(True)
|
770
|
+
else:
|
771
|
+
self._set_active_mode_button(active_button)
|
772
|
+
|
773
|
+
def set_edit_mode_active(self, active: bool):
|
774
|
+
"""Set edit mode button as active or inactive."""
|
775
|
+
if active:
|
776
|
+
# Clear all mode buttons and set edit as active
|
777
|
+
self._set_active_mode_button(None)
|
778
|
+
self.btn_edit_mode.setChecked(True)
|
779
|
+
else:
|
780
|
+
self.btn_edit_mode.setChecked(False)
|
781
|
+
|
782
|
+
# Delegate methods for sub-widgets
|
783
|
+
def populate_models(self, models):
|
784
|
+
"""Populate the models combo box."""
|
785
|
+
self.model_widget.populate_models(models)
|
786
|
+
|
787
|
+
def set_current_model(self, model_name):
|
788
|
+
"""Set the current model display."""
|
789
|
+
self.model_widget.set_current_model(model_name)
|
790
|
+
|
791
|
+
def get_settings(self):
|
792
|
+
"""Get current settings from the settings widget."""
|
793
|
+
return self.settings_widget.get_settings()
|
794
|
+
|
795
|
+
def set_settings(self, settings):
|
796
|
+
"""Set settings in the settings widget."""
|
797
|
+
self.settings_widget.set_settings(settings)
|
798
|
+
|
799
|
+
def get_annotation_size(self):
|
800
|
+
"""Get current annotation size."""
|
801
|
+
return self.adjustments_widget.get_annotation_size()
|
802
|
+
|
803
|
+
def set_annotation_size(self, value):
|
804
|
+
"""Set annotation size."""
|
805
|
+
self.adjustments_widget.set_annotation_size(value)
|
806
|
+
|
807
|
+
def set_pan_speed(self, value):
|
808
|
+
"""Set pan speed."""
|
809
|
+
self.adjustments_widget.set_pan_speed(value)
|
810
|
+
|
811
|
+
def set_join_threshold(self, value):
|
812
|
+
"""Set join threshold."""
|
813
|
+
self.adjustments_widget.set_join_threshold(value)
|
814
|
+
|
815
|
+
def set_fragment_threshold(self, value):
|
816
|
+
"""Set fragment threshold."""
|
817
|
+
self.fragment_widget.set_fragment_threshold(value)
|
818
|
+
|
819
|
+
def set_brightness(self, value):
|
820
|
+
"""Set brightness."""
|
821
|
+
self.adjustments_widget.set_brightness(value)
|
822
|
+
|
823
|
+
def set_contrast(self, value):
|
824
|
+
"""Set contrast."""
|
825
|
+
self.adjustments_widget.set_contrast(value)
|
826
|
+
|
827
|
+
def set_gamma(self, value):
|
828
|
+
"""Set gamma."""
|
829
|
+
self.adjustments_widget.set_gamma(value)
|
830
|
+
|
831
|
+
def set_sam_mode_enabled(self, enabled: bool):
|
832
|
+
"""Enable or disable the SAM mode button."""
|
833
|
+
self.btn_sam_mode.setEnabled(enabled)
|
834
|
+
if not enabled:
|
835
|
+
self.btn_sam_mode.setToolTip("AI Mode (SAM model not available)")
|
836
|
+
else:
|
837
|
+
self.btn_sam_mode.setToolTip("Switch to AI Mode for AI segmentation (1)")
|
838
|
+
|
839
|
+
def set_popout_mode(self, is_popped_out: bool):
|
840
|
+
"""Update the pop-out button based on panel state."""
|
841
|
+
if is_popped_out:
|
842
|
+
self.btn_popout.setText("⇤")
|
843
|
+
self.btn_popout.setToolTip("Return panel to main window")
|
844
|
+
else:
|
845
|
+
self.btn_popout.setText("⋯")
|
846
|
+
self.btn_popout.setToolTip("Pop out panel to separate window")
|
847
|
+
|
848
|
+
# Border crop delegate methods
|
849
|
+
def set_crop_coordinates(self, x1, y1, x2, y2):
|
850
|
+
"""Set crop coordinates in the crop widget."""
|
851
|
+
self.crop_widget.set_crop_coordinates(x1, y1, x2, y2)
|
852
|
+
|
853
|
+
def clear_crop_coordinates(self):
|
854
|
+
"""Clear crop coordinates."""
|
855
|
+
self.crop_widget.clear_crop_coordinates()
|
856
|
+
|
857
|
+
def get_crop_coordinates(self):
|
858
|
+
"""Get current crop coordinates."""
|
859
|
+
return self.crop_widget.get_crop_coordinates()
|
860
|
+
|
861
|
+
def has_crop(self):
|
862
|
+
"""Check if crop coordinates are set."""
|
863
|
+
return self.crop_widget.has_crop()
|
864
|
+
|
865
|
+
def set_crop_status(self, message):
|
866
|
+
"""Set crop status message."""
|
867
|
+
self.crop_widget.set_status(message)
|
868
|
+
|
869
|
+
# Channel threshold delegate methods
|
870
|
+
def update_channel_threshold_for_image(self, image_array):
|
871
|
+
"""Update channel threshold widget for new image."""
|
872
|
+
self.channel_threshold_widget.update_for_image(image_array)
|
873
|
+
|
874
|
+
def get_channel_threshold_widget(self):
|
875
|
+
"""Get the channel threshold widget."""
|
876
|
+
return self.channel_threshold_widget
|
877
|
+
|
878
|
+
# FFT threshold delegate methods
|
879
|
+
def update_fft_threshold_for_image(self, image_array):
|
880
|
+
"""Update FFT threshold widget for new image."""
|
881
|
+
self.fft_threshold_widget.update_fft_threshold_for_image(image_array)
|
882
|
+
|
883
|
+
def get_fft_threshold_widget(self):
|
884
|
+
"""Get the FFT threshold widget."""
|
885
|
+
return self.fft_threshold_widget
|