lazylabel-gui 1.1.1__py3-none-any.whl → 1.1.3__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.
Files changed (35) hide show
  1. lazylabel/__init__.py +1 -1
  2. lazylabel/config/__init__.py +3 -3
  3. lazylabel/config/hotkeys.py +96 -58
  4. lazylabel/config/paths.py +8 -9
  5. lazylabel/config/settings.py +15 -16
  6. lazylabel/core/__init__.py +3 -3
  7. lazylabel/core/file_manager.py +49 -33
  8. lazylabel/core/model_manager.py +9 -11
  9. lazylabel/core/segment_manager.py +21 -22
  10. lazylabel/main.py +1 -0
  11. lazylabel/models/__init__.py +1 -1
  12. lazylabel/models/sam_model.py +24 -19
  13. lazylabel/ui/__init__.py +3 -3
  14. lazylabel/ui/control_panel.py +21 -19
  15. lazylabel/ui/editable_vertex.py +16 -3
  16. lazylabel/ui/hotkey_dialog.py +125 -93
  17. lazylabel/ui/hoverable_polygon_item.py +1 -2
  18. lazylabel/ui/main_window.py +290 -49
  19. lazylabel/ui/photo_viewer.py +4 -7
  20. lazylabel/ui/reorderable_class_table.py +2 -3
  21. lazylabel/ui/right_panel.py +15 -16
  22. lazylabel/ui/widgets/__init__.py +1 -1
  23. lazylabel/ui/widgets/adjustments_widget.py +22 -21
  24. lazylabel/ui/widgets/model_selection_widget.py +28 -21
  25. lazylabel/ui/widgets/settings_widget.py +35 -28
  26. lazylabel/ui/widgets/status_bar.py +2 -2
  27. lazylabel/utils/__init__.py +2 -2
  28. lazylabel/utils/custom_file_system_model.py +3 -2
  29. {lazylabel_gui-1.1.1.dist-info → lazylabel_gui-1.1.3.dist-info}/METADATA +48 -2
  30. lazylabel_gui-1.1.3.dist-info/RECORD +37 -0
  31. lazylabel_gui-1.1.1.dist-info/RECORD +0 -37
  32. {lazylabel_gui-1.1.1.dist-info → lazylabel_gui-1.1.3.dist-info}/WHEEL +0 -0
  33. {lazylabel_gui-1.1.1.dist-info → lazylabel_gui-1.1.3.dist-info}/entry_points.txt +0 -0
  34. {lazylabel_gui-1.1.1.dist-info → lazylabel_gui-1.1.3.dist-info}/licenses/LICENSE +0 -0
  35. {lazylabel_gui-1.1.1.dist-info → lazylabel_gui-1.1.3.dist-info}/top_level.txt +0 -0
lazylabel/__init__.py CHANGED
@@ -6,4 +6,4 @@ __email__ = "deniz.n.cakan@gmail.com"
6
6
 
7
7
  from .main import main
8
8
 
9
- __all__ = ['main']
9
+ __all__ = ["main"]
@@ -1,7 +1,7 @@
1
1
  """Configuration management for LazyLabel."""
2
2
 
3
- from .settings import Settings, DEFAULT_SETTINGS
3
+ from .hotkeys import HotkeyAction, HotkeyManager
4
4
  from .paths import Paths
5
- from .hotkeys import HotkeyManager, HotkeyAction
5
+ from .settings import DEFAULT_SETTINGS, Settings
6
6
 
7
- __all__ = ['Settings', 'DEFAULT_SETTINGS', 'Paths', 'HotkeyManager', 'HotkeyAction']
7
+ __all__ = ["Settings", "DEFAULT_SETTINGS", "Paths", "HotkeyManager", "HotkeyAction"]
@@ -2,89 +2,127 @@
2
2
 
3
3
  import json
4
4
  import os
5
- from dataclasses import dataclass, asdict
6
- from typing import Dict, Optional, List, Tuple
7
- from PyQt6.QtCore import Qt
5
+ from dataclasses import dataclass
6
+
8
7
  from PyQt6.QtGui import QKeySequence
9
8
 
10
9
 
11
10
  @dataclass
12
11
  class HotkeyAction:
