driftlessmap 1.1.0__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 (210) hide show
  1. driftlessmap/__init__.py +16 -0
  2. driftlessmap/__main__.py +5 -0
  3. driftlessmap/about.py +24 -0
  4. driftlessmap/allen_downloader.py +908 -0
  5. driftlessmap/app.py +8085 -0
  6. driftlessmap/atlas_downloader.py +370 -0
  7. driftlessmap/atlas_loader.py +526 -0
  8. driftlessmap/atlas_processor.py +784 -0
  9. driftlessmap/atlas_transform.py +111 -0
  10. driftlessmap/atlas_view.py +1435 -0
  11. driftlessmap/cell_detection.py +27 -0
  12. driftlessmap/coordinate_validation.py +19 -0
  13. driftlessmap/czi_reader.py +236 -0
  14. driftlessmap/data/WHS_atlas_labels.pkl +0 -0
  15. driftlessmap/data/allen_mice_atlas_labels.pkl +0 -0
  16. driftlessmap/data/atlas_labels.pkl +0 -0
  17. driftlessmap/data/query.csv +1328 -0
  18. driftlessmap/download_utils.py +85 -0
  19. driftlessmap/herbs.png +0 -0
  20. driftlessmap/icons/backward.svg +15 -0
  21. driftlessmap/icons/dot.svg +11 -0
  22. driftlessmap/icons/down-arrow.svg +10 -0
  23. driftlessmap/icons/fast_backward.svg +14 -0
  24. driftlessmap/icons/fast_forward.svg +14 -0
  25. driftlessmap/icons/forward.svg +14 -0
  26. driftlessmap/icons/layers/add.png +0 -0
  27. driftlessmap/icons/layers/ai.png +0 -0
  28. driftlessmap/icons/layers/eye_off.png +0 -0
  29. driftlessmap/icons/layers/eye_on.png +0 -0
  30. driftlessmap/icons/layers/eye_white.png +0 -0
  31. driftlessmap/icons/layers/hi.png +0 -0
  32. driftlessmap/icons/layers/trash.png +0 -0
  33. driftlessmap/icons/object.svg +22 -0
  34. driftlessmap/icons/sidebar/add.png +0 -0
  35. driftlessmap/icons/sidebar/atlascontrol.png +0 -0
  36. driftlessmap/icons/sidebar/atlascontrol.svg +32 -0
  37. driftlessmap/icons/sidebar/bnd.svg +14 -0
  38. driftlessmap/icons/sidebar/c_section.png +0 -0
  39. driftlessmap/icons/sidebar/c_section2.png +0 -0
  40. driftlessmap/icons/sidebar/cell.svg +24 -0
  41. driftlessmap/icons/sidebar/check.svg +9 -0
  42. driftlessmap/icons/sidebar/compare.svg +18 -0
  43. driftlessmap/icons/sidebar/contour.svg +14 -0
  44. driftlessmap/icons/sidebar/h_section.png +0 -0
  45. driftlessmap/icons/sidebar/info.svg +14 -0
  46. driftlessmap/icons/sidebar/layers.png +0 -0
  47. driftlessmap/icons/sidebar/layers.svg +23 -0
  48. driftlessmap/icons/sidebar/line.svg +10 -0
  49. driftlessmap/icons/sidebar/link.svg +18 -0
  50. driftlessmap/icons/sidebar/link_off.svg +18 -0
  51. driftlessmap/icons/sidebar/merge.svg +11 -0
  52. driftlessmap/icons/sidebar/object.svg +13 -0
  53. driftlessmap/icons/sidebar/probe.png +0 -0
  54. driftlessmap/icons/sidebar/probe.svg +23 -0
  55. driftlessmap/icons/sidebar/rotation_horizontal.svg +14 -0
  56. driftlessmap/icons/sidebar/rotation_vertical.svg +14 -0
  57. driftlessmap/icons/sidebar/s_section.png +0 -0
  58. driftlessmap/icons/sidebar/tool.png +0 -0
  59. driftlessmap/icons/sidebar/tool.svg +19 -0
  60. driftlessmap/icons/sidebar/trash.png +0 -0
  61. driftlessmap/icons/sidebar/tree_checked.svg +10 -0
  62. driftlessmap/icons/sidebar/treeview.png +0 -0
  63. driftlessmap/icons/sidebar/treeview.svg +10 -0
  64. driftlessmap/icons/sidebar/treeview2.png +0 -0
  65. driftlessmap/icons/sidebar/virus.svg +24 -0
  66. driftlessmap/icons/tdown.svg +16 -0
  67. driftlessmap/icons/toolbar/accept.svg +18 -0
  68. driftlessmap/icons/toolbar/accept2.svg +24 -0
  69. driftlessmap/icons/toolbar/aim.svg +44 -0
  70. driftlessmap/icons/toolbar/aim_not.svg +28 -0
  71. driftlessmap/icons/toolbar/anchor.svg +14 -0
  72. driftlessmap/icons/toolbar/anticlockwise_rotation.svg +12 -0
  73. driftlessmap/icons/toolbar/atlas_icon.png +0 -0
  74. driftlessmap/icons/toolbar/boundary_register.svg +18 -0
  75. driftlessmap/icons/toolbar/cancel.svg +32 -0
  76. driftlessmap/icons/toolbar/cell_select.svg +22 -0
  77. driftlessmap/icons/toolbar/cell_select_not.svg +12 -0
  78. driftlessmap/icons/toolbar/check.svg +9 -0
  79. driftlessmap/icons/toolbar/clockwise_rotation.svg +11 -0
  80. driftlessmap/icons/toolbar/closed_path.svg +16 -0
  81. driftlessmap/icons/toolbar/closed_path2 copy.svg +19 -0
  82. driftlessmap/icons/toolbar/closed_path2.svg +20 -0
  83. driftlessmap/icons/toolbar/eraser.png +0 -0
  84. driftlessmap/icons/toolbar/eraser.svg +19 -0
  85. driftlessmap/icons/toolbar/eye.svg +17 -0
  86. driftlessmap/icons/toolbar/eye_closed.svg +19 -0
  87. driftlessmap/icons/toolbar/fill.svg +12 -0
  88. driftlessmap/icons/toolbar/gps.svg +15 -0
  89. driftlessmap/icons/toolbar/h_flip.png +0 -0
  90. driftlessmap/icons/toolbar/handle.png +0 -0
  91. driftlessmap/icons/toolbar/image_icon.png +0 -0
  92. driftlessmap/icons/toolbar/image_icon.svg +25 -0
  93. driftlessmap/icons/toolbar/info.svg +14 -0
  94. driftlessmap/icons/toolbar/inpart.png +0 -0
  95. driftlessmap/icons/toolbar/inpart.svg +17 -0
  96. driftlessmap/icons/toolbar/lasso.png +0 -0
  97. driftlessmap/icons/toolbar/lasso.svg +14 -0
  98. driftlessmap/icons/toolbar/left90.png +0 -0
  99. driftlessmap/icons/toolbar/line.svg +10 -0
  100. driftlessmap/icons/toolbar/line_sites.svg +11 -0
  101. driftlessmap/icons/toolbar/linear_silicon.png +0 -0
  102. driftlessmap/icons/toolbar/linear_silicon.svg +88 -0
  103. driftlessmap/icons/toolbar/list.svg +14 -0
  104. driftlessmap/icons/toolbar/location.svg +14 -0
  105. driftlessmap/icons/toolbar/magic-wand.svg +16 -0
  106. driftlessmap/icons/toolbar/magic_white.png +0 -0
  107. driftlessmap/icons/toolbar/mask.svg +10 -0
  108. driftlessmap/icons/toolbar/match.svg +20 -0
  109. driftlessmap/icons/toolbar/matchbnd.svg +18 -0
  110. driftlessmap/icons/toolbar/matching.svg +36 -0
  111. driftlessmap/icons/toolbar/merge.svg +11 -0
  112. driftlessmap/icons/toolbar/move_down.png +0 -0
  113. driftlessmap/icons/toolbar/move_left.png +0 -0
  114. driftlessmap/icons/toolbar/move_right.png +0 -0
  115. driftlessmap/icons/toolbar/move_up.png +0 -0
  116. driftlessmap/icons/toolbar/moving.png +0 -0
  117. driftlessmap/icons/toolbar/multi-probe.svg +119 -0
  118. driftlessmap/icons/toolbar/multi_pencil.svg +23 -0
  119. driftlessmap/icons/toolbar/multi_probe.png +0 -0
  120. driftlessmap/icons/toolbar/object.svg +13 -0
  121. driftlessmap/icons/toolbar/open_path.svg +22 -0
  122. driftlessmap/icons/toolbar/outpart.png +0 -0
  123. driftlessmap/icons/toolbar/outpart.svg +26 -0
  124. driftlessmap/icons/toolbar/pencil.png +0 -0
  125. driftlessmap/icons/toolbar/pencil.svg +19 -0
  126. driftlessmap/icons/toolbar/plasso.png +0 -0
  127. driftlessmap/icons/toolbar/probe.svg +19 -0
  128. driftlessmap/icons/toolbar/radar.svg +32 -0
  129. driftlessmap/icons/toolbar/rotation.svg +45 -0
  130. driftlessmap/icons/toolbar/rotation_clockwise.svg +18 -0
  131. driftlessmap/icons/toolbar/rotation_counter_clockwise.svg +18 -0
  132. driftlessmap/icons/toolbar/ruler.svg +27 -0
  133. driftlessmap/icons/toolbar/separate_sites.svg +13 -0
  134. driftlessmap/icons/toolbar/single_pencil.svg +16 -0
  135. driftlessmap/icons/toolbar/toa.svg +11 -0
  136. driftlessmap/icons/toolbar/toa_delete.svg +36 -0
  137. driftlessmap/icons/toolbar/toaa.svg +11 -0
  138. driftlessmap/icons/toolbar/toh.svg +11 -0
  139. driftlessmap/icons/toolbar/toh_delete.svg +36 -0
  140. driftlessmap/icons/toolbar/tohh.svg +11 -0
  141. driftlessmap/icons/toolbar/trans.png +0 -0
  142. driftlessmap/icons/toolbar/trans.svg +16 -0
  143. driftlessmap/icons/toolbar/triangulation.svg +10 -0
  144. driftlessmap/icons/toolbar/two_window.png +0 -0
  145. driftlessmap/icons/toolbar/unmerge.svg +16 -0
  146. driftlessmap/icons/toolbar/v_flip.png +0 -0
  147. driftlessmap/icons/toolbar/virus.svg +24 -0
  148. driftlessmap/icons/toolbar/virus_register.svg +31 -0
  149. driftlessmap/icons/toolbar/vis2d.svg +29 -0
  150. driftlessmap/icons/toolbar/window2.png +0 -0
  151. driftlessmap/icons/toolbar/window3.png +0 -0
  152. driftlessmap/icons/toolbar/window4.png +0 -0
  153. driftlessmap/icons/tree_close.svg +14 -0
  154. driftlessmap/icons/tree_open.svg +14 -0
  155. driftlessmap/icons/up-arrow.svg +10 -0
  156. driftlessmap/image_curves.py +579 -0
  157. driftlessmap/image_reader.py +186 -0
  158. driftlessmap/image_stacks.py +359 -0
  159. driftlessmap/image_view.py +646 -0
  160. driftlessmap/images_reader.py +5 -0
  161. driftlessmap/label_tree.py +278 -0
  162. driftlessmap/layer_validation.py +25 -0
  163. driftlessmap/layers_control.py +554 -0
  164. driftlessmap/main_window.ui +883 -0
  165. driftlessmap/movable_points.py +254 -0
  166. driftlessmap/obj_items.py +122 -0
  167. driftlessmap/object_control.py +1762 -0
  168. driftlessmap/persistence.py +376 -0
  169. driftlessmap/popup_message.py +16 -0
  170. driftlessmap/probe_csv.py +319 -0
  171. driftlessmap/probe_reconstruction.py +409 -0
  172. driftlessmap/probe_utiles.py +1442 -0
  173. driftlessmap/qss/atlas_view_group_box.qss +15 -0
  174. driftlessmap/qss/box_label.qss +7 -0
  175. driftlessmap/qss/channel_selector.qss +19 -0
  176. driftlessmap/qss/color_combo.qss +121 -0
  177. driftlessmap/qss/decor_label.qss +9 -0
  178. driftlessmap/qss/dialogs.qss +11 -0
  179. driftlessmap/qss/hidden_line_edit.qss +5 -0
  180. driftlessmap/qss/label_tree.qss +123 -0
  181. driftlessmap/qss/main_window.qss +243 -0
  182. driftlessmap/qss/menu_bar.qss +42 -0
  183. driftlessmap/qss/multi_handle_slider.qss +14 -0
  184. driftlessmap/qss/obj_ctrl_bottom_button.qss +29 -0
  185. driftlessmap/qss/object_text_button.qss +19 -0
  186. driftlessmap/qss/page_control.qss +36 -0
  187. driftlessmap/qss/rotation_button.qss +9 -0
  188. driftlessmap/qss/side_bar.qss +23 -0
  189. driftlessmap/qss/spinbox.qss +106 -0
  190. driftlessmap/qss/tabs.qss +211 -0
  191. driftlessmap/qss/tool_bar.qss +77 -0
  192. driftlessmap/resources.py +34 -0
  193. driftlessmap/roi_analysis.py +375 -0
  194. driftlessmap/run_driftlessmap.py +4 -0
  195. driftlessmap/slice_stacks.py +268 -0
  196. driftlessmap/slice_validation.py +20 -0
  197. driftlessmap/toolbox.py +569 -0
  198. driftlessmap/triangulation.py +517 -0
  199. driftlessmap/triangulation_points.py +108 -0
  200. driftlessmap/user_settings.py +92 -0
  201. driftlessmap/uuuuuu.py +1168 -0
  202. driftlessmap/version.py +3 -0
  203. driftlessmap/widgets_utils.py +140 -0
  204. driftlessmap/wtiles.py +715 -0
  205. driftlessmap-1.1.0.dist-info/METADATA +186 -0
  206. driftlessmap-1.1.0.dist-info/RECORD +210 -0
  207. driftlessmap-1.1.0.dist-info/WHEEL +5 -0
  208. driftlessmap-1.1.0.dist-info/entry_points.txt +2 -0
  209. driftlessmap-1.1.0.dist-info/licenses/LICENSE.txt +23 -0
  210. driftlessmap-1.1.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,278 @@
