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.
- driftlessmap/__init__.py +16 -0
- driftlessmap/__main__.py +5 -0
- driftlessmap/about.py +24 -0
- driftlessmap/allen_downloader.py +908 -0
- driftlessmap/app.py +8085 -0
- driftlessmap/atlas_downloader.py +370 -0
- driftlessmap/atlas_loader.py +526 -0
- driftlessmap/atlas_processor.py +784 -0
- driftlessmap/atlas_transform.py +111 -0
- driftlessmap/atlas_view.py +1435 -0
- driftlessmap/cell_detection.py +27 -0
- driftlessmap/coordinate_validation.py +19 -0
- driftlessmap/czi_reader.py +236 -0
- driftlessmap/data/WHS_atlas_labels.pkl +0 -0
- driftlessmap/data/allen_mice_atlas_labels.pkl +0 -0
- driftlessmap/data/atlas_labels.pkl +0 -0
- driftlessmap/data/query.csv +1328 -0
- driftlessmap/download_utils.py +85 -0
- driftlessmap/herbs.png +0 -0
- driftlessmap/icons/backward.svg +15 -0
- driftlessmap/icons/dot.svg +11 -0
- driftlessmap/icons/down-arrow.svg +10 -0
- driftlessmap/icons/fast_backward.svg +14 -0
- driftlessmap/icons/fast_forward.svg +14 -0
- driftlessmap/icons/forward.svg +14 -0
- driftlessmap/icons/layers/add.png +0 -0
- driftlessmap/icons/layers/ai.png +0 -0
- driftlessmap/icons/layers/eye_off.png +0 -0
- driftlessmap/icons/layers/eye_on.png +0 -0
- driftlessmap/icons/layers/eye_white.png +0 -0
- driftlessmap/icons/layers/hi.png +0 -0
- driftlessmap/icons/layers/trash.png +0 -0
- driftlessmap/icons/object.svg +22 -0
- driftlessmap/icons/sidebar/add.png +0 -0
- driftlessmap/icons/sidebar/atlascontrol.png +0 -0
- driftlessmap/icons/sidebar/atlascontrol.svg +32 -0
- driftlessmap/icons/sidebar/bnd.svg +14 -0
- driftlessmap/icons/sidebar/c_section.png +0 -0
- driftlessmap/icons/sidebar/c_section2.png +0 -0
- driftlessmap/icons/sidebar/cell.svg +24 -0
- driftlessmap/icons/sidebar/check.svg +9 -0
- driftlessmap/icons/sidebar/compare.svg +18 -0
- driftlessmap/icons/sidebar/contour.svg +14 -0
- driftlessmap/icons/sidebar/h_section.png +0 -0
- driftlessmap/icons/sidebar/info.svg +14 -0
- driftlessmap/icons/sidebar/layers.png +0 -0
- driftlessmap/icons/sidebar/layers.svg +23 -0
- driftlessmap/icons/sidebar/line.svg +10 -0
- driftlessmap/icons/sidebar/link.svg +18 -0
- driftlessmap/icons/sidebar/link_off.svg +18 -0
- driftlessmap/icons/sidebar/merge.svg +11 -0
- driftlessmap/icons/sidebar/object.svg +13 -0
- driftlessmap/icons/sidebar/probe.png +0 -0
- driftlessmap/icons/sidebar/probe.svg +23 -0
- driftlessmap/icons/sidebar/rotation_horizontal.svg +14 -0
- driftlessmap/icons/sidebar/rotation_vertical.svg +14 -0
- driftlessmap/icons/sidebar/s_section.png +0 -0
- driftlessmap/icons/sidebar/tool.png +0 -0
- driftlessmap/icons/sidebar/tool.svg +19 -0
- driftlessmap/icons/sidebar/trash.png +0 -0
- driftlessmap/icons/sidebar/tree_checked.svg +10 -0
- driftlessmap/icons/sidebar/treeview.png +0 -0
- driftlessmap/icons/sidebar/treeview.svg +10 -0
- driftlessmap/icons/sidebar/treeview2.png +0 -0
- driftlessmap/icons/sidebar/virus.svg +24 -0
- driftlessmap/icons/tdown.svg +16 -0
- driftlessmap/icons/toolbar/accept.svg +18 -0
- driftlessmap/icons/toolbar/accept2.svg +24 -0
- driftlessmap/icons/toolbar/aim.svg +44 -0
- driftlessmap/icons/toolbar/aim_not.svg +28 -0
- driftlessmap/icons/toolbar/anchor.svg +14 -0
- driftlessmap/icons/toolbar/anticlockwise_rotation.svg +12 -0
- driftlessmap/icons/toolbar/atlas_icon.png +0 -0
- driftlessmap/icons/toolbar/boundary_register.svg +18 -0
- driftlessmap/icons/toolbar/cancel.svg +32 -0
- driftlessmap/icons/toolbar/cell_select.svg +22 -0
- driftlessmap/icons/toolbar/cell_select_not.svg +12 -0
- driftlessmap/icons/toolbar/check.svg +9 -0
- driftlessmap/icons/toolbar/clockwise_rotation.svg +11 -0
- driftlessmap/icons/toolbar/closed_path.svg +16 -0
- driftlessmap/icons/toolbar/closed_path2 copy.svg +19 -0
- driftlessmap/icons/toolbar/closed_path2.svg +20 -0
- driftlessmap/icons/toolbar/eraser.png +0 -0
- driftlessmap/icons/toolbar/eraser.svg +19 -0
- driftlessmap/icons/toolbar/eye.svg +17 -0
- driftlessmap/icons/toolbar/eye_closed.svg +19 -0
- driftlessmap/icons/toolbar/fill.svg +12 -0
- driftlessmap/icons/toolbar/gps.svg +15 -0
- driftlessmap/icons/toolbar/h_flip.png +0 -0
- driftlessmap/icons/toolbar/handle.png +0 -0
- driftlessmap/icons/toolbar/image_icon.png +0 -0
- driftlessmap/icons/toolbar/image_icon.svg +25 -0
- driftlessmap/icons/toolbar/info.svg +14 -0
- driftlessmap/icons/toolbar/inpart.png +0 -0
- driftlessmap/icons/toolbar/inpart.svg +17 -0
- driftlessmap/icons/toolbar/lasso.png +0 -0
- driftlessmap/icons/toolbar/lasso.svg +14 -0
- driftlessmap/icons/toolbar/left90.png +0 -0
- driftlessmap/icons/toolbar/line.svg +10 -0
- driftlessmap/icons/toolbar/line_sites.svg +11 -0
- driftlessmap/icons/toolbar/linear_silicon.png +0 -0
- driftlessmap/icons/toolbar/linear_silicon.svg +88 -0
- driftlessmap/icons/toolbar/list.svg +14 -0
- driftlessmap/icons/toolbar/location.svg +14 -0
- driftlessmap/icons/toolbar/magic-wand.svg +16 -0
- driftlessmap/icons/toolbar/magic_white.png +0 -0
- driftlessmap/icons/toolbar/mask.svg +10 -0
- driftlessmap/icons/toolbar/match.svg +20 -0
- driftlessmap/icons/toolbar/matchbnd.svg +18 -0
- driftlessmap/icons/toolbar/matching.svg +36 -0
- driftlessmap/icons/toolbar/merge.svg +11 -0
- driftlessmap/icons/toolbar/move_down.png +0 -0
- driftlessmap/icons/toolbar/move_left.png +0 -0
- driftlessmap/icons/toolbar/move_right.png +0 -0
- driftlessmap/icons/toolbar/move_up.png +0 -0
- driftlessmap/icons/toolbar/moving.png +0 -0
- driftlessmap/icons/toolbar/multi-probe.svg +119 -0
- driftlessmap/icons/toolbar/multi_pencil.svg +23 -0
- driftlessmap/icons/toolbar/multi_probe.png +0 -0
- driftlessmap/icons/toolbar/object.svg +13 -0
- driftlessmap/icons/toolbar/open_path.svg +22 -0
- driftlessmap/icons/toolbar/outpart.png +0 -0
- driftlessmap/icons/toolbar/outpart.svg +26 -0
- driftlessmap/icons/toolbar/pencil.png +0 -0
- driftlessmap/icons/toolbar/pencil.svg +19 -0
- driftlessmap/icons/toolbar/plasso.png +0 -0
- driftlessmap/icons/toolbar/probe.svg +19 -0
- driftlessmap/icons/toolbar/radar.svg +32 -0
- driftlessmap/icons/toolbar/rotation.svg +45 -0
- driftlessmap/icons/toolbar/rotation_clockwise.svg +18 -0
- driftlessmap/icons/toolbar/rotation_counter_clockwise.svg +18 -0
- driftlessmap/icons/toolbar/ruler.svg +27 -0
- driftlessmap/icons/toolbar/separate_sites.svg +13 -0
- driftlessmap/icons/toolbar/single_pencil.svg +16 -0
- driftlessmap/icons/toolbar/toa.svg +11 -0
- driftlessmap/icons/toolbar/toa_delete.svg +36 -0
- driftlessmap/icons/toolbar/toaa.svg +11 -0
- driftlessmap/icons/toolbar/toh.svg +11 -0
- driftlessmap/icons/toolbar/toh_delete.svg +36 -0
- driftlessmap/icons/toolbar/tohh.svg +11 -0
- driftlessmap/icons/toolbar/trans.png +0 -0
- driftlessmap/icons/toolbar/trans.svg +16 -0
- driftlessmap/icons/toolbar/triangulation.svg +10 -0
- driftlessmap/icons/toolbar/two_window.png +0 -0
- driftlessmap/icons/toolbar/unmerge.svg +16 -0
- driftlessmap/icons/toolbar/v_flip.png +0 -0
- driftlessmap/icons/toolbar/virus.svg +24 -0
- driftlessmap/icons/toolbar/virus_register.svg +31 -0
- driftlessmap/icons/toolbar/vis2d.svg +29 -0
- driftlessmap/icons/toolbar/window2.png +0 -0
- driftlessmap/icons/toolbar/window3.png +0 -0
- driftlessmap/icons/toolbar/window4.png +0 -0
- driftlessmap/icons/tree_close.svg +14 -0
- driftlessmap/icons/tree_open.svg +14 -0
- driftlessmap/icons/up-arrow.svg +10 -0
- driftlessmap/image_curves.py +579 -0
- driftlessmap/image_reader.py +186 -0
- driftlessmap/image_stacks.py +359 -0
- driftlessmap/image_view.py +646 -0
- driftlessmap/images_reader.py +5 -0
- driftlessmap/label_tree.py +278 -0
- driftlessmap/layer_validation.py +25 -0
- driftlessmap/layers_control.py +554 -0
- driftlessmap/main_window.ui +883 -0
- driftlessmap/movable_points.py +254 -0
- driftlessmap/obj_items.py +122 -0
- driftlessmap/object_control.py +1762 -0
- driftlessmap/persistence.py +376 -0
- driftlessmap/popup_message.py +16 -0
- driftlessmap/probe_csv.py +319 -0
- driftlessmap/probe_reconstruction.py +409 -0
- driftlessmap/probe_utiles.py +1442 -0
- driftlessmap/qss/atlas_view_group_box.qss +15 -0
- driftlessmap/qss/box_label.qss +7 -0
- driftlessmap/qss/channel_selector.qss +19 -0
- driftlessmap/qss/color_combo.qss +121 -0
- driftlessmap/qss/decor_label.qss +9 -0
- driftlessmap/qss/dialogs.qss +11 -0
- driftlessmap/qss/hidden_line_edit.qss +5 -0
- driftlessmap/qss/label_tree.qss +123 -0
- driftlessmap/qss/main_window.qss +243 -0
- driftlessmap/qss/menu_bar.qss +42 -0
- driftlessmap/qss/multi_handle_slider.qss +14 -0
- driftlessmap/qss/obj_ctrl_bottom_button.qss +29 -0
- driftlessmap/qss/object_text_button.qss +19 -0
- driftlessmap/qss/page_control.qss +36 -0
- driftlessmap/qss/rotation_button.qss +9 -0
- driftlessmap/qss/side_bar.qss +23 -0
- driftlessmap/qss/spinbox.qss +106 -0
- driftlessmap/qss/tabs.qss +211 -0
- driftlessmap/qss/tool_bar.qss +77 -0
- driftlessmap/resources.py +34 -0
- driftlessmap/roi_analysis.py +375 -0
- driftlessmap/run_driftlessmap.py +4 -0
- driftlessmap/slice_stacks.py +268 -0
- driftlessmap/slice_validation.py +20 -0
- driftlessmap/toolbox.py +569 -0
- driftlessmap/triangulation.py +517 -0
- driftlessmap/triangulation_points.py +108 -0
- driftlessmap/user_settings.py +92 -0
- driftlessmap/uuuuuu.py +1168 -0
- driftlessmap/version.py +3 -0
- driftlessmap/widgets_utils.py +140 -0
- driftlessmap/wtiles.py +715 -0
- driftlessmap-1.1.0.dist-info/METADATA +186 -0
- driftlessmap-1.1.0.dist-info/RECORD +210 -0
- driftlessmap-1.1.0.dist-info/WHEEL +5 -0
- driftlessmap-1.1.0.dist-info/entry_points.txt +2 -0
- driftlessmap-1.1.0.dist-info/licenses/LICENSE.txt +23 -0
- 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
|