13
12
  """Represents a hotkey action with primary and secondary keys."""
13
+
14
14
  name: str
15
15
  description: str
16
16
  primary_key: str
17
- secondary_key: Optional[str] = None
17
+ secondary_key: str | None = None
18
18
  category: str = "General"
19
19
  mouse_related: bool = False # Cannot be reassigned if True
20
20
 
21
21
 
22
22
  class HotkeyManager:
23
23
  """Manages application hotkeys with persistence."""
24
-
24
+
25
25
  def __init__(self, config_dir: str):
26
26
  self.config_dir = config_dir
27
27
  self.hotkeys_file = os.path.join(config_dir, "hotkeys.json")
28
- self.actions: Dict[str, HotkeyAction] = {}
28
+ self.actions: dict[str, HotkeyAction] = {}
29
29
  self._initialize_default_hotkeys()
30
30
  self.load_hotkeys()
31
-
31
+
32
32
  def _initialize_default_hotkeys(self):
33
33
  """Initialize default hotkey mappings."""
34
34
  default_hotkeys = [
35
35
  # Navigation
36
- HotkeyAction("load_next_image", "Load Next Image", "Right", category="Navigation"),
37
- HotkeyAction("load_previous_image", "Load Previous Image", "Left", category="Navigation"),
38
- HotkeyAction("fit_view", "Fit View", "Period", category="Navigation"),
39
-
36
+ HotkeyAction(
37
+ "load_next_image", "Load Next Image", "Right", category="Navigation"
38
+ ),
39
+ HotkeyAction(
40
+ "load_previous_image",
41
+ "Load Previous Image",
42
+ "Left",
43
+ category="Navigation",
44
+ ),
45
+ HotkeyAction("fit_view", "Fit View", ".", category="Navigation"),
40
46
  # Modes
41
47
  HotkeyAction("sam_mode", "Point Mode (SAM)", "1", category="Modes"),
42
48
  HotkeyAction("polygon_mode", "Polygon Mode", "2", category="Modes"),
43
49
  HotkeyAction("selection_mode", "Selection Mode", "E", category="Modes"),
44
50
  HotkeyAction("pan_mode", "Pan Mode", "Q", category="Modes"),
45
51
  HotkeyAction("edit_mode", "Edit Mode", "R", category="Modes"),
46
-
47
52
  # Actions
48
- HotkeyAction("clear_points", "Clear Points/Vertices", "C", category="Actions"),
49
- HotkeyAction("save_segment", "Save Current Segment", "Space", category="Actions"),
53
+ HotkeyAction(
54
+ "clear_points", "Clear Points/Vertices", "C", category="Actions"
55
+ ),
56
+ HotkeyAction(
57
+ "save_segment", "Save Current Segment", "Space", category="Actions"
58
+ ),
50
59
  HotkeyAction("save_output", "Save Output", "Return", category="Actions"),
51
- HotkeyAction("save_output_alt", "Save Output (Alt)", "Enter", category="Actions"),
60
+ HotkeyAction(
61
+ "save_output_alt", "Save Output (Alt)", "Enter", category="Actions"
62
+ ),
52
63
  HotkeyAction("undo", "Undo Last Action", "Ctrl+Z", category="Actions"),
53
- HotkeyAction("escape", "Cancel/Clear Selection", "Escape", category="Actions"),
54
-
64
+ HotkeyAction(
65
+ "redo", "Redo Last Action", "Ctrl+Y", "Ctrl+Shift+Z", category="Actions"
66
+ ),
67
+ HotkeyAction(
68
+ "escape", "Cancel/Clear Selection", "Escape", category="Actions"
69
+ ),
55
70
  # Segments
56
- HotkeyAction("merge_segments", "Merge Selected Segments", "M", category="Segments"),
57
- HotkeyAction("delete_segments", "Delete Selected Segments", "V", category="Segments"),
58
- HotkeyAction("delete_segments_alt", "Delete Selected Segments (Alt)", "Backspace", category="Segments"),
59
- HotkeyAction("select_all", "Select All Segments", "Ctrl+A", category="Segments"),
60
-
71
+ HotkeyAction(
72
+ "merge_segments", "Merge Selected Segments", "M", category="Segments"
73
+ ),
74
+ HotkeyAction(
75
+ "delete_segments", "Delete Selected Segments", "V", category="Segments"
76
+ ),
77
+ HotkeyAction(
78
+ "delete_segments_alt",
79
+ "Delete Selected Segments (Alt)",
80
+ "Backspace",
81
+ category="Segments",
82
+ ),
83
+ HotkeyAction(
84
+ "select_all", "Select All Segments", "Ctrl+A", category="Segments"
85
+ ),
61
86
  # View
62
87
  HotkeyAction("zoom_in", "Zoom In", "Ctrl+Plus", category="View"),
63
88
  HotkeyAction("zoom_out", "Zoom Out", "Ctrl+Minus", category="View"),
64
-
65
89
  # Movement (WASD)
66
90
  HotkeyAction("pan_up", "Pan Up", "W", category="Movement"),
67
91
  HotkeyAction("pan_down", "Pan Down", "S", category="Movement"),
68
92
  HotkeyAction("pan_left", "Pan Left", "A", category="Movement"),
69
93
  HotkeyAction("pan_right", "Pan Right", "D", category="Movement"),
70
-
71
94
  # Mouse-related (cannot be reassigned)
72
- HotkeyAction("left_click", "Add Positive Point / Select", "Left Click",
73
- category="Mouse", mouse_related=True),
74
- HotkeyAction("right_click", "Add Negative Point", "Right Click",
75
- category="Mouse", mouse_related=True),
76
- HotkeyAction("mouse_drag", "Drag/Pan", "Mouse Drag",
77
- category="Mouse", mouse_related=True),
95
+ HotkeyAction(
96
+ "left_click",
97
+ "Add Positive Point / Select",
98
+ "Left Click",
99
+ category="Mouse",
100
+ mouse_related=True,
101
+ ),
102
+ HotkeyAction(
103
+ "right_click",
104
+ "Add Negative Point",
105
+ "Right Click",
106
+ category="Mouse",
107
+ mouse_related=True,
108
+ ),
109
+ HotkeyAction(
110
+ "mouse_drag",
111
+ "Drag/Pan",
112
+ "Mouse Drag",
113
+ category="Mouse",
114
+ mouse_related=True,
115
+ ),
78
116
  ]