1
+ import os
2
+ import sys
3
+ import numpy as np
4
+ from PyQt6.QtWidgets import *
5
+ from PyQt6.QtGui import *
6
+ from PyQt6.QtCore import *
7
+ import pyqtgraph as pg
8
+ from .uuuuuu import read_qss_file
9
+
10
+
11
+ reset_button_style = '''
12
+ QPushButton{
13
+ background: #656565;
14
+ border-radius: 5px;
15
+ color: white;
16
+ border-style: outset;
17
+ border-bottom: 1px solid rgb(30, 30, 30);
18
+ min-height: 16px;
19
+ margin: 0px;
20
+ }
21
+
22
+ QPushButton:hover{
23
+ background-color: #323232;
24
+ border: 1px solid #656565;
25
+ }
26
+
27
+ '''
28
+
29
+
30
+ class SignalBlock(object):
31
+ """Class used to temporarily block a Qt signal connection::
32
+
33
+ with SignalBlock(signal, slot):
34
+ # do something that emits a signal; it will
35
+ # not be delivered to slot
36
+ """
37
+ def __init__(self, signal, slot):
38
+ self.signal = signal
39
+ self.slot = slot
40
+
41
+ def __enter__(self):
42
+ self.signal.disconnect(self.slot)
43
+ return self
44
+
45
+ def __exit__(self, *args):
46
+ self.signal.connect(self.slot)
47
+
48
+
49
+ class LabelTree(QWidget):
50
+
51
+ class SignalProxy(QObject):
52
+ labelColorChanged = pyqtSignal(object)
53
+ labelsChanged = pyqtSignal()
54
+ resetLabels = pyqtSignal()
55
+
56
+ def __init__(self, parent=None):
57
+ self._sigprox = LabelTree.SignalProxy()
58
+ self.label_color_changed = self._sigprox.labelColorChanged
59
+ self.labels_changed = self._sigprox.labelsChanged
60
+ self.reset_labels = self._sigprox.resetLabels
61
+
62
+ self._block_signals = False
63
+ QWidget.__init__(self, parent)
64
+ label_tree_style = read_qss_file('qss/label_tree.qss')
65
+ self.setStyleSheet(label_tree_style)
66
+
67
+ self.layout = QVBoxLayout()
68
+ self.setLayout(self.layout)
69
+ self.layout.setSpacing(0)
70
+ self.layout.setContentsMargins(0, 0, 0, 0)
71
+
72
+ self.label_level = None
73
+ self.current_lut = None
74
+ self.display_label_ids = None
75
+ self.display_index_by_id = {}
76
+ self.root_item = []
77
+ self.root_acronym = []
78
+
79
+ self.tree = QTreeWidget(self)
80
+ self.layout.addWidget(self.tree)
81
+ self.tree.header().setSectionResizeMode(QHeaderView.ResizeMode.ResizeToContents)
82
+ self.tree.headerItem().setText(0, "id")
83
+ self.tree.headerItem().setText(1, "name")
84
+ self.tree.headerItem().setText(2, "color")
85
+ self.labels_by_id = {}
86
+ self.labels_by_acronym = {}
87
+ self.checked = set()
88
+ self.tree.itemChanged.connect(self.item_change)
89
+
90
+ self.layout.addSpacing(10)
91
+ self.reset_btn = QPushButton('Reset colors')
92
+ self.reset_btn.setStyleSheet(reset_button_style)
93
+ self.layout.addWidget(self.reset_btn)
94
+ self.reset_btn.clicked.connect(self.reset_colors)
95
+
96
+ def set_labels(self, label_data):
97
+ self._block_signals = True
98
+ try:
99
+ if self.current_lut is not None:
100
+ self.clear_labels()
101
+
102
+ n_labels = len(label_data['index'])
103
+ if n_labels == 0:
104
+ raise ValueError('Atlas label data is empty.')
105
+ label_ids = np.asarray(label_data['index'], dtype=np.int64)
106
+ self.display_label_ids = np.unique(
107
+ np.concatenate((np.array([0], dtype=np.int64), label_ids))
108
+ )
109
+ self.display_index_by_id = {
110
+ int(label_id): display_index
111
+ for display_index, label_id in enumerate(self.display_label_ids)
112
+ }
113
+ self.label_level = len(self.display_label_ids) - 1
114
+ self.current_lut = np.zeros((self.label_level + 1, 4), dtype=np.ubyte)
115
+ # Pass 1: create every item first so that parent lookups in pass 2
116
+ # do not depend on the order labels appear in the atlas data.
117
+ for i in range(n_labels):
118
+ label_id = int(label_data['index'][i])
119
+ parent = int(label_data['parent'][i])
120
+ color = label_data['color'][i]
121
+ display_index = self.display_index_by_id[label_id]
122
+ self.current_lut[display_index] = np.array(
123
+ [color[0], color[1], color[2], 255]
124
+ )
125
+ da_color = QColor(color[0], color[1], color[2]).name(QColor.NameFormat.HexRgb)
126
+ name = label_data['label'][i]
127
+ acronym = label_data['abbrev'][i]
128
+ if parent < 0:
129
+ parent = -1
130
+ self.root_acronym.append(acronym.encode())
131
+ rec = (label_id, parent, name.encode(), acronym.encode(), da_color.encode())
132
+ self.add_label(*rec)
133
+ # Pass 2: attach each item to its parent (or to the tree root when the
134
+ # parent is missing/negative), registering every top-level node as a
135
+ # root so describe() can always terminate its upward walk.
136
+ tree_root = self.tree.invisibleRootItem()
137
+ for label_id, rec in self.labels_by_id.items():
138
+ item = rec['item']
139
+ parent = rec['parent']
140
+ if parent in self.labels_by_id:
141
+ parent_item = self.labels_by_id[parent]['item']
142
+ else:
143
+ parent_item = tree_root
144
+ self.root_item.append(item)
145
+ parent_item.addChild(item)
146
+ self.tree.setItemWidget(item, 2, rec['btn'])
147
+ rec['btn'].sigColorChanged.connect(self.item_color_changed)
148
+ finally:
149
+ self._block_signals = False
150
+ self.labels_changed.emit()
151
+
152
+ def add_label(self, label_id, parent, name, acronym, color):
153
+ item = QTreeWidgetItem([acronym.decode(), name.decode(), ''])
154
+ item.setFlags(item.flags() | Qt.ItemFlag.ItemIsUserCheckable)
155
+ item.setCheckState(0, Qt.CheckState.Unchecked)
156
+
157
+ btn = pg.ColorButton(color=pg.mkColor(color.decode()))
158
+ btn.defaultColor = QColor(btn.color())
159
+ btn.id = label_id
160
+
161
+ # Item creation and tree attachment are split into two passes (see
162
+ # set_labels), so only register the item here; attachment happens later.
163
+ self.labels_by_id[label_id] = {'item': item, 'btn': btn, 'parent': parent}
164
+ item.id = label_id
165
+ self.labels_by_acronym[acronym] = self.labels_by_id[label_id]
166
+
167
+ def clear_labels(self):
168
+ root = self.tree.invisibleRootItem()
169
+ for label_id in list(self.labels_by_id.keys()):
170
+ da_item = self.labels_by_id[label_id]['item']
171
+ (da_item.parent() or root).removeChild(da_item)
172
+ # Reset all bookkeeping so a subsequent set_labels() starts clean and
173
+ # does not re-attach or treat stale entries as roots.
174
+ self.labels_by_id = {}
175
+ self.labels_by_acronym = {}
176
+ self.root_item = []
177
+ self.root_acronym = []
178
+ self.checked = set()
179
+ self.display_label_ids = None
180
+ self.display_index_by_id = {}
181
+
182
+ def item_change(self, item, col):
183
+ checked = item.checkState(0) == Qt.CheckState.Checked
184
+ with SignalBlock(self.tree.itemChanged, self.item_change):
185
+ self.check_recursive(item, checked)
186
+
187
+ if not self._block_signals:
188
+ self.labels_changed.emit()
189
+
190
+ def check_recursive(self, item, checked):
191
+ if checked:
192
+ self.checked.add(item.id)
193
+ item.setCheckState(0, Qt.CheckState.Checked)
194
+ else:
195
+ if item.id in self.checked:
196
+ self.checked.remove(item.id)
197
+ item.setCheckState(0, Qt.CheckState.Unchecked)
198
+
199
+ for i in range(item.childCount()):
200
+ self.check_recursive(item.child(i), checked)
201
+
202
+ def item_color_changed(self, btn):
203
+ color = btn.color()
204
+ self.set_label_color(btn.id, color)
205
+
206
+ def set_label_color(self, label_id, color, recursive=True, emit=True):
207
+ item = self.labels_by_id[label_id]['item']
208
+ btn = self.labels_by_id[label_id]['btn']
209
+ rgb_color = (color.red(), color.green(), color.blue(), color.alpha())
210
+ display_index = self.display_index_by_id[int(label_id)]
211
+ self.current_lut[display_index] = np.array(
212
+ [rgb_color[0], rgb_color[1], rgb_color[2], rgb_color[3]]
213
+ )
214
+ with SignalBlock(btn.sigColorChanged, self.item_color_changed):
215
+ btn.setColor(color)
216
+ if recursive:
217
+ for i in range(item.childCount()):
218
+ ch = item.child(i)
219
+ self.set_label_color(ch.id, color, recursive=recursive, emit=False)
220
+ if emit:
221
+ self.label_color_changed.emit((label_id, self.current_lut[label_id]))
222
+
223
+ def lookup_table(self):
224
+ lut = np.zeros((self.label_level + 1, 4), dtype=np.ubyte)
225
+ for layer_id in self.checked:
226
+ display_index = self.display_index_by_id.get(int(layer_id))
227
+ if display_index is None:
228
+ continue
229
+ lut[display_index] = self.labels_by_id[layer_id]['btn'].color(mode='byte')
230
+ return lut
231
+
232
+ def map_labels(self, label_data):
233
+ """Map sparse atlas structure IDs to compact display-LUT indexes."""
234
+ label_data = np.asarray(label_data)
235
+ if self.display_label_ids is None:
236
+ return label_data
237
+
238
+ flat_labels = label_data.reshape(-1)
239
+ mapped_indexes = np.searchsorted(self.display_label_ids, flat_labels)
240
+ valid = mapped_indexes < len(self.display_label_ids)
241
+ clipped_indexes = np.minimum(mapped_indexes, len(self.display_label_ids) - 1)
242
+ valid &= self.display_label_ids[clipped_indexes] == flat_labels
243
+
244
+ dtype = np.uint16 if self.label_level <= np.iinfo(np.uint16).max else np.uint32
245
+ mapped = np.zeros(flat_labels.shape, dtype=dtype)
246
+ mapped[valid] = mapped_indexes[valid]
247
+ return mapped.reshape(label_data.shape)
248
+
249
+ def color_for_label(self, label_id):
250
+ """Return the configured RGBA color for an original structure ID."""
251
+ display_index = self.display_index_by_id.get(int(label_id))
252
+ if display_index is None:
253
+ return np.zeros(4, dtype=np.ubyte)
254
+ return self.current_lut[display_index]
255
+
256
+ def reset_colors(self):
257
+ try:
258
+ self.blockSignals(True)
259
+ for k, v in self.labels_by_id.items():
260
+ self.set_label_color(k, v['btn'].defaultColor, recursive=False, emit=False)
261
+ finally:
262
+ self.blockSignals(False)
263
+ self.reset_labels.emit()
264
+
265
+ def describe(self, label_id):
266
+ if label_id == 0:
267
+ return ''
268
+ else:
269
+ if label_id not in self.labels_by_id:
270
+ return "Unknown label: %d" % label_id
271
+ descr = []
272
+ item = self.labels_by_id[label_id]['item']
273
+ name = str(item.text(1))
274
+ while item is not None and item not in self.root_item:
275
+ # self.labels_by_acronym[b'Brain']['item'], self.labels_by_acronym[b'SpC']['item'], self.labels_by_acronym[b'IE']['item']
276
+ descr.insert(0, str(item.text(0)))
277
+ item = item.parent()
278
+ return '[%d]' % label_id + ' > '.join(descr) + " : " + name
@@ -0,0 +1,25 @@
1
+ """Validation helpers for persisted DriftlessMap layer data."""
2
+
3
+ import numpy as np
4
+
5
+
6
+ def image_layer_matches(
7
+ layer_dict, image_shape, *, expected_level=None, require_metadata=False
8
+ ):
9
+ """Return whether a persisted pixel layer matches the current image."""
10
+ required = {"data"}
11
+ if require_metadata:
12
+ required.update({"level", "size"})
13
+ if not isinstance(layer_dict, dict) or not required.issubset(layer_dict):
14
+ return False
15
+
16
+ data = np.asarray(layer_dict["data"])
17
+ if data.ndim < 2 or tuple(data.shape[:2]) != tuple(image_shape):
18
+ return False
19
+
20
+ if require_metadata:
21
+ if tuple(np.ravel(layer_dict["size"])) != tuple(image_shape):
22
+ return False
23
+ if layer_dict["level"] != expected_level:
24
+ return False
25
+ return True