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,784 @@
1
+ import os
2
+
3
+
4
+ import pickle
5
+ import nrrd
6
+ import csv
7
+ import nibabel as nib
8
+ import numpy as np
9
+ import pandas as pd
10
+ import cv2
11
+ from PyQt6.QtGui import *
12
+ from PyQt6.QtCore import *
13
+ from PyQt6.QtWidgets import *
14
+ import pyqtgraph.opengl as gl
15
+
16
+ from .uuuuuu import read_qss_file, make_contour_img, read_excel_file, hex2rgb
17
+ from .obj_items import render_volume, render_small_volume
18
+ from .atlas_loader import process_atlas_raw_data, AtlasLoader, check_data_path_and_load
19
+ from .atlas_transform import (
20
+ compact_boundary_volume,
21
+ compact_label_volume,
22
+ normalize_atlas_volume,
23
+ transform_atlas_volumes,
24
+ validate_downsample_factor,
25
+ )
26
+
27
+
28
+ class CustomerAtlasWorker(QObject):
29
+ finished = pyqtSignal()
30
+ progress = pyqtSignal(float)
31
+ error_occur = pyqtSignal(str)
32
+
33
+ def __init__(self):
34
+ super(CustomerAtlasWorker, self).__init__()
35
+ self.saving_folder = None
36
+ self.data_local = None
37
+ self.segmentation_local = None
38
+ self.label_local = None
39
+ self.mask_local = None
40
+ self.b_val = None
41
+ self.l_val = None
42
+ self.vox_size = None
43
+ self.factor = 2
44
+ self.axis_info = None
45
+
46
+ def set_data(
47
+ self,
48
+ saving_folder,
49
+ data_local,
50
+ segmentation_local,
51
+ label_local,
52
+ axis_info,
53
+ b_val,
54
+ vox_size,
55
+ mask_local=None,
56
+ factor=2,
57
+ ):
58
+ self.saving_folder = saving_folder
59
+ self.data_local = data_local
60
+ self.segmentation_local = segmentation_local
61
+ self.label_local = label_local
62
+ self.mask_local = mask_local
63
+ self.b_val = b_val
64
+ self.vox_size = vox_size
65
+ self.axis_info = axis_info
66
+ self.factor = factor
67
+
68
+ def progress_control(self, total_count):
69
+ self.progress.emit(total_count)
70
+
71
+ def run(self):
72
+ self.progress.emit(1)
73
+ if self.vox_size < 1e-4:
74
+ self.error_occur.emit("Please set voxel size.")
75
+ return
76
+ print(self.saving_folder)
77
+ print(self.label_local)
78
+ df, msg = read_excel_file(os.path.join(self.saving_folder, self.label_local))
79
+ if msg is not None:
80
+ self.error_occur.emit(msg)
81
+ return
82
+
83
+ reformat_keys = list(df.columns)
84
+ for i in range(len(reformat_keys)):
85
+ reformat_keys[i] = reformat_keys[i].lower()
86
+ df.columns = reformat_keys
87
+ self.progress.emit(2)
88
+ try:
89
+ da_labels = df["name"].to_numpy(dtype=str, copy=True)
90
+ self.progress.emit(3)
91
+
92
+ da_short_label = df["acronym"].to_numpy(dtype=str, copy=True)
93
+
94
+ except KeyError:
95
+ self.error_occur.emit('Label file missing columns "name" or "acronym".')
96
+ return
97
+ self.progress.emit(4)
98
+
99
+ try:
100
+ levels = []
101
+ structure_id_path = df["structure_id_path"].to_numpy(
102
+ dtype=str, copy=True
103
+ )
104
+ for i in range(len(structure_id_path)):
105
+ da_path = structure_id_path[i]
106
+ da_path_split = da_path.split("/")
107
+ for j in np.arange(len(da_path_split))[::-1]:
108
+ if da_path_split[j] == "":
109
+ da_path_split.pop(j)
110
+ levels.append(len(da_path_split))
111
+ except KeyError:
112
+ self.error_occur.emit('Label file missing columns "structure_id_path".')
113
+ return
114
+ self.progress.emit(5)
115
+
116
+ try:
117
+ hex_colors = df["color_hex_triplet"].to_numpy(dtype=str, copy=True)
118
+ rgb_colors = []
119
+ for i in range(len(hex_colors)):
120
+ r, g, b = hex2rgb(hex_colors[i])
121
+ rgb_colors.append([r, g, b])
122
+ rgb_colors = np.asarray(rgb_colors)
123
+ except KeyError:
124
+ rgb_colors = []
125
+ for i in range(len(da_short_label)):
126
+ r, g, b = np.random.randint(0, 255, 3)
127
+ rgb_colors.append([r, g, b])
128
+ rgb_colors = np.asarray(rgb_colors)
129
+ self.progress.emit(6)
130
+
131
+ try:
132
+ parent = df["parent_id"].fillna(0).to_numpy(dtype=int, copy=True)
133
+ ids = df["id"].to_numpy(dtype=int, copy=True)
134
+ except KeyError:
135
+ self.error_occur.emit(
136
+ 'Label file missing columns "parent_structure_id" or "id".'
137
+ )
138
+ return
139
+
140
+ label_info = {
141
+ "index": ids,
142
+ "label": da_labels,
143
+ "parent": parent,
144
+ "abbrev": da_short_label,
145
+ "color": rgb_colors,
146
+ "level_indicator": levels,
147
+ }
148
+
149
+ with open(os.path.join(self.saving_folder, "atlas_labels.pkl"), "wb") as handle:
150
+ pickle.dump(label_info, handle, protocol=pickle.HIGHEST_PROTOCOL)
151
+ self.progress.emit(9)
152
+
153
+ # laod atlas
154
+ atlas_data, success = check_data_path_and_load(
155
+ os.path.join(self.saving_folder, self.data_local)
156
+ )
157
+ if not success:
158
+ self.error_occur.emit(
159
+ "Failed to load atlas data file. Currently only support for .nii and .nrrd."
160
+ )
161
+ return
162
+ atlas_size = atlas_data.shape
163
+ try:
164
+ self.factor = validate_downsample_factor(self.factor, atlas_size)
165
+ except ValueError as error:
166
+ self.error_occur.emit(str(error))
167
+ return
168
+ self.progress.emit(14)
169
+ # laod segmentation data
170
+ segmentation_data, success = check_data_path_and_load(
171
+ os.path.join(self.saving_folder, self.segmentation_local)
172
+ )
173
+ if not success:
174
+ self.error_occur.emit(
175
+ "Failed to load segmentation data file. Currently only support for .nii and .nrrd."
176
+ )
177
+ return
178
+ self.progress.emit(19)
179
+ if not (segmentation_data.shape == atlas_data.shape):
180
+ self.error_occur.emit("Atlas shape is different than segmentation shape.")
181
+ return
182
+
183
+ if self.mask_local is not None:
184
+ mask_data, success = check_data_path_and_load(
185
+ os.path.join(self.saving_folder, self.mask_local)
186
+ )
187
+ if not success:
188
+ self.error_occur.emit(
189
+ "Failed to load mask data file. Currently only support for .nii and .nrrd."
190
+ )
191
+ return
192
+ self.progress.emit(23)
193
+ if not mask_data.shape == atlas_data.shape:
194
+ self.error_occur.emit("Atlas shape is different than mask shape.")
195
+ return
196
+
197
+ # make segmentation with mask
198
+ progress_step = np.linspace(23, 26, len(mask_data))
199
+ for i in range(len(mask_data)):
200
+ self.progress.emit(progress_step[i])
201
+ segmentation_data[i][mask_data[i] == 0] = 0
202
+ segmentation_data = compact_label_volume(segmentation_data)
203
+
204
+ progress_step = np.linspace(26, 28, len(mask_data))
205
+ for i in range(len(mask_data)):
206
+ self.progress.emit(progress_step[i])
207
+ atlas_data[i][mask_data[i] == 0] = 0
208
+
209
+ atlas_data = normalize_atlas_volume(atlas_data)
210
+ self.progress.emit(30)
211
+
212
+ unique_label = np.unique(segmentation_data)
213
+ n_unique_labels = len(unique_label)
214
+ self.progress.emit(35)
215
+
216
+ atlas_data, segmentation_data, b_val = transform_atlas_volumes(
217
+ atlas_data, segmentation_data, self.b_val, self.axis_info
218
+ )
219
+ self.progress.emit(38)
220
+
221
+ segmentation_data = compact_label_volume(segmentation_data)
222
+ # print(segmentation_data.shape)
223
+ self.progress.emit(39)
224
+
225
+ segment = {"data": segmentation_data, "unique_label": unique_label}
226
+
227
+ outfile = open(os.path.join(self.saving_folder, "segment_pre_made.pkl"), "wb")
228
+ pickle.dump(segment, outfile)
229
+ outfile.close()
230
+ self.progress.emit(42)
231
+
232
+ atlas_info = [
233
+ {
234
+ "name": "anterior",
235
+ "values": np.arange(atlas_data.shape[0]) * self.vox_size,
236
+ "units": "um",
237
+ },
238
+ {
239
+ "name": "dorsal",
240
+ "values": np.arange(atlas_data.shape[1]) * self.vox_size,
241
+ "units": "um",
242
+ },
243
+ {
244
+ "name": "right",
245
+ "values": np.arange(atlas_data.shape[2]) * self.vox_size,
246
+ "units": "um",
247
+ },
248
+ {"vxsize": self.vox_size, "Bregma": [b_val[0], b_val[1], b_val[2]]},
249
+ ]
250
+ self.progress.emit(45)
251
+ atlas = {"data": atlas_data, "info": atlas_info}
252
+
253
+ outfile = open(os.path.join(self.saving_folder, "atlas_pre_made.pkl"), "wb")
254
+ pickle.dump(atlas, outfile)
255
+ outfile.close()
256
+ self.progress.emit(50)
257
+
258
+ mesh_data = render_volume(
259
+ atlas_data, self.saving_folder, factor=self.factor, level=0.1
260
+ )
261
+ self.progress.emit(55)
262
+
263
+ mesh_path = os.path.join(self.saving_folder, "meshes")
264
+ if not os.path.exists(mesh_path):
265
+ os.mkdir(mesh_path)
266
+
267
+ progress_step = np.linspace(55, 68, n_unique_labels)
268
+ for i in range(n_unique_labels):
269
+ self.progress.emit(progress_step[i])
270
+ label_id = int(unique_label[i])
271
+
272
+ if label_id == 0:
273
+ continue
274
+ render_small_volume(
275
+ label_id,
276
+ mesh_path,
277
+ atlas_data,
278
+ segmentation_data,
279
+ factor=self.factor,
280
+ level=0.1,
281
+ )
282
+
283
+ small_mesh_list = {}
284
+ file_list = os.listdir(mesh_path)
285
+ for da_file in file_list:
286
+ file_name = os.path.basename(da_file)
287
+ da_name, file_extension = os.path.splitext(file_name)
288
+ if file_extension == ".pkl":
289
+ infile = open(os.path.join(mesh_path, da_file), "rb")
290
+ md = pickle.load(infile)
291
+ infile.close()
292
+
293
+ small_mesh_list[str(da_name)] = md
294
+
295
+ self.progress.emit(69)
296
+ outfile = open(
297
+ os.path.join(self.saving_folder, "atlas_small_meshdata.pkl"), "wb"
298
+ )
299
+ pickle.dump(small_mesh_list, outfile)
300
+ outfile.close()
301
+ self.progress.emit(70)
302
+
303
+ segment_data_shape = segmentation_data.shape
304
+
305
+ sagital_contour_img = np.zeros(segment_data_shape, dtype=np.uint8)
306
+ coronal_contour_img = np.zeros(segment_data_shape, dtype=np.uint8)
307
+ horizontal_contour_img = np.zeros(segment_data_shape, dtype=np.uint8)
308
+
309
+ # pre-process boundary ----- todo: change this part as optional
310
+ process_index = np.linspace(70, 78, segment_data_shape[0])
311
+ for i in range(segment_data_shape[0]):
312
+ self.progress.emit(process_index[i])
313
+ da_slice = segmentation_data[i, :, :].copy()
314
+ contour_img = make_contour_img(da_slice)
315
+ sagital_contour_img[i, :, :] = compact_boundary_volume(contour_img)
316
+
317
+ outfile_ct = open(
318
+ os.path.join(self.saving_folder, "sagital_contour_pre_made.pkl"), "wb"
319
+ )
320
+ pickle.dump(sagital_contour_img, outfile_ct)
321
+ outfile_ct.close()
322
+ self.progress.emit(80)
323
+
324
+ process_index = np.linspace(80, 88, segment_data_shape[1])
325
+ for i in range(segment_data_shape[1]):
326
+ self.progress.emit(process_index[i])
327
+ da_slice = segmentation_data[:, i, :].copy()
328
+ contour_img = make_contour_img(da_slice)
329
+ coronal_contour_img[:, i, :] = compact_boundary_volume(contour_img)
330
+
331
+ outfile_ct = open(
332
+ os.path.join(self.saving_folder, "coronal_contour_pre_made.pkl"), "wb"
333
+ )
334
+ pickle.dump(coronal_contour_img, outfile_ct)
335
+ outfile_ct.close()
336
+ self.progress.emit(90)
337
+
338
+ process_index = np.linspace(90, 98, segment_data_shape[2])
339
+ for i in range(segment_data_shape[2]):
340
+ self.progress.emit(process_index[i])
341
+ da_slice = segmentation_data[:, :, i].copy()
342
+ contour_img = make_contour_img(da_slice)
343
+ horizontal_contour_img[:, :, i] = compact_boundary_volume(contour_img)
344
+
345
+ outfile_ct = open(
346
+ os.path.join(self.saving_folder, "horizontal_contour_pre_made.pkl"), "wb"
347
+ )
348
+ pickle.dump(horizontal_contour_img, outfile_ct)
349
+ outfile_ct.close()
350
+
351
+ # saving atlas axis changing information
352
+ self.axis_info["size"] = tuple(atlas_size)
353
+ outfile_axis = open(
354
+ os.path.join(self.saving_folder, "atlas_axis_info.pkl"), "wb"
355
+ )
356
+ pickle.dump(self.axis_info, outfile_axis)
357
+ outfile_axis.close()
358
+
359
+ self.progress.emit(100)
360
+
361
+ self.finished.emit()
362
+
363
+
364
+ class AtlasProcessor(QDialog):
365
+ def __init__(self):
366
+ super().__init__()
367
+ qss_file_name = "qss/dialogs.qss"
368
+ qss_style_sheet = read_qss_file(qss_file_name)
369
+ self.setStyleSheet(qss_style_sheet)
370
+ self.setWindowTitle("Atlas Processor")
371
+
372
+ self.folder_path = None
373
+ self.data_local = None
374
+ self.segmentation_local = None
375
+ self.mask_local = None
376
+ self.label_local = None
377
+ self.bregma_coord = [0, 0, 0]
378
+ self.lambda_coord = [0, 0, 0]
379
+ self.voxel_size = 0
380
+ self.factor_val = 2
381
+ self.axis_info = None
382
+ self.directions = [0, 0, 0]
383
+ self.dim_group = [-1, -1, -1]
384
+ self.info_flag = True
385
+
386
+ self.thread = None
387
+ self.worker = None
388
+
389
+ self.selector_vals = [
390
+ "L.H. --> R.H.",
391
+ "R.H. --> L.H.",
392
+ "Post. --> Ant.",
393
+ "Ant. --> Post.",
394
+ "Sup. --> Inf.",
395
+ "Inf. --> Sup.",
396
+ ]
397
+ self.group_maps = np.array([1, 1, 2, 2, 3, 3])
398
+
399
+ layout = QGridLayout(self)
400
+
401
+ box_label_style = read_qss_file("qss/box_label.qss")
402
+ data_label = QLabel("Volume File:")
403
+ self.data_btn = QPushButton("Select File")
404
+ self.data_btn.setAutoDefault(False)
405
+ self.data_btn.setFocus(False)
406
+ self.data_line = QLabel()
407
+ self.data_line.setStyleSheet(box_label_style)
408
+
409
+ seg_label = QLabel("Segmentation File: ")
410
+ self.seg_btn = QPushButton("Select File")
411
+ self.seg_btn.setAutoDefault(False)
412
+ self.seg_btn.setFocus(False)
413
+ self.seg_line = QLabel()
414
+ self.seg_line.setStyleSheet(box_label_style)
415
+
416
+ mask_label = QLabel("Mask File (optional): ")
417
+ self.mask_btn = QPushButton("Select File")
418
+ self.mask_btn.setAutoDefault(False)
419
+ self.mask_btn.setFocus(False)
420
+ self.mask_line = QLabel()
421
+ self.mask_line.setStyleSheet(box_label_style)
422
+
423
+ labinf_label = QLabel("Label Information File:")
424
+ self.labinf_btn = QPushButton("Select File")
425
+ self.labinf_btn.setAutoDefault(False)
426
+ self.labinf_btn.setFocus(False)
427
+ self.labinf_line = QLabel()
428
+ self.labinf_line.setStyleSheet(box_label_style)
429
+
430
+ valid_input = QIntValidator(0, 99999)
431
+
432
+ float_input = QRegularExpressionValidator(
433
+ QRegularExpression(r"[0-9].+")
434
+ )
435
+ # float_input = QDoubleValidator(0.0, 100.0, 6)
436
+
437
+ bregma_label = QLabel("Bregma Coordinates (voxel): ")
438
+ self.bregma_input1 = QLineEdit("0")
439
+ self.bregma_input1.setValidator(valid_input)
440
+ self.bregma_input2 = QLineEdit("0")
441
+ self.bregma_input2.setValidator(valid_input)
442
+ self.bregma_input3 = QLineEdit("0")
443
+ self.bregma_input3.setValidator(valid_input)
444
+
445
+ lambda_label = QLabel("Lambda Coordinates (voxel): ")
446
+ self.lambda_input1 = QLineEdit("0")
447
+ self.lambda_input1.setValidator(valid_input)
448
+ self.lambda_input2 = QLineEdit("0")
449
+ self.lambda_input2.setValidator(valid_input)
450
+ self.lambda_input3 = QLineEdit("0")
451
+ self.lambda_input3.setValidator(valid_input)
452
+
453
+ vox_size_label = QLabel("Voxel Size (um): ")
454
+ self.vox_size_input1 = QLineEdit("0")
455
+ self.vox_size_input1.setValidator(float_input)
456
+
457
+ factor_label = QLabel("Factor (voxel): ")
458
+ self.factor_input1 = QLineEdit("2")
459
+ self.factor_input1.setValidator(QIntValidator(2, 99999))
460
+
461
+ dim_selector_label = QLabel("CSys Selector: ")
462
+
463
+ self.x_axis_combo = QComboBox()
464
+ self.x_axis_combo.addItem("x-axis: ")
465
+ self.x_axis_combo.addItems(self.selector_vals)
466
+ self.x_axis_combo.currentIndexChanged.connect(
467
+ lambda: self.dim_combo_changed("x")
468
+ )
469
+
470
+ self.y_axis_combo = QComboBox()
471
+ self.y_axis_combo.addItem("y-axis: ")
472
+ self.y_axis_combo.addItems(self.selector_vals)
473
+ self.y_axis_combo.currentIndexChanged.connect(
474
+ lambda: self.dim_combo_changed("y")
475
+ )
476
+
477
+ self.z_axis_combo = QComboBox()
478
+ self.z_axis_combo.addItem("z-axis: ")
479
+ self.z_axis_combo.addItems(self.selector_vals)
480
+ self.z_axis_combo.currentIndexChanged.connect(
481
+ lambda: self.dim_combo_changed("z")
482
+ )
483
+
484
+ self.process_btn = QPushButton("Start Process")
485
+ self.process_btn.setAutoDefault(False)
486
+ self.process_btn.setFocus(False)
487
+ self.process_info = QLabel(
488
+ "The whole process takes some time. \n"
489
+ "This window will be closed automatically when processing finished."
490
+ )
491
+
492
+ self.progress = QProgressBar(self)
493
+ self.progress.setMinimumWidth(100)
494
+ self.progress.setTextVisible(False)
495
+ self.progress_label = QLabel("0 %")
496
+ self.progress_label.setFixedWidth(50)
497
+
498
+ progress_wrap = QFrame()
499
+ pw_layout = QHBoxLayout(progress_wrap)
500
+ pw_layout.setSpacing(5)
501
+ pw_layout.setContentsMargins(0, 0, 0, 0)
502
+ pw_layout.setAlignment(Qt.AlignmentFlag.AlignRight)
503
+ pw_layout.addWidget(self.progress)
504
+ pw_layout.addWidget(self.progress_label)
505
+
506
+ layout.addWidget(data_label, 0, 0, 1, 1)
507
+ layout.addWidget(self.data_btn, 0, 1, 1, 1)
508
+ layout.addWidget(self.data_line, 0, 2, 1, 2)
509
+ layout.addWidget(seg_label, 1, 0, 1, 1)
510
+ layout.addWidget(self.seg_btn, 1, 1, 1, 1)
511
+ layout.addWidget(self.seg_line, 1, 2, 1, 2)
512
+ layout.addWidget(mask_label, 2, 0, 1, 1)
513
+ layout.addWidget(self.mask_btn, 2, 1, 1, 1)
514
+ layout.addWidget(self.mask_line, 2, 2, 1, 2)
515
+ layout.addWidget(labinf_label, 3, 0, 1, 1)
516
+ layout.addWidget(self.labinf_btn, 3, 1, 1, 1)
517
+ layout.addWidget(self.labinf_line, 3, 2, 1, 2)
518
+ layout.addWidget(bregma_label, 4, 0, 1, 1)
519
+ layout.addWidget(self.bregma_input1, 4, 1, 1, 1)
520
+ layout.addWidget(self.bregma_input2, 4, 2, 1, 1)
521
+ layout.addWidget(self.bregma_input3, 4, 3, 1, 1)
522
+ layout.addWidget(lambda_label, 5, 0, 1, 1)
523
+ layout.addWidget(self.lambda_input1, 5, 1, 1, 1)
524
+ layout.addWidget(self.lambda_input2, 5, 2, 1, 1)
525
+ layout.addWidget(self.lambda_input3, 5, 3, 1, 1)
526
+ layout.addWidget(vox_size_label, 6, 0, 1, 1)
527
+ layout.addWidget(self.vox_size_input1, 6, 1, 1, 1)
528
+ layout.addWidget(factor_label, 6, 2, 1, 1)
529
+ layout.addWidget(self.factor_input1, 6, 3, 1, 1)
530
+
531
+ layout.addWidget(dim_selector_label, 7, 0, 1, 1)
532
+ layout.addWidget(self.x_axis_combo, 7, 1, 1, 1)
533
+ layout.addWidget(self.y_axis_combo, 7, 2, 1, 1)
534
+ layout.addWidget(self.z_axis_combo, 7, 3, 1, 1)
535
+
536
+ layout.addWidget(self.process_info, 8, 0, 1, 4)
537
+ layout.addWidget(self.process_btn, 9, 0, 1, 4)
538
+ layout.addWidget(progress_wrap, 10, 0, 1, 4)
539
+
540
+ # connect all buttons
541
+ self.data_btn.clicked.connect(self.get_data_file)
542
+ self.seg_btn.clicked.connect(self.get_seg_file)
543
+ self.mask_btn.clicked.connect(self.get_mask_file)
544
+ self.labinf_btn.clicked.connect(self.get_info_file)
545
+ self.process_btn.clicked.connect(self.process_data_called)
546
+ self.bregma_input1.textChanged.connect(self.bregma_input1_changed)
547
+ self.bregma_input2.textChanged.connect(self.bregma_input2_changed)
548
+ self.bregma_input3.textChanged.connect(self.bregma_input3_changed)
549
+ self.lambda_input1.textChanged.connect(self.lambda_input1_changed)
550
+ self.lambda_input2.textChanged.connect(self.lambda_input2_changed)
551
+ self.lambda_input3.textChanged.connect(self.lambda_input3_changed)
552
+ self.vox_size_input1.textChanged.connect(self.vox_size_input_changed)
553
+ self.factor_input1.textChanged.connect(self.factor_input_changed)
554
+
555
+ def dim_combo_changed(self, ax):
556
+ if not self.info_flag:
557
+ self.process_info.setText("")
558
+ if ax == "x":
559
+ self.directions[0] = self.x_axis_combo.currentIndex()
560
+ elif ax == "y":
561
+ self.directions[1] = self.y_axis_combo.currentIndex()
562
+ else:
563
+ self.directions[2] = self.z_axis_combo.currentIndex()
564
+
565
+ if np.all(np.ravel(self.directions) != 0):
566
+ dir_groups = self.group_maps[np.array(self.directions) - 1]
567
+
568
+ dir_goal = ["Post. --> Ant.", "Inf. --> Sup.", "L.H. --> R.H."]
569
+ direction_change = [False, False, False]
570
+ if self.x_axis_combo.currentText() not in dir_goal:
571
+ direction_change[0] = True
572
+ if self.y_axis_combo.currentText() not in dir_goal:
573
+ direction_change[1] = True
574
+ if self.z_axis_combo.currentText() not in dir_goal:
575
+ direction_change[2] = True
576
+
577
+ transpose_order = dir_groups - 1
578
+ transpose_order = transpose_order.tolist()
579
+
580
+ if transpose_order == [1, 2, 0]:
581
+ inv_transpose_order = [2, 0, 1]
582
+ elif transpose_order == [2, 0, 1]:
583
+ inv_transpose_order = [1, 2, 0]
584
+ else:
585
+ inv_transpose_order = transpose_order.copy()
586
+
587
+ self.axis_info = {
588
+ "to_HERBS": tuple(inv_transpose_order),
589
+ "from_HERBS": tuple(transpose_order),
590
+ "direction_change": tuple(direction_change),
591
+ }
592
+
593
+ print(self.axis_info)
594
+
595
+ def get_folder_path(self, file_path):
596
+ self.folder_path = os.path.dirname(file_path)
597
+
598
+ def get_data_file(self):
599
+ file_options = QFileDialog.Option(0)
600
+ file_options |= QFileDialog.Option.DontUseNativeDialog
601
+ dlg = QFileDialog()
602
+ if self.folder_path is not None:
603
+ data_path = dlg.getOpenFileName(
604
+ self, "Select Atlas Volume File", self.folder_path, options=file_options
605
+ )
606
+ else:
607
+ data_path = dlg.getOpenFileName(
608
+ self, "Select Atlas Volume File", options=file_options
609
+ )
610
+ self.get_folder_path(data_path[0])
611
+ self.data_local = os.path.basename(data_path[0])
612
+ self.data_line.setText(self.data_local)
613
+
614
+ def get_seg_file(self):
615
+ file_options = QFileDialog.Option(0)
616
+ file_options |= QFileDialog.Option.DontUseNativeDialog
617
+ dlg = QFileDialog()
618
+ if self.folder_path is not None:
619
+ seg_path = dlg.getOpenFileName(
620
+ self, "Select Segmentation File", self.folder_path, options=file_options
621
+ )
622
+ else:
623
+ seg_path = dlg.getOpenFileName(
624
+ self, "Select Segmentation File", options=file_options
625
+ )
626
+ self.get_folder_path(seg_path[0])
627
+ self.segmentation_local = os.path.basename(seg_path[0])
628
+ self.seg_line.setText(self.segmentation_local)
629
+
630
+ def get_mask_file(self):
631
+ file_options = QFileDialog.Option(0)
632
+ file_options |= QFileDialog.Option.DontUseNativeDialog
633
+ dlg = QFileDialog()
634
+ if self.folder_path is not None:
635
+ mask_path = dlg.getOpenFileName(
636
+ self, "Select Mask File", self.folder_path, options=file_options
637
+ )
638
+ else:
639
+ mask_path = dlg.getOpenFileName(
640
+ self, "Select Mask File", options=file_options
641
+ )
642
+ self.get_folder_path(mask_path[0])
643
+ self.mask_local = os.path.basename(mask_path[0])
644
+ self.mask_line.setText(self.mask_local)
645
+
646
+ def get_info_file(self):
647
+ file_options = QFileDialog.Option(0)
648
+ file_options |= QFileDialog.Option.DontUseNativeDialog
649
+ dlg = QFileDialog()
650
+ if self.folder_path is not None:
651
+ info_path = dlg.getOpenFileName(
652
+ self, "Select Label File", self.folder_path, options=file_options
653
+ )
654
+ else:
655
+ info_path = dlg.getOpenFileName(
656
+ self, "Select Label File", options=file_options
657
+ )
658
+ self.get_folder_path(info_path[0])
659
+ self.label_local = os.path.basename(info_path[0])
660
+ self.labinf_line.setText(os.path.basename(self.label_local))
661
+
662
+ def bregma_input1_changed(self, text):
663
+ if text == "":
664
+ return
665
+ self.bregma_coord[0] = int(text)
666
+
667
+ def bregma_input2_changed(self, text):
668
+ if text == "":
669
+ return
670
+ self.bregma_coord[1] = int(text)
671
+
672
+ def bregma_input3_changed(self, text):
673
+ if text == "":
674
+ return
675
+ self.bregma_coord[2] = int(text)
676
+
677
+ def lambda_input1_changed(self, text):
678
+ if text == "":
679
+ return
680
+ self.lambda_coord[0] = int(text)
681
+
682
+ def lambda_input2_changed(self, text):
683
+ if text == "":
684
+ return
685
+ self.lambda_coord[1] = int(text)
686
+
687
+ def lambda_input3_changed(self, text):
688
+ if text == "":
689
+ return
690
+ self.lambda_coord[2] = int(text)
691
+
692
+ def vox_size_input_changed(self, text):
693
+ if text == "":
694
+ return
695
+ self.voxel_size = float(text)
696
+
697
+ def factor_input_changed(self, text):
698
+ if text == "":
699
+ return
700
+ self.factor_val = int(text)
701
+
702
+ def check_empty_file(self):
703
+ if self.data_local is None:
704
+ msg = "Please select atlas data."
705
+ return msg
706
+ if self.segmentation_local is None:
707
+ msg = "Please select segmentation file."
708
+ return msg
709
+ if self.label_local is None:
710
+ msg = "Please select label information file."
711
+ return msg
712
+ return None
713
+
714
+ def process_data_called(self):
715
+ if np.any(np.array(self.directions) == 0):
716
+ self.info_flag = False
717
+ self.process_info.setText("Please select directions for each axis.")
718
+ return
719
+ dir_groups = self.group_maps[np.array(self.directions) - 1]
720
+ if len(np.unique(dir_groups)) != 3:
721
+ self.info_flag = False
722
+ self.process_info.setText("Axis directions can not duplicat.")
723
+ return
724
+
725
+ msg = self.check_empty_file()
726
+ if msg is not None:
727
+ self.process_info.setText("Please select file path.")
728
+ return
729
+
730
+ dir_goal = ["Post. --> Ant.", "Inf. --> Sup.", "L.H. --> R.H."]
731
+ direction_change = [False, False, False]
732
+ if self.x_axis_combo.currentText() not in dir_goal:
733
+ direction_change[0] = True
734
+ if self.y_axis_combo.currentText() not in dir_goal:
735
+ direction_change[1] = True
736
+ if self.z_axis_combo.currentText() not in dir_goal:
737
+ direction_change[2] = True
738
+
739
+ transpose_order = dir_groups - 1
740
+
741
+ self.process_btn.setVisible(False)
742
+ self.process_info.setText(
743
+ "The whole process takes some time. \n"
744
+ "This window will be closed automatically when processing finished."
745
+ )
746
+
747
+ self.thread = QThread()
748
+ self.worker = CustomerAtlasWorker()
749
+ self.worker.set_data(
750
+ self.folder_path,
751
+ self.data_local,
752
+ self.segmentation_local,
753
+ self.label_local,
754
+ self.axis_info,
755
+ self.bregma_coord,
756
+ self.voxel_size,
757
+ self.mask_local,
758
+ self.factor_val,
759
+ )
760
+ self.worker.moveToThread(self.thread)
761
+ self.thread.started.connect(self.worker.run)
762
+ self.worker.finished.connect(self.on_finish)
763
+ self.worker.error_occur.connect(self.report_error)
764
+ # self.worker.finished.connect(self.worker.deleteLater)
765
+ self.thread.finished.connect(self.thread.deleteLater)
766
+ self.worker.progress.connect(self.report_progress)
767
+ self.thread.start()
768
+
769
+ def report_error(self, msg):
770
+ self.thread.quit()
771
+ self.process_btn.setVisible(True)
772
+ self.progress.setValue(int(0))
773
+ self.progress_label.setText("%.02f %%" % 0)
774
+ self.process_info.setText(msg)
775
+
776
+ def report_progress(self, val):
777
+ val = np.round(val, 2)
778
+ self.progress.setValue(int(val))
779
+ # self.progress.setFormat("%.02f %%" % val)
780
+ self.progress_label.setText("%.02f %%" % val)
781
+
782
+ def on_finish(self):
783
+ self.thread.quit()
784
+ self.close()