79
-
117
+
80
118
  for action in default_hotkeys:
81
119
  self.actions[action.name] = action
82
-
83
- def get_action(self, action_name: str) -> Optional[HotkeyAction]:
120
+
121
+ def get_action(self, action_name: str) -> HotkeyAction | None:
84
122
  """Get hotkey action by name."""
85
123
  return self.actions.get(action_name)
86
-
87
- def get_actions_by_category(self) -> Dict[str, List[HotkeyAction]]:
124
+
125
+ def get_actions_by_category(self) -> dict[str, list[HotkeyAction]]:
88
126
  """Get actions grouped by category."""
89
127
  categories = {}
90
128
  for action in self.actions.values():
@@ -92,29 +130,29 @@ class HotkeyManager:
92
130
  categories[action.category] = []
93
131
  categories[action.category].append(action)
94
132
  return categories
95
-
133
+
96
134
  def set_primary_key(self, action_name: str, key: str) -> bool:
97
135
  """Set primary key for an action."""
98
136
  if action_name in self.actions and not self.actions[action_name].mouse_related:
99
137
  self.actions[action_name].primary_key = key
100
138
  return True
101
139
  return False
102
-
103
- def set_secondary_key(self, action_name: str, key: Optional[str]) -> bool:
140
+
141
+ def set_secondary_key(self, action_name: str, key: str | None) -> bool:
104
142
  """Set secondary key for an action."""
105
143
  if action_name in self.actions and not self.actions[action_name].mouse_related:
106
144
  self.actions[action_name].secondary_key = key
107
145
  return True
108
146
  return False
109
-
110
- def get_key_for_action(self, action_name: str) -> Tuple[Optional[str], Optional[str]]:
147
+
148
+ def get_key_for_action(self, action_name: str) -> tuple[str | None, str | None]:
111
149
  """Get primary and secondary keys for an action."""
