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,908 @@
1
+ import gzip
2
+ import os
3
+ from os.path import dirname, join
4
+ from PyQt6.QtWidgets import *
5
+ from PyQt6.QtGui import *
6
+ from PyQt6.QtCore import *
7
+ import pyqtgraph.opengl as gl
8
+
9
+ import nrrd
10
+ import pickle
11
+ import shutil
12
+ import numpy as np
13
+ import pandas as pd
14
+
15
+ from .atlas_loader import process_atlas_raw_data
16
+ from .uuuuuu import hex2rgb, obj_data_to_mesh3d, make_contour_img
17
+ from .obj_items import render_volume, render_small_volume
18
+ from .atlas_downloader import DownloadThread
19
+ from .atlas_transform import (
20
+ compact_boundary_volume,
21
+ compact_label_volume,
22
+ make_boundary_dict,
23
+ normalize_atlas_volume,
24
+ )
25
+ from .download_utils import download_file
26
+ from .probe_reconstruction import allen_ccf_estimated_bregma_vox
27
+
28
+
29
+ def _stream_unique_nrrd_values(filename, progress=None, chunk_size=8 * 1024 * 1024):
30
+ """Return unique NRRD values without loading or sorting the whole volume."""
31
+ with open(filename, "rb") as source:
32
+ header = nrrd.read_header(source)
33
+ dtype = nrrd.reader._determine_datatype(header)
34
+ encoding = str(header["encoding"]).lower()
35
+ total_values = int(np.prod(header["sizes"], dtype=np.int64))
36
+
37
+ if encoding in ("gzip", "gz"):
38
+ data_stream = gzip.GzipFile(fileobj=source)
39
+ elif encoding == "raw":
40
+ data_stream = source
41
+ else:
42
+ raise ValueError(
43
+ "Unsupported NRRD encoding for label scan: {}".format(encoding)
44
+ )
45
+
46
+ unique_values = set()
47
+ remainder = b""
48
+ values_read = 0
49
+ if progress is not None:
50
+ progress(0, total_values)
51
+
52
+ try:
53
+ while True:
54
+ chunk = data_stream.read(chunk_size)
55
+ if not chunk:
56
+ break
57
+ chunk = remainder + chunk
58
+ aligned_size = len(chunk) - (len(chunk) % dtype.itemsize)
59
+ if aligned_size:
60
+ values = np.frombuffer(chunk[:aligned_size], dtype=dtype)
61
+ unique_values.update(int(value) for value in np.unique(values))
62
+ values_read += values.size
63
+ if progress is not None:
64
+ progress(values_read, total_values)
65
+ remainder = chunk[aligned_size:]
66
+ finally:
67
+ if data_stream is not source:
68
+ data_stream.close()
69
+
70
+ if remainder or values_read != total_values:
71
+ raise ValueError(
72
+ "Annotation data size does not match its NRRD header: "
73
+ "{} of {} values read.".format(values_read, total_values)
74
+ )
75
+ return np.asarray(sorted(unique_values), dtype=dtype)
76
+
77
+
78
+ class WorkerProcessAllen(QObject):
79
+ finished = pyqtSignal()
80
+ failed = pyqtSignal(str)
81
+ progress = pyqtSignal(float)
82
+ status = pyqtSignal(str)
83
+
84
+ def __init__(self):
85
+ super(WorkerProcessAllen, self).__init__()
86
+ self.saving_folder = None
87
+ self.data_local = None
88
+ self.segmentation_local = None
89
+ self.label_local = None
90
+ self.b_val = None
91
+ self.l_val = None
92
+ self.vox_size = None
93
+
94
+ self.atlas_data = None
95
+ self.atlas_info = None
96
+ self.segmentation_data = None
97
+ self.unique_label = None
98
+ self.label_info = None
99
+ self.boundary = None
100
+ self.small_mesh_list = {}
101
+ self.mesh_data = None
102
+
103
+ def set_data(self, saving_folder, data_local, segmentation_local, label_local, b_val, vox_size):
104
+ self.saving_folder = saving_folder
105
+ self.data_local = data_local
106
+ self.segmentation_local = segmentation_local
107
+ self.label_local = label_local
108
+ self.b_val = b_val
109
+ self.vox_size = vox_size
110
+
111
+ def progress_control(self, total_count):
112
+ self.progress.emit(total_count)
113
+
114
+ def run(self):
115
+ try:
116
+ self._run()
117
+ except Exception as exc:
118
+ self.failed.emit(str(exc))
119
+ return
120
+ self.finished.emit()
121
+
122
+ def _run(self):
123
+ self.status.emit("Scanning annotation structure IDs...")
124
+ self.progress.emit(1)
125
+ segmentation_path = os.path.join(
126
+ self.saving_folder, self.segmentation_local
127
+ )
128
+ self.unique_label = _stream_unique_nrrd_values(
129
+ segmentation_path,
130
+ progress=lambda current, total: self.progress.emit(
131
+ 1 + 8 * current / total
132
+ ),
133
+ )
134
+ self.progress.emit(10)
135
+ self.status.emit(
136
+ "Loading the {} µm annotation volume into memory...".format(
137
+ self.vox_size
138
+ )
139
+ )
140
+ label_data, header = nrrd.read(segmentation_path)
141
+ self.progress.emit(14)
142
+
143
+ n_unique_labels = len(self.unique_label)
144
+ self.status.emit("Preparing downloaded structure meshes...")
145
+ mesh_path = os.path.join(self.saving_folder, 'meshes')
146
+ if not os.path.exists(mesh_path):
147
+ os.mkdir(mesh_path)
148
+ self.progress.emit(15)
149
+
150
+ atlas_size = label_data.shape
151
+
152
+ axis_info = {'to_HERBS': (2, 0, 1), 'from_HERBS': (1, 2, 0), 'direction_change': (True, True, False),
153
+ 'size': tuple(atlas_size)}
154
+
155
+ outfile_axis = open(os.path.join(self.saving_folder, 'atlas_axis_info.pkl'), 'wb')
156
+ pickle.dump(axis_info, outfile_axis)
157
+ outfile_axis.close()
158
+
159
+ downloaded_mesh_path = os.path.join(self.saving_folder, 'downloaded_meshes')
160
+
161
+ progress_step = np.linspace(15, 30, n_unique_labels)
162
+ mesh_status_interval = max(1, n_unique_labels // 100)
163
+ missing_mesh_index = []
164
+ for i in range(n_unique_labels):
165
+ ind = self.unique_label[i]
166
+ self.progress.emit(progress_step[i])
167
+ if i % mesh_status_interval == 0 or i == n_unique_labels - 1:
168
+ self.status.emit(
169
+ "Converting structure meshes: {} of {}...".format(
170
+ i + 1, n_unique_labels
171
+ )
172
+ )
173
+ if ind in [0]:
174
+ continue
175
+
176
+ filename = os.path.join(downloaded_mesh_path, '{}.obj'.format(ind))
177
+
178
+ if os.path.exists(filename):
179
+ try:
180
+ vertices, faces = obj_data_to_mesh3d(filename)
181
+ vertices = vertices / self.vox_size
182
+
183
+ vertices[:, 0] = atlas_size[0] - vertices[:, 0]
184
+ vertices[:, 1] = atlas_size[1] - vertices[:, 1]
185
+
186
+ verts = vertices.copy()
187
+ verts[:, 0] = vertices[:, 2].copy()
188
+ verts[:, 1] = vertices[:, 0].copy()
189
+ verts[:, 2] = vertices[:, 1].copy()
190
+
191
+ md = gl.MeshData(vertexes=verts, faces=faces)
192
+
193
+ outfile = open(os.path.join(mesh_path, '{}.pkl'.format(ind)), 'wb')
194
+ pickle.dump(md, outfile)
195
+ outfile.close()
196
+ except IndexError:
197
+ missing_mesh_index.append(ind)
198
+ else:
199
+ missing_mesh_index.append(ind)
200
+
201
+ self.status.emit("Loading the whole-brain mesh...")
202
+ target = os.path.join(self.saving_folder, "atlas_meshdata.pkl")
203
+ shutil.copyfile(join(mesh_path, '997.pkl'), target)
204
+
205
+ self.progress.emit(31)
206
+
207
+ infile = open(os.path.join(self.saving_folder, 'atlas_meshdata.pkl'), 'rb')
208
+ self.mesh_data = pickle.load(infile)
209
+ infile.close()
210
+ self.progress.emit(33)
211
+
212
+ self.status.emit("Preparing the Allen label hierarchy...")
213
+ df = pd.read_csv(os.path.join(self.saving_folder, self.label_local))
214
+
215
+ da_labels = df['safe_name'].to_numpy(dtype=str, copy=True)
216
+ da_labels[da_labels == 'root'] = 'Brain'
217
+ self.progress.emit(34)
218
+
219
+ da_short_label = df['acronym'].to_numpy(dtype=str, copy=True)
220
+ da_short_label[da_short_label == 'root'] = 'Brain'
221
+ self.progress.emit(35)
222
+
223
+ hex_colors = df['color_hex_triplet'].to_numpy(dtype=str, copy=True)
224
+ rgb_colors = []
225
+ for i in range(len(hex_colors)):
226
+ r, g, b = hex2rgb(hex_colors[i])
227
+ rgb_colors.append([r, g, b])
228
+ rgb_colors = np.asarray(rgb_colors)
229
+
230
+ self.progress.emit(36)
231
+
232
+ levels = []
233
+ structure_id_path = df['structure_id_path'].to_numpy(dtype=str, copy=True)
234
+ for i in range(len(structure_id_path)):
235
+ da_path = structure_id_path[i]
236
+ da_path_split = da_path.split('/')
237
+ for j in np.arange(len(da_path_split))[::-1]:
238
+ if da_path_split[j] == '':
239
+ da_path_split.pop(j)
240
+ levels.append(len(da_path_split))
241
+
242
+ self.progress.emit(37)
243
+
244
+ self.label_info = {'index': df['id'].to_numpy(dtype=int, copy=True),
245
+ 'label': da_labels,
246
+ 'parent': df['parent_structure_id'].fillna(0).to_numpy(dtype=int, copy=True),
247
+ 'abbrev': da_short_label,
248
+ 'color': rgb_colors,
249
+ 'level_indicator': levels}
250
+
251
+ with open(os.path.join(self.saving_folder, 'atlas_labels.pkl'), 'wb') as handle:
252
+ pickle.dump(self.label_info, handle, protocol=pickle.HIGHEST_PROTOCOL)
253
+
254
+ self.progress.emit(38)
255
+
256
+ self.status.emit(
257
+ "Loading the {} µm atlas intensity volume into memory...".format(
258
+ self.vox_size
259
+ )
260
+ )
261
+ volume_data, header = nrrd.read(os.path.join(self.saving_folder, self.data_local))
262
+ self.progress.emit(45)
263
+ self.status.emit("Transforming the atlas intensity volume...")
264
+ volume_data = np.transpose(volume_data[::-1, ::-1, :], (2, 0, 1))
265
+ self.progress.emit(46)
266
+ self.status.emit("Normalizing atlas intensities...")
267
+ self.atlas_data = normalize_atlas_volume(volume_data)
268
+ del volume_data
269
+ self.progress.emit(47)
270
+
271
+ b_val = self.b_val.copy()
272
+ if self.b_val[0] == 0:
273
+ b_val[0] = int(atlas_size[0] / 2)
274
+ if self.b_val[2] == 0:
275
+ b_val[2] = int(atlas_size[2] / 2)
276
+ self.progress.emit(48)
277
+
278
+ self.atlas_info = [
279
+ {'name': 'anterior', 'values': np.arange(self.atlas_data.shape[0]) * self.vox_size, 'units': 'um'},
280
+ {'name': 'dorsal', 'values': np.arange(self.atlas_data.shape[1]) * self.vox_size, 'units': 'um'},
281
+ {'name': 'right', 'values': np.arange(self.atlas_data.shape[2]) * self.vox_size, 'units': 'um'},
282
+ {'vxsize': self.vox_size,
283
+ 'Bregma': [b_val[2], atlas_size[0] - 1 - b_val[0], atlas_size[1] - 1 - b_val[1]]}
284
+ ]
285
+ self.progress.emit(49)
286
+
287
+ atlas = {'data': self.atlas_data, 'info': self.atlas_info}
288
+
289
+ self.status.emit("Saving the normalized atlas cache; this file can be large...")
290
+ outfile = open(os.path.join(self.saving_folder, 'atlas_pre_made.pkl'), 'wb')
291
+ pickle.dump(atlas, outfile)
292
+ outfile.close()
293
+ self.progress.emit(53)
294
+
295
+ self.status.emit("Transforming the annotation volume...")
296
+ self.segmentation_data = np.transpose(label_data[::-1, ::-1, :], (2, 0, 1))
297
+ self.segmentation_data = compact_label_volume(self.segmentation_data)
298
+ del label_data
299
+ print(self.segmentation_data.shape)
300
+
301
+ self.progress.emit(54)
302
+
303
+ segment = {'data': self.segmentation_data, 'unique_label': self.unique_label}
304
+
305
+ self.status.emit("Saving the annotation cache; this file can be large...")
306
+ outfile = open(os.path.join(self.saving_folder, 'segment_pre_made.pkl'), 'wb')
307
+ pickle.dump(segment, outfile)
308
+ outfile.close()
309
+
310
+ self.progress.emit(58)
311
+
312
+ if missing_mesh_index:
313
+ missing_count = len(missing_mesh_index)
314
+ for missing_index, da_ind in enumerate(missing_mesh_index):
315
+ def report_missing_mesh(fraction, phase, index=missing_index, label_id=da_ind):
316
+ self.progress.emit(58 + 2 * (index + fraction) / missing_count)
317
+ self.status.emit(
318
+ "Generating fallback mesh {} of {} for structure {}: {}...".format(
319
+ index + 1, missing_count, label_id, phase
320
+ )
321
+ )
322
+
323
+ render_small_volume(
324
+ da_ind,
325
+ mesh_path,
326
+ self.atlas_data,
327
+ self.segmentation_data,
328
+ factor=2,
329
+ level=0.1,
330
+ progress=report_missing_mesh,
331
+ )
332
+
333
+ self.progress.emit(60)
334
+
335
+ self.status.emit("Packing processed structure meshes...")
336
+ file_list = os.listdir(mesh_path)
337
+ progress_step = np.linspace(60, 68, len(file_list))
338
+ file_status_interval = max(1, len(file_list) // 100)
339
+ for i in range(len(file_list)):
340
+ self.progress.emit(progress_step[i])
341
+ if i % file_status_interval == 0 or i == len(file_list) - 1:
342
+ self.status.emit(
343
+ "Packing processed structure meshes: {} of {}...".format(
344
+ i + 1, len(file_list)
345
+ )
346
+ )
347
+ da_file = file_list[i]
348
+ file_name = os.path.basename(da_file)
349
+ da_name, file_extension = os.path.splitext(file_name)
350
+ if file_extension == '.pkl':
351
+ infile = open(os.path.join(mesh_path, da_file), 'rb')
352
+ md = pickle.load(infile)
353
+ infile.close()
354
+
355
+ self.small_mesh_list[str(da_name)] = md
356
+
357
+ self.status.emit("Saving the processed structure-mesh cache...")
358
+ outfile = open(os.path.join(self.saving_folder, 'atlas_small_meshdata.pkl'), 'wb')
359
+ pickle.dump(self.small_mesh_list, outfile)
360
+ outfile.close()
361
+ self.progress.emit(70)
362
+
363
+ segment_data_shape = self.segmentation_data.shape
364
+
365
+ self.status.emit("Allocating atlas boundary volumes...")
366
+ sagital_contour_img = np.zeros(segment_data_shape, dtype=np.uint8)
367
+ coronal_contour_img = np.zeros(segment_data_shape, dtype=np.uint8)
368
+ horizontal_contour_img = np.zeros(segment_data_shape, dtype=np.uint8)
369
+
370
+ # pre-process boundary ----- todo: change this part as optional
371
+ process_index = np.linspace(70, 78, segment_data_shape[0])
372
+ status_interval = max(1, segment_data_shape[0] // 100)
373
+ for i in range(segment_data_shape[0]):
374
+ self.progress.emit(process_index[i])
375
+ if i % status_interval == 0 or i == segment_data_shape[0] - 1:
376
+ self.status.emit(
377
+ "Computing sagittal boundaries: slice {} of {}...".format(
378
+ i + 1, segment_data_shape[0]
379
+ )
380
+ )
381
+ da_slice = self.segmentation_data[i, :, :].copy()
382
+ contour_img = make_contour_img(da_slice)
383
+ sagital_contour_img[i, :, :] = compact_boundary_volume(contour_img)
384
+
385
+ self.status.emit("Saving the sagittal boundary cache...")
386
+ outfile_ct = open(os.path.join(self.saving_folder, 'sagital_contour_pre_made.pkl'), 'wb')
387
+ pickle.dump(sagital_contour_img, outfile_ct)
388
+ outfile_ct.close()
389
+ self.progress.emit(80)
390
+
391
+ process_index = np.linspace(80, 88, segment_data_shape[1])
392
+ status_interval = max(1, segment_data_shape[1] // 100)
393
+ for i in range(segment_data_shape[1]):
394
+ self.progress.emit(process_index[i])
395
+ if i % status_interval == 0 or i == segment_data_shape[1] - 1:
396
+ self.status.emit(
397
+ "Computing coronal boundaries: slice {} of {}...".format(
398
+ i + 1, segment_data_shape[1]
399
+ )
400
+ )
401
+ da_slice = self.segmentation_data[:, i, :].copy()
402
+ contour_img = make_contour_img(da_slice)
403
+ coronal_contour_img[:, i, :] = compact_boundary_volume(contour_img)
404
+
405
+ self.status.emit("Saving the coronal boundary cache...")
406
+ outfile_ct = open(os.path.join(self.saving_folder, 'coronal_contour_pre_made.pkl'), 'wb')
407
+ pickle.dump(coronal_contour_img, outfile_ct)
408
+ outfile_ct.close()
409
+ self.progress.emit(90)
410
+
411
+ process_index = np.linspace(90, 98, segment_data_shape[2])
412
+ status_interval = max(1, segment_data_shape[2] // 100)
413
+ for i in range(segment_data_shape[2]):
414
+ self.progress.emit(process_index[i])
415
+ if i % status_interval == 0 or i == segment_data_shape[2] - 1:
416
+ self.status.emit(
417
+ "Computing horizontal boundaries: slice {} of {}...".format(
418
+ i + 1, segment_data_shape[2]
419
+ )
420
+ )
421
+ da_slice = self.segmentation_data[:, :, i].copy()
422
+ contour_img = make_contour_img(da_slice)
423
+ horizontal_contour_img[:, :, i] = compact_boundary_volume(contour_img)
424
+
425
+ self.status.emit("Saving the horizontal boundary cache...")
426
+ outfile_ct = open(os.path.join(self.saving_folder, 'horizontal_contour_pre_made.pkl'), 'wb')
427
+ pickle.dump(horizontal_contour_img, outfile_ct)
428
+ outfile_ct.close()
429
+
430
+ self.status.emit("Finalizing atlas boundaries...")
431
+ self.boundary = make_boundary_dict(
432
+ sagital_contour_img, coronal_contour_img, horizontal_contour_img
433
+ )
434
+ self.progress.emit(100)
435
+ self.status.emit("Atlas processing complete.")
436
+
437
+ # target = os.path.join(self.saving_folder, 'atlas_labels.pkl')
438
+ # if not os.path.exists(target):
439
+ # shutil.copyfile(join(dirname(__file__), "data/atlas_labels.pkl"), target)
440
+
441
+ # self.progress.emit(100)
442
+
443
+ class MeshDownloader(QObject):
444
+ finished = pyqtSignal()
445
+ progress = pyqtSignal(int, int)
446
+ status = pyqtSignal(str)
447
+ failed = pyqtSignal(str)
448
+
449
+ def __init__(self):
450
+ super(MeshDownloader, self).__init__()
451
+
452
+ self.save_folder = None
453
+ self.segmentation_local = None
454
+ self.success = False
455
+
456
+ def set_data(self, save_folder, segmentation_local):
457
+ self.save_folder = save_folder
458
+ self.segmentation_local = os.path.join(save_folder, segmentation_local)
459
+
460
+ def run(self):
461
+ try:
462
+ downloaded_mesh_path = os.path.join(self.save_folder, 'downloaded_meshes')
463
+ os.makedirs(downloaded_mesh_path, exist_ok=True)
464
+ self.status.emit("Scanning atlas structure IDs...")
465
+ unique_label = [
466
+ int(value)
467
+ for value in _stream_unique_nrrd_values(
468
+ self.segmentation_local, progress=self.progress.emit
469
+ )
470
+ if value not in (0, 545)
471
+ ]
472
+ total_labels = len(unique_label)
473
+
474
+ for index, label_id in enumerate(unique_label):
475
+ self.status.emit(
476
+ "Downloading mesh {} of {} (structure {})...".format(
477
+ index + 1, total_labels, label_id
478
+ )
479
+ )
480
+ url = (
481
+ 'https://download.alleninstitute.org/informatics-archive/'
482
+ 'current-release/mouse_ccf/annotation/ccf_2017/'
483
+ 'structure_meshes/{}.obj'
484
+ ).format(label_id)
485
+ destination = os.path.join(
486
+ downloaded_mesh_path, '{}.obj'.format(label_id)
487
+ )
488
+ progress_maximum = max(1, total_labels * 100)
489
+ if not os.path.isfile(destination) or os.path.getsize(destination) == 0:
490
+ download_file(
491
+ url,
492
+ destination,
493
+ progress=lambda value, base=index: self.progress.emit(
494
+ base * 100 + value, progress_maximum
495
+ ),
496
+ )
497
+ self.progress.emit((index + 1) * 100, progress_maximum)
498
+ self.success = True
499
+ except Exception as exc:
500
+ self.failed.emit(str(exc))
501
+ finally:
502
+ self.finished.emit()
503
+
504
+
505
+ class AllenDownloader(QDialog):
506
+ def __init__(self, *args, **kwargs):
507
+ super().__init__(*args, **kwargs)
508
+ layout = QVBoxLayout(self)
509
+
510
+ self.setWindowTitle("Allen Mice Atlas Downloader")
511
+
512
+ self.thread = QThread()
513
+ self.worker = WorkerProcessAllen()
514
+ self.mesh_thread = QThread()
515
+ self.mesh_worker = MeshDownloader()
516
+
517
+ self.continue_process = True
518
+
519
+ self.voxel_size = 10
520
+ self.bregma_coord = (
521
+ allen_ccf_estimated_bregma_vox(self.voxel_size).tolist()
522
+ )
523
+ radio_group = QFrame()
524
+ radio_group.setStyleSheet('QFrame{border: 1px solid gray; border-radius: 3px}')
525
+ radio_group_layout = QHBoxLayout(radio_group)
526
+ radio_group_layout.setContentsMargins(5, 0, 5, 0)
527
+ radio_group_layout.setAlignment(Qt.AlignmentFlag.AlignCenter)
528
+ self.vs_rabnt1 = QRadioButton('10 um')
529
+ self.vs_rabnt1.setChecked(True)
530
+ self.vs_rabnt2 = QRadioButton('25 um')
531
+ self.vs_rabnt3 = QRadioButton('50 um')
532
+ radio_group_layout.addWidget(self.vs_rabnt1)
533
+ radio_group_layout.addWidget(self.vs_rabnt2)
534
+ radio_group_layout.addWidget(self.vs_rabnt3)
535
+
536
+ self.vs_rabnt1.toggled.connect(self.voxel_size_radio_clicked)
537
+ self.vs_rabnt2.toggled.connect(self.voxel_size_radio_clicked)
538
+ self.vs_rabnt3.toggled.connect(self.voxel_size_radio_clicked)
539
+
540
+ self.data_url = "https://download.alleninstitute.org/informatics-archive/current-release/mouse_ccf/average_template/average_template_10.nrrd"
541
+ self.segmentation_url = "https://download.alleninstitute.org/informatics-archive/current-release/mouse_ccf/annotation/ccf_2017/annotation_10.nrrd"
542
+
543
+ self.label_local = "query.csv"
544
+ self.data_local = "average_template_10.nrrd"
545
+ self.segmentation_local = "annotation_10.nrrd"
546
+
547
+ self.saving_folder = None
548
+
549
+ self.finish = [False, False, False]
550
+ self.process_finished = False
551
+ self.downloading_atlas = False
552
+ self.downloading_meshes = False
553
+ self.download_threads = {}
554
+ self.download_errors = {}
555
+
556
+ self.label_bar = QProgressBar()
557
+ self.label_bar.setMinimumWidth(400)
558
+ self.label_bar.setValue(0)
559
+
560
+ self.data_bar = QProgressBar()
561
+ self.data_bar.setMinimumWidth(400)
562
+ self.data_bar.setValue(0)
563
+
564
+ self.segmentation_bar = QProgressBar()
565
+ self.segmentation_bar.setMinimumWidth(400)
566
+ self.segmentation_bar.setValue(0)
567
+
568
+ self.mesh_bar = QProgressBar()
569
+ self.mesh_bar.setMinimumWidth(400)
570
+ self.mesh_bar.setValue(0)
571
+
572
+ self.download_btn = QPushButton()
573
+ self.download_btn. setMinimumWidth(100)
574
+ self.download_btn.setText("Download")
575
+
576
+ self.download_mesh_btn = QPushButton()
577
+ self.download_mesh_btn.setMinimumWidth(100)
578
+ self.download_mesh_btn.setText("Download Meshes")
579
+ # self.download_mesh_btn.setEnabled(False)
580
+
581
+
582
+ valid_input = QIntValidator(0, 99999)
583
+
584
+ b_wrap = QFrame()
585
+ b_layout = QHBoxLayout(b_wrap)
586
+
587
+ b_label = QLabel('Estimated Bregma voxel (AP, DV, ML): ')
588
+ self.b_input1 = QLineEdit(str(self.bregma_coord[0]))
589
+ self.b_input1.setStyleSheet('color: black')
590
+ self.b_input1.setValidator(valid_input)
591
+ self.b_input1.setAccessibleName('AP Bregma voxel')
592
+ self.b_input1.setToolTip('AP source-atlas voxel')
593
+ self.b_input2 = QLineEdit(str(self.bregma_coord[1]))
594
+ self.b_input2.setStyleSheet('color: black')
595
+ self.b_input2.setValidator(valid_input)
596
+ self.b_input2.setAccessibleName('DV Bregma voxel')
597
+ self.b_input2.setToolTip('DV source-atlas voxel')
598
+ self.b_input3 = QLineEdit(str(self.bregma_coord[2]))
599
+ self.b_input3.setStyleSheet('color: black')
600
+ self.b_input3.setValidator(valid_input)
601
+ self.b_input3.setAccessibleName('ML Bregma voxel')
602
+ self.b_input3.setToolTip('ML source-atlas voxel')
603
+
604
+ b_layout.addWidget(b_label)
605
+ b_layout.addWidget(self.b_input1)
606
+ b_layout.addWidget(self.b_input2)
607
+ b_layout.addWidget(self.b_input3)
608
+
609
+ self.process_btn = QPushButton()
610
+ self.process_btn.setMinimumWidth(100)
611
+ self.process_btn.setText("Process")
612
+
613
+ self.process_info = QLabel('The whole process takes some time. \n'
614
+ 'This window will be closed automatically when processing finished.')
615
+
616
+ self.progress = QProgressBar(self)
617
+ self.progress.setMinimumWidth(100)
618
+ self.progress.setTextVisible(False)
619
+ self.progress_label = QLabel()
620
+
621
+ progress_wrap = QFrame()
622
+ pw_layout = QHBoxLayout(progress_wrap)
623
+ pw_layout.addWidget(self.progress)
624
+ pw_layout.addWidget(self.progress_label)
625
+
626
+ # ok button, used to close window
627
+ ok_btn = QDialogButtonBox(QDialogButtonBox.StandardButton.Ok)
628
+ ok_btn.accepted.connect(self.accept)
629
+
630
+ layout.addWidget(radio_group)
631
+ # layout.addWidget(self.label_bar)
632
+ layout.addWidget(self.data_bar)
633
+ layout.addWidget(self.segmentation_bar)
634
+ layout.addWidget(self.download_btn)
635
+ layout.addWidget(self.mesh_bar)
636
+ layout.addWidget(self.download_mesh_btn)
637
+ layout.addWidget(b_wrap)
638
+ layout.addWidget(self.process_info)
639
+ layout.addWidget(self.process_btn)
640
+ layout.addWidget(progress_wrap)
641
+ # layout.addWidget(ok_btn)
642
+
643
+ # Binding Button Event
644
+ self.download_btn.clicked.connect(self.download_start)
645
+ self.download_mesh_btn.clicked.connect(self.download_mesh_start)
646
+ self.process_btn.clicked.connect(self.process_start)
647
+
648
+ self.b_input1.textChanged.connect(self.bregma_input1_changed)
649
+ self.b_input2.textChanged.connect(self.bregma_input2_changed)
650
+ self.b_input3.textChanged.connect(self.bregma_input3_changed)
651
+
652
+ def bregma_input1_changed(self, text):
653
+ if self.downloading_atlas:
654
+ return
655
+ if text == '':
656
+ return
657
+ self.bregma_coord[0] = int(text)
658
+
659
+ def bregma_input2_changed(self, text):
660
+ if self.downloading_atlas:
661
+ return
662
+ if text == '':
663
+ return
664
+ self.bregma_coord[1] = int(text)
665
+
666
+ def bregma_input3_changed(self, text):
667
+ if self.downloading_atlas:
668
+ return
669
+ if text == '':
670
+ return
671
+ self.bregma_coord[2] = int(text)
672
+
673
+ def set_default_bregma_coordinates(self):
674
+ defaults = allen_ccf_estimated_bregma_vox(self.voxel_size).tolist()
675
+ self.bregma_coord[:] = defaults
676
+ for line_edit, value in zip(
677
+ (self.b_input1, self.b_input2, self.b_input3), defaults
678
+ ):
679
+ line_edit.setText(str(value))
680
+
681
+ def voxel_size_radio_clicked(self):
682
+ if self.vs_rabnt1.isChecked():
683
+ self.voxel_size = 10
684
+ self.data_url = "https://download.alleninstitute.org/informatics-archive/current-release/mouse_ccf/average_template/average_template_10.nrrd"
685
+ self.segmentation_url = "https://download.alleninstitute.org/informatics-archive/current-release/mouse_ccf/annotation/ccf_2017/annotation_10.nrrd"
686
+ self.data_local = "average_template_10.nrrd"
687
+ self.segmentation_local = "annotation_10.nrrd"
688
+ elif self.vs_rabnt2.isChecked():
689
+ self.voxel_size = 25
690
+ self.data_url = 'https://download.alleninstitute.org/informatics-archive/current-release/mouse_ccf/average_template/average_template_25.nrrd'
691
+ self.segmentation_url = 'https://download.alleninstitute.org/informatics-archive/current-release/mouse_ccf/annotation/ccf_2017/annotation_25.nrrd'
692
+ self.data_local = "average_template_25.nrrd"
693
+ self.segmentation_local = "annotation_25.nrrd"
694
+ else:
695
+ self.voxel_size = 50
696
+ self.data_url = 'https://download.alleninstitute.org/informatics-archive/current-release/mouse_ccf/average_template/average_template_50.nrrd'
697
+ self.segmentation_url = 'https://download.alleninstitute.org/informatics-archive/current-release/mouse_ccf/annotation/ccf_2017/annotation_50.nrrd'
698
+ self.data_local = "average_template_50.nrrd"
699
+ self.segmentation_local = "annotation_50.nrrd"
700
+ self.set_default_bregma_coordinates()
701
+
702
+ # Download button event
703
+ def download_start(self):
704
+ self.saving_folder = str(QFileDialog.getExistingDirectory(self, "Select Folder to Save Atlas"))
705
+
706
+ if self.saving_folder != '':
707
+ self.download_btn.setVisible(False)
708
+ self.process_info.setText('')
709
+ self.downloading_atlas = True
710
+ self.vs_rabnt1.setEnabled(False)
711
+ self.vs_rabnt2.setEnabled(False)
712
+ self.vs_rabnt3.setEnabled(False)
713
+
714
+ target = os.path.join(self.saving_folder, self.label_local)
715
+ if not os.path.exists(target):
716
+ shutil.copyfile(join(dirname(__file__), "data/query.csv"), target)
717
+
718
+ self.start_thread(self.segmentation_url, self.segmentation_local, self.set_segmentation_bar_value)
719
+ self.start_thread(self.data_url, self.data_local, self.set_data_bar_value)
720
+
721
+ def download_mesh_start(self):
722
+ if self.has_active_downloads():
723
+ self.process_info.setText('Please wait until the atlas finish downloading.')
724
+ return
725
+
726
+ self.process_info.setText('')
727
+
728
+ if self.saving_folder is not None:
729
+ saving_folder = self.saving_folder
730
+ else:
731
+ saving_folder = str(QFileDialog.getExistingDirectory(self, "Select Atlas Folder"))
732
+
733
+ if saving_folder != '':
734
+ exist_files = os.listdir(saving_folder)
735
+ if self.data_local not in exist_files:
736
+ self.process_info.setText('Atlas Data file is not in the selected folder. Please download atlas.')
737
+ return
738
+ if self.segmentation_local not in exist_files:
739
+ self.process_info.setText('Segmentation Data file is not in the selected folder.')
740
+ return
741
+
742
+ self.download_mesh_btn.setVisible(False)
743
+ self.downloading_meshes = True
744
+ self.download_btn.setEnabled(False)
745
+ self.mesh_bar.setValue(0)
746
+ self.mesh_worker.set_data(saving_folder, self.segmentation_local)
747
+ self.mesh_worker.moveToThread(self.mesh_thread)
748
+ self.mesh_thread.started.connect(self.mesh_worker.run)
749
+ self.mesh_worker.finished.connect(self.mesh_thread.quit)
750
+ self.mesh_worker.finished.connect(self.mesh_download_finished)
751
+ self.mesh_worker.finished.connect(self.mesh_worker.deleteLater)
752
+ self.mesh_thread.finished.connect(self.mesh_thread.deleteLater)
753
+ self.mesh_worker.progress.connect(self.mesh_report_progress)
754
+ self.mesh_worker.status.connect(self.mesh_download_status)
755
+ self.mesh_worker.failed.connect(self.mesh_download_failed)
756
+ self.mesh_thread.start()
757
+
758
+ #
759
+ def start_thread(self, url, local, func):
760
+ destination = os.path.join(self.saving_folder, local)
761
+ self.download_errors.pop(local, None)
762
+ thread = DownloadThread(url, destination, parent=self)
763
+ self.download_threads[local] = thread
764
+ thread.download_process_signal.connect(func)
765
+ thread.download_error_signal.connect(
766
+ lambda message, name=local: self.download_failed(name, message)
767
+ )
768
+ thread.finished.connect(
769
+ lambda name=local: self.download_thread_finished(name)
770
+ )
771
+ thread.start()
772
+
773
+ def download_failed(self, local, message):
774
+ self.download_errors[local] = message
775
+ self.process_info.setText('Download failed for {}: {}'.format(local, message))
776
+
777
+ def download_thread_finished(self, local):
778
+ thread = self.download_threads.pop(local, None)
779
+ if thread is not None:
780
+ thread.deleteLater()
781
+ self.downloading_atlas = self.has_active_downloads()
782
+
783
+ def has_active_downloads(self):
784
+ return any(thread.isRunning() for thread in self.download_threads.values())
785
+
786
+ def mesh_download_failed(self, message):
787
+ self.downloading_meshes = False
788
+ self.process_info.setText('Mesh download failed: {}'.format(message))
789
+
790
+ def mesh_download_finished(self):
791
+ self.downloading_meshes = False
792
+ if self.mesh_worker.success:
793
+ self.finish[2] = True
794
+ self.mesh_bar.setValue(self.mesh_bar.maximum())
795
+ self.process_info.setText('Mesh download finished.')
796
+
797
+ # Setting progress bar
798
+ def set_data_bar_value(self, value):
799
+ self.data_bar.setValue(value)
800
+ if value == 100:
801
+ self.finish[0] = True
802
+ if self.finish[0] and self.finish[1]:
803
+ self.downloading_atlas = False
804
+ return
805
+
806
+ def set_segmentation_bar_value(self, value):
807
+ self.segmentation_bar.setValue(value)
808
+ if value == 100:
809
+ self.finish[1] = True
810
+ if self.finish[0] and self.finish[1]:
811
+ self.downloading_atlas = False
812
+ return
813
+
814
+ def report_progress(self, val):
815
+ val = np.round(val, 2)
816
+ self.progress.setValue(int(val))
817
+ # self.progress.setFormat("%.02f %%" % val)
818
+ self.progress_label.setText("%.02f %%" % val)
819
+
820
+ def mesh_report_progress(self, value, maximum):
821
+ self.mesh_bar.setRange(0, max(1, maximum))
822
+ self.mesh_bar.setValue(value)
823
+
824
+ def mesh_download_status(self, message):
825
+ self.process_info.setText(message)
826
+
827
+ def process_start(self):
828
+ if self.has_active_downloads() or self.downloading_meshes:
829
+ self.process_info.setText('Please wait until finishing downloading files.')
830
+ return
831
+ else:
832
+ self.process_info.setText('')
833
+
834
+ if self.saving_folder is not None:
835
+ saving_folder = self.saving_folder
836
+ else:
837
+ saving_folder = str(QFileDialog.getExistingDirectory(self, "Select Atlas Folder"))
838
+
839
+ if saving_folder != '':
840
+ # check files
841
+ data_path = os.path.join(saving_folder, self.data_local)
842
+ segmentation_path = os.path.join(saving_folder, self.segmentation_local)
843
+ if not os.path.isfile(data_path) or os.path.getsize(data_path) == 0:
844
+ self.process_info.setText('Atlas Data file is not in the selected folder. Please download atlas.')
845
+ return
846
+ if not os.path.isfile(segmentation_path) or os.path.getsize(segmentation_path) == 0:
847
+ self.process_info.setText('Segmentation Data file is not in the selected folder.')
848
+ return
849
+ if not os.path.exists(os.path.join(saving_folder, self.label_local)):
850
+ target = os.path.join(saving_folder, self.label_local)
851
+ if not os.path.exists(target):
852
+ shutil.copyfile(join(dirname(__file__), "data/query.csv"), target)
853
+ mesh_folder = os.path.join(saving_folder, 'downloaded_meshes')
854
+ root_mesh = os.path.join(mesh_folder, '997.obj')
855
+ if not os.path.isfile(root_mesh) or os.path.getsize(root_mesh) == 0:
856
+ self.process_info.setText(
857
+ 'Could not find complete downloaded meshes. Please download meshes.')
858
+ return
859
+ self.process_btn.setVisible(False)
860
+ self.worker.set_data(saving_folder, self.data_local, self.segmentation_local, self.label_local,
861
+ self.bregma_coord, self.voxel_size)
862
+ self.worker.moveToThread(self.thread)
863
+ self.thread.started.connect(self.worker.run)
864
+ self.worker.finished.connect(self.on_finish)
865
+ self.worker.failed.connect(self.process_failed)
866
+ # self.worker.finished.connect(self.worker.deleteLater)
867
+ self.thread.finished.connect(self.thread.deleteLater)
868
+ self.worker.progress.connect(self.report_progress)
869
+ self.worker.status.connect(self.process_status_changed)
870
+ self.thread.start()
871
+
872
+ def on_finish(self):
873
+ self.thread.quit()
874
+ self.process_finished = True
875
+ self.continue_process = True
876
+ self.accept()
877
+
878
+ def process_failed(self, message):
879
+ self.thread.quit()
880
+ self.continue_process = False
881
+ QMessageBox.warning(self, 'Atlas processing failed', message)
882
+ self.reject()
883
+
884
+ def process_status_changed(self, message):
885
+ self.process_info.setText(message)
886
+
887
+ def closeEvent(self, event):
888
+ if self.process_finished:
889
+ event.accept()
890
+ return
891
+ if (
892
+ self.has_active_downloads()
893
+ or self.mesh_thread.isRunning()
894
+ or self.thread.isRunning()
895
+ ):
896
+ QMessageBox.information(
897
+ self, 'Operation in progress', 'Please wait for the active operation to finish.'
898
+ )
899
+ event.ignore()
900
+ return
901
+ reply = QMessageBox.question(self, 'Message',
902
+ "Do you want to leave?", QMessageBox.StandardButton.Yes, QMessageBox.StandardButton.No)
903
+
904
+ if reply == QMessageBox.StandardButton.Yes:
905
+ self.continue_process = False
906
+ event.accept()
907
+ else:
908
+ event.ignore()