112
150
  action = self.actions.get(action_name)
113
151
  if action:
114
152
  return action.primary_key, action.secondary_key
115
153
  return None, None
116
-
117
- def is_key_in_use(self, key: str, exclude_action: str = None) -> Optional[str]:
154
+
155
+ def is_key_in_use(self, key: str, exclude_action: str = None) -> str | None:
118
156
  """Check if a key is already in use by another action."""
119
157
  for name, action in self.actions.items():
120
158
  if name == exclude_action:
@@ -122,48 +160,48 @@ class HotkeyManager:
122
160
  if action.primary_key == key or action.secondary_key == key:
123
161
  return name
124
162
  return None
125
-
163
+
126
164
  def reset_to_defaults(self):
127
165
  """Reset all hotkeys to default values."""
128
166
  self._initialize_default_hotkeys()
129
-
167
+
130
168
  def save_hotkeys(self):
131
169
  """Save hotkeys to file."""
132
170
  os.makedirs(self.config_dir, exist_ok=True)
133
-
171
+
134
172
  # Convert to serializable format
135
173
  data = {}
136
174
  for name, action in self.actions.items():
137
175
  if not action.mouse_related: # Don't save mouse-related actions
138
176
  data[name] = {
139
- 'primary_key': action.primary_key,
140
- 'secondary_key': action.secondary_key
177
+ "primary_key": action.primary_key,
178
+ "secondary_key": action.secondary_key,
141
179
  }
142
-
143
- with open(self.hotkeys_file, 'w') as f:
180
+
181
+ with open(self.hotkeys_file, "w") as f:
144
182
  json.dump(data, f, indent=4)
145
-
183
+
146
184
  def load_hotkeys(self):
147
185
  """Load hotkeys from file."""
148
186
  if not os.path.exists(self.hotkeys_file):
149
187
  return
150
-
188
+
151
189
  try:
152
- with open(self.hotkeys_file, 'r') as f:
190
+ with open(self.hotkeys_file) as f:
153
191
  data = json.load(f)
154
-
192
+
155
193
  for name, keys in data.items():
156
194
  if name in self.actions and not self.actions[name].mouse_related:
157
- self.actions[name].primary_key = keys.get('primary_key', '')
158
- self.actions[name].secondary_key = keys.get('secondary_key')
195
+ self.actions[name].primary_key = keys.get("primary_key", "")
196
+ self.actions[name].secondary_key = keys.get("secondary_key")
159
197
  except (json.JSONDecodeError, KeyError, FileNotFoundError):
160
198
  # If loading fails, keep defaults
161
199
  pass
162
-
200
+
163
201
  def key_sequence_to_string(self, key_sequence: QKeySequence) -> str:
164
202
  """Convert QKeySequence to string representation."""
165
203
  return key_sequence.toString()
166
-
204
+
167
205
  def string_to_key_sequence(self, key_string: str) -> QKeySequence:
168
206
  """Convert string to QKeySequence."""
169
- return QKeySequence(key_string)
207
+ return QKeySequence(key_string)
lazylabel/config/paths.py CHANGED
@@ -1,41 +1,40 @@
1
1
  """Path management for LazyLabel."""
2
2
 
3
- import os
4
3
  from pathlib import Path
5
4
 
6
5
 
7
6
  class Paths:
8
7
  """Centralized path management."""
9
-
8
+
10
9
  def __init__(self):
11
10
  self.app_dir = Path(__file__).parent.parent
12
11
  self.models_dir = self.app_dir / "models"
13
12
  self.config_dir = Path.home() / ".config" / "lazylabel"
14
13
  self.cache_dir = Path.home() / ".cache" / "lazylabel"
15
-
14
+
16
15
  # Ensure directories exist
17
16
  self.models_dir.mkdir(exist_ok=True)
18
17
  self.config_dir.mkdir(parents=True, exist_ok=True)
19
-
18
+
20
19
  @property
21
20
  def settings_file(self) -> Path:
22
21
  """Path to settings file."""
23
22
  return self.config_dir / "settings.json"
24
-
23
+
25
24
  @property
26
25
  def demo_pictures_dir(self) -> Path:
27
26
  """Path to demo pictures directory."""
28
27
  return self.app_dir / "demo_pictures"
29
-
28
+
30
29
  @property
31
30
  def logo_path(self) -> Path:
32
31
  """Path to application logo."""
33
32
  return self.demo_pictures_dir / "logo2.png"
34
-
33
+
35
34
  def get_model_path(self, filename: str) -> Path:
36
35
  """Get path for a model file."""
37
36
  return self.models_dir / filename
38
-
37
+
39
38
  def get_old_cache_model_path(self, filename: str) -> Path:
40
39
  """Get path for model in old cache location."""
41
- return self.cache_dir / filename
40
+ return self.cache_dir / filename
@@ -1,60 +1,59 @@
1
1
  """Application settings and configuration."""
2
2
 
3
- import os
4
- from dataclasses import dataclass, asdict
5
- from typing import Dict, Any
6
3
  import json
4
+ import os
5
+ from dataclasses import asdict, dataclass
7
6
 
8
7
 
9
8
  @dataclass
10
9
  class Settings:
11
10
  """Application settings with defaults."""
12
-
11
+
13
12
  # UI Settings
14
13
  window_width: int = 1600
15
14
  window_height: int = 900
16
15
  left_panel_width: int = 250
17
16
  right_panel_width: int = 350
18
-
17
+
19
18
  # Annotation Settings
20
19
  point_radius: float = 0.3
21
20
  line_thickness: float = 0.5
22
21
  pan_multiplier: float = 1.0
23
22
  polygon_join_threshold: int = 2
24
-
23
+
25
24
  # Model Settings
26
25
  default_model_type: str = "vit_h"
27
26
  default_model_filename: str = "sam_vit_h_4b8939.pth"
28
-
27
+
29
28
  # Save Settings
30
29
  auto_save: bool = True
31
30
  save_npz: bool = True
32
31
  save_txt: bool = True
33
32
  save_class_aliases: bool = False
34
33
  yolo_use_alias: bool = True
35
-
34
+
36
35
  # UI State
37
36
  annotation_size_multiplier: float = 1.0
38
-
37
+
39
38
  def save_to_file(self, filepath: str) -> None:
40
39
  """Save settings to JSON file."""
41
40
  os.makedirs(os.path.dirname(filepath), exist_ok=True)
42
- with open(filepath, 'w') as f:
41
+ with open(filepath, "w") as f:
43
42
  json.dump(asdict(self), f, indent=4)
44
-
43
+
45
44
  @classmethod
46
- def load_from_file(cls, filepath: str) -> 'Settings':
45
+ def load_from_file(cls, filepath: str) -> "Settings":
47
46
  """Load settings from JSON file."""
48
47
  if not os.path.exists(filepath):
49
48
  return cls()
50
-
49
+
51
50
  try:
52
- with open(filepath, 'r') as f:
51
+ with open(filepath) as f:
53
52
  data = json.load(f)
54
53
  return cls(**data)
55
54
  except (json.JSONDecodeError, TypeError):
56
55
  return cls()
57
-
56
+
58
57
  def update(self, **kwargs) -> None:
59
58
  """Update settings with new values."""
60
59
  for key, value in kwargs.items():
@@ -63,4 +62,4 @@ class Settings:
63
62
 
64
63
 
65
64
  # Default settings instance
66
- DEFAULT_SETTINGS = Settings()
65
+ DEFAULT_SETTINGS = Settings()
@@ -1,7 +1,7 @@
1
1
  """Core business logic for LazyLabel."""
2
2
 
3
- from .segment_manager import SegmentManager
4
- from .model_manager import ModelManager
5
3
  from .file_manager import FileManager
4
+ from .model_manager import ModelManager
5
+ from .segment_manager import SegmentManager
6
6
 
7
- __all__ = ['SegmentManager', 'ModelManager', 'FileManager']
7
+ __all__ = ["SegmentManager", "ModelManager", "FileManager"]
@@ -1,45 +1,55 @@
1
1
  """File management functionality."""
2
2
 
3
- import os
4
3
  import json
5
- import numpy as np
4
+ import os
5
+
6
6
  import cv2
7
- from typing import List, Dict, Any, Optional, Tuple
8
- from pathlib import Path
7
+ import numpy as np
9
8
 
10
9
  from .segment_manager import SegmentManager
11
10
 
12
11
 
13
12
  class FileManager:
14
13
  """Manages file operations for saving and loading."""
15
-
14
+
16
15
  def __init__(self, segment_manager: SegmentManager):
17
16
  self.segment_manager = segment_manager
18
-
19
- def save_npz(self, image_path: str, image_size: Tuple[int, int], class_order: List[int]) -> str:
17
+
18
+ def save_npz(
19
+ self, image_path: str, image_size: tuple[int, int], class_order: list[int]
20
+ ) -> str:
20
21
  """Save segments as NPZ file."""
21
- final_mask_tensor = self.segment_manager.create_final_mask_tensor(image_size, class_order)
22
+ final_mask_tensor = self.segment_manager.create_final_mask_tensor(
23
+ image_size, class_order
24
+ )
22
25
  npz_path = os.path.splitext(image_path)[0] + ".npz"
23
26
  np.savez_compressed(npz_path, mask=final_mask_tensor.astype(np.uint8))
24
27
  return npz_path
25
-
26
- def save_yolo_txt(self, image_path: str, image_size: Tuple[int, int],
27
- class_order: List[int], class_labels: List[str]) -> Optional[str]:
28
+
29
+ def save_yolo_txt(
30
+ self,
31
+ image_path: str,
32
+ image_size: tuple[int, int],
33
+ class_order: list[int],
34
+ class_labels: list[str],
35
+ ) -> str | None:
28
36
  """Save segments as YOLO format TXT file."""
29
- final_mask_tensor = self.segment_manager.create_final_mask_tensor(image_size, class_order)
37
+ final_mask_tensor = self.segment_manager.create_final_mask_tensor(
38
+ image_size, class_order
39
+ )
30
40
  output_path = os.path.splitext(image_path)[0] + ".txt"
31
41
  h, w = image_size
32
-
42
+
33
43
  yolo_annotations = []
34
44
  for channel in range(final_mask_tensor.shape[2]):
35
45
  single_channel_image = final_mask_tensor[:, :, channel]
36
46
  if not np.any(single_channel_image):
37
47
  continue
38
-
48
+
39
49
  contours, _ = cv2.findContours(
40
50
  single_channel_image, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE
41
51
  )
42
-
52
+
43
53
  class_label = class_labels[channel]
44
54
  for contour in contours:
45
55
  x, y, width, height = cv2.boundingRect(contour)
@@ -49,37 +59,41 @@ class FileManager:
49
59
  normalized_height = height / h
50
60
  yolo_entry = f"{class_label} {center_x} {center_y} {normalized_width} {normalized_height}"
51
61
  yolo_annotations.append(yolo_entry)
52
-
62
+
53
63
  if not yolo_annotations:
54
64
  return None
55
-
65
+
56
66
  os.makedirs(os.path.dirname(output_path), exist_ok=True)
57
67
  with open(output_path, "w") as file:
58
68
  for annotation in yolo_annotations:
59
69
  file.write(annotation + "\n")
60
-
70
+
61
71
  return output_path
62
-
72
+
63
73
  def save_class_aliases(self, image_path: str) -> str:
64
74
  """Save class aliases as JSON file."""
65
75
  aliases_path = os.path.splitext(image_path)[0] + ".json"
66
- aliases_to_save = {str(k): v for k, v in self.segment_manager.class_aliases.items()}
76
+ aliases_to_save = {
77
+ str(k): v for k, v in self.segment_manager.class_aliases.items()
78
+ }
67
79
  with open(aliases_path, "w") as f:
68
80
  json.dump(aliases_to_save, f, indent=4)
69
81
  return aliases_path
70
-
82
+
71
83
  def load_class_aliases(self, image_path: str) -> None:
72
84
  """Load class aliases from JSON file."""
73
85
  json_path = os.path.splitext(image_path)[0] + ".json"
74
86
  if os.path.exists(json_path):
75
87
  try:
76
- with open(json_path, "r") as f:
88
+ with open(json_path) as f:
77
89
  loaded_aliases = json.load(f)
78
- self.segment_manager.class_aliases = {int(k): v for k, v in loaded_aliases.items()}
90
+ self.segment_manager.class_aliases = {
91
+ int(k): v for k, v in loaded_aliases.items()
92
+ }
79
93
  except (json.JSONDecodeError, ValueError) as e:
80
94
  print(f"Error loading class aliases from {json_path}: {e}")
81
95
  self.segment_manager.class_aliases.clear()
82
-
96
+
83
97
  def load_existing_mask(self, image_path: str) -> None:
84
98
  """Load existing mask from NPZ file."""
85
99
  npz_path = os.path.splitext(image_path)[0] + ".npz"
@@ -89,18 +103,20 @@ class FileManager:
89
103
  mask_data = data["mask"]
90
104
  if mask_data.ndim == 2:
91
105
  mask_data = np.expand_dims(mask_data, axis=-1)
92
-
106
+
93
107
  num_classes = mask_data.shape[2]
94
108
  for i in range(num_classes):
95
109
  class_mask = mask_data[:, :, i].astype(bool)
96
110
  if np.any(class_mask):
97
- self.segment_manager.add_segment({
98
- "mask": class_mask,
99
- "type": "Loaded",
100
- "vertices": None,
101
- "class_id": i,
102
- })
103
-
111
+ self.segment_manager.add_segment(
112
+ {
113
+ "mask": class_mask,
114
+ "type": "Loaded",
115
+ "vertices": None,
116
+ "class_id": i,
117
+ }
118
+ )
119
+
104
120
  def is_image_file(self, filepath: str) -> bool:
105
121
  """Check if file is a supported image format."""
106
- return filepath.lower().endswith((".png", ".jpg", ".jpeg", ".tiff", ".tif"))
122
+ return filepath.lower().endswith((".png", ".jpg", ".jpeg", ".tiff", ".tif"))
@@ -1,12 +1,10 @@
1
1
  """Model management functionality."""
2
2
 
3
3
  import os
4
- import glob
5
- from typing import List, Tuple, Optional, Callable
6
- from pathlib import Path
4
+ from collections.abc import Callable
7
5
 
8
- from ..models.sam_model import SamModel
9
6
  from ..config import Paths
7
+ from ..models.sam_model import SamModel
10
8
 
11
9
 
12
10
  class ModelManager:
@@ -14,11 +12,11 @@ class ModelManager:
14
12
 
15
13
  def __init__(self, paths: Paths):
16
14
  self.paths = paths
17
- self.sam_model: Optional[SamModel] = None
18
- self.current_models_folder: Optional[str] = None
19
- self.on_model_changed: Optional[Callable[[str], None]] = None
15
+ self.sam_model: SamModel | None = None
16
+ self.current_models_folder: str | None = None
17
+ self.on_model_changed: Callable[[str], None] | None = None
20
18
 
21
- def initialize_default_model(self, model_type: str = "vit_h") -> Optional[SamModel]:
19
+ def initialize_default_model(self, model_type: str = "vit_h") -> SamModel | None:
22
20
  """Initialize the default SAM model.
23
21
 
24
22
  Returns:
@@ -34,14 +32,14 @@ class ModelManager:
34
32
  self.sam_model = None
35
33
  return None
36
34
 
37
- def get_available_models(self, folder_path: str) -> List[Tuple[str, str]]:
35
+ def get_available_models(self, folder_path: str) -> list[tuple[str, str]]:
38
36
  """Get list of available .pth models in folder.
39
37
 
40
38
  Returns:
41
39
  List of (display_name, full_path) tuples
42
40
  """
43
41
  pth_files = []
44
- for root, dirs, files in os.walk(folder_path):
42
+ for root, _dirs, files in os.walk(folder_path):
45
43
  for file in files:
46
44
  if file.lower().endswith(".pth"):
47
45
  full_path = os.path.join(root, file)
@@ -86,7 +84,7 @@ class ModelManager:
86
84
  """Set the current models folder."""
87
85
  self.current_models_folder = folder_path
88
86
 
89
- def get_models_folder(self) -> Optional[str]:
87
+ def get_models_folder(self) -> str | None:
90
88
  """Get the current models folder."""
91
89
  return self.current_models_folder
92
90