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,1442 @@
1
+ import os
2
+ import numpy as np
3
+ import math
4
+ import pandas as pd
5
+ import cv2
6
+ import pickle
7
+ import colorsys
8
+ import pyqtgraph as pg
9
+ from scipy.interpolate import interp1d, splprep, splev
10
+ from .uuuuuu import rotation_z, rotation_x, rotation_y
11
+ from .coordinate_validation import (
12
+ coordinate_groups_in_bounds,
13
+ coordinates_in_bounds,
14
+ )
15
+ from .probe_reconstruction import build_probe_reconstruction
16
+ from .version import __version__
17
+
18
+
19
+ PROBE_COORDINATES_OUTSIDE_ATLAS = 16
20
+ _LINE_SAMPLE_STEP_VOX = 0.25
21
+
22
+
23
+ def get_closest_point_to_line(p0, r, p):
24
+ ap = p - p0
25
+ res = p0 + np.dot(ap, r) * r
26
+ return res
27
+
28
+
29
+ def _validate_line_points(points):
30
+ points = np.asarray(points, dtype=float)
31
+ if points.ndim != 2 or points.shape[1] not in (2, 3):
32
+ raise ValueError("Probe points must have shape (N, 2) or (N, 3).")
33
+ if len(points) < 2:
34
+ raise ValueError("At least two probe points are required.")
35
+ if not np.all(np.isfinite(points)):
36
+ raise ValueError("Probe points must contain only finite coordinates.")
37
+ if np.max(np.linalg.norm(points - points[0], axis=1)) <= 1e-9:
38
+ raise ValueError("Probe points must contain two distinct locations.")
39
+ return points
40
+
41
+
42
+ def _principal_direction(points, weights=None):
43
+ if weights is None:
44
+ center = np.mean(points, axis=0)
45
+ centered = points - center
46
+ else:
47
+ center = np.average(points, axis=0, weights=weights)
48
+ centered = (points - center) * np.sqrt(weights[:, None])
49
+ _u, singular_values, vh = np.linalg.svd(centered, full_matrices=False)
50
+ direction = vh[0]
51
+ direction = direction / np.linalg.norm(direction)
52
+ return center, direction, singular_values
53
+
54
+
55
+ def _robust_initial_line(points):
56
+ point_count = len(points)
57
+ if point_count > 64:
58
+ candidates = np.unique(
59
+ np.linspace(0, point_count - 1, 64).astype(int)
60
+ )
61
+ else:
62
+ candidates = np.arange(point_count)
63
+ best = None
64
+ for first_pos, first in enumerate(candidates[:-1]):
65
+ for second in candidates[first_pos + 1 :]:
66
+ vector = points[second] - points[first]
67
+ length = np.linalg.norm(vector)
68
+ if length <= 1e-9:
69
+ continue
70
+ direction = vector / length
71
+ delta = points - points[first]
72
+ residual = np.linalg.norm(
73
+ delta - (delta @ direction)[:, None] * direction,
74
+ axis=1,
75
+ )
76
+ score = (
77
+ float(np.median(residual)),
78
+ float(np.mean(np.partition(residual, point_count // 2)[
79
+ : point_count // 2 + 1
80
+ ])),
81
+ )
82
+ if best is None or score < best[0]:
83
+ best = (score, points[first], direction)
84
+ if best is None:
85
+ raise ValueError("Probe points cannot define a line.")
86
+ return best[1], best[2]
87
+
88
+
89
+ def robust_probe_line_fit(points):
90
+ """Fit an outlier-resistant principal line and return diagnostics."""
91
+ points = _validate_line_points(points)
92
+ initial_origin, direction = _robust_initial_line(points)
93
+ center = initial_origin
94
+ inliers = np.ones(len(points), dtype=bool)
95
+ for _iteration in range(8):
96
+ projection = (points - center) @ direction
97
+ residual_vectors = points - center - projection[:, None] * direction
98
+ residual = np.linalg.norm(residual_vectors, axis=1)
99
+ median = float(np.median(residual))
100
+ mad = 1.4826 * float(np.median(np.abs(residual - median)))
101
+ threshold = max(median + 3.0 * mad, 1.0)
102
+ new_inliers = residual <= threshold
103
+ if np.count_nonzero(new_inliers) < 2:
104
+ break
105
+ center, direction, _singular_values = _principal_direction(
106
+ points[new_inliers]
107
+ )
108
+ if np.array_equal(new_inliers, inliers):
109
+ inliers = new_inliers
110
+ break
111
+ inliers = new_inliers
112
+
113
+ projection = (points - center) @ direction
114
+ residual_vectors = points - center - projection[:, None] * direction
115
+ residual = np.linalg.norm(residual_vectors, axis=1)
116
+ median = float(np.median(residual))
117
+ mad = 1.4826 * float(np.median(np.abs(residual - median)))
118
+ inlier_threshold = max(median + 3.0 * mad, 1.0)
119
+ inliers = residual <= inlier_threshold
120
+ if np.count_nonzero(inliers) >= 2:
121
+ center, direction, singular_values = _principal_direction(
122
+ points[inliers]
123
+ )
124
+ else:
125
+ inliers = np.ones(len(points), dtype=bool)
126
+ center, direction, singular_values = _principal_direction(points)
127
+
128
+ projection = (points - center) @ direction
129
+ residual_vectors = points - center - projection[:, None] * direction
130
+ residual = np.linalg.norm(residual_vectors, axis=1)
131
+ inlier_projection = projection[inliers]
132
+ endpoint_1 = center + np.min(inlier_projection) * direction
133
+ endpoint_2 = center + np.max(inlier_projection) * direction
134
+
135
+ if points.shape[1] == 2:
136
+ if endpoint_1[1] > endpoint_2[1]:
137
+ endpoint_1, endpoint_2 = endpoint_2, endpoint_1
138
+ elif endpoint_1[2] < endpoint_2[2]:
139
+ endpoint_1, endpoint_2 = endpoint_2, endpoint_1
140
+ direction = endpoint_2 - endpoint_1
141
+ direction = direction / np.linalg.norm(direction)
142
+
143
+ inlier_residual = residual[inliers]
144
+ total_variance = float(
145
+ np.sum((points[inliers] - np.mean(points[inliers], axis=0)) ** 2)
146
+ )
147
+ residual_variance = float(np.sum(inlier_residual**2))
148
+ explained_fraction = (
149
+ max(0.0, min(1.0, 1.0 - residual_variance / total_variance))
150
+ if total_variance > np.finfo(float).eps
151
+ else 1.0
152
+ )
153
+ diagnostics = {
154
+ "method": "robust orthogonal 3D line fit",
155
+ "point_count": int(len(points)),
156
+ "inlier_count": int(np.count_nonzero(inliers)),
157
+ "inlier_mask": inliers,
158
+ "residual_vox": residual,
159
+ "rms_error_vox": float(np.sqrt(np.mean(inlier_residual**2))),
160
+ "max_error_vox": float(np.max(residual)),
161
+ "explained_fraction": explained_fraction,
162
+ "singular_values": singular_values,
163
+ }
164
+ return endpoint_1, endpoint_2, center, direction, diagnostics
165
+
166
+
167
+ def _line_box_interval(origin, direction, shape):
168
+ lower = -np.inf
169
+ upper = np.inf
170
+ for axis, axis_size in enumerate(shape):
171
+ if abs(direction[axis]) <= np.finfo(float).eps:
172
+ if origin[axis] < 0 or origin[axis] > axis_size - 1:
173
+ return None
174
+ continue
175
+ first = (0.0 - origin[axis]) / direction[axis]
176
+ last = (float(axis_size - 1) - origin[axis]) / direction[axis]
177
+ lower = max(lower, min(first, last))
178
+ upper = min(upper, max(first, last))
179
+ if not np.isfinite(lower) or not np.isfinite(upper) or lower > upper:
180
+ return None
181
+ return lower, upper
182
+
183
+
184
+ def _first_occupied_point(origin, direction, shape, is_occupied):
185
+ interval = _line_box_interval(origin, direction, shape)
186
+ if interval is None:
187
+ return None
188
+ lower, upper = interval
189
+ sample_count = max(
190
+ 2, int(np.ceil((upper - lower) / _LINE_SAMPLE_STEP_VOX)) + 1
191
+ )
192
+ distances = np.linspace(lower, upper, sample_count)
193
+ coordinates = origin + distances[:, None] * direction
194
+ indexes = np.floor(coordinates).astype(int)
195
+ indexes = np.clip(indexes, 0, np.asarray(shape, dtype=int) - 1)
196
+ occupied = np.asarray(is_occupied(indexes), dtype=bool)
197
+ occupied_indexes = np.flatnonzero(occupied)
198
+ if not len(occupied_indexes):
199
+ return None
200
+ first = int(occupied_indexes[0])
201
+ return coordinates[first]
202
+
203
+
204
+ def line_fit_2d(points_2d, image=None):
205
+ msg = None
206
+ sp, ep, center, direction, _diagnostics = robust_probe_line_fit(
207
+ points_2d
208
+ )
209
+ if image is not None:
210
+ image = np.asarray(image)
211
+ if image.ndim != 2:
212
+ return np.asarray([sp, ep]), "Atlas labels must be two-dimensional."
213
+
214
+ def occupied(indexes):
215
+ return image[indexes[:, 1], indexes[:, 0]] != 0
216
+
217
+ surface = _first_occupied_point(
218
+ center,
219
+ direction,
220
+ (image.shape[1], image.shape[0]),
221
+ occupied,
222
+ )
223
+ if surface is None:
224
+ msg = "The fitted probe line does not intersect the atlas brain mask."
225
+ else:
226
+ sp = surface
227
+ return np.asarray([sp, ep]), msg
228
+
229
+
230
+ def line_fit(points, return_diagnostics=False):
231
+ result = robust_probe_line_fit(points)
232
+ if return_diagnostics:
233
+ return result
234
+ return result[:4]
235
+
236
+
237
+ def find_probe_surface_entry(label_data, center, direction, bregma):
238
+ """Find the first labeled atlas voxel along the fitted insertion line."""
239
+ label_data = np.asarray(label_data)
240
+ absolute_center = np.asarray(center, dtype=float) + np.asarray(
241
+ bregma, dtype=float
242
+ )
243
+
244
+ def occupied(indexes):
245
+ return label_data[indexes[:, 0], indexes[:, 1], indexes[:, 2]] != 0
246
+
247
+ surface = _first_occupied_point(
248
+ absolute_center,
249
+ np.asarray(direction, dtype=float),
250
+ label_data.shape,
251
+ occupied,
252
+ )
253
+ if surface is None:
254
+ return np.asarray(center, dtype=float), PROBE_COORDINATES_OUTSIDE_ATLAS
255
+ return surface - np.asarray(bregma, dtype=float), 0
256
+
257
+
258
+ def get_angles(direction):
259
+ direction = direction / np.linalg.norm(direction)
260
+
261
+ vertical_vec = np.array([0, 0, 1])
262
+ ap_proj = direction.copy()
263
+ ap_proj[0] = 0
264
+ ap_proj = ap_proj / np.linalg.norm(ap_proj)
265
+ ml_proj = direction.copy()
266
+ ml_proj[1] = 0
267
+ ml_proj = ml_proj / np.linalg.norm(ml_proj)
268
+
269
+ ap_val = math.acos(np.max([np.min([np.dot(ap_proj, vertical_vec), 1]), -1]))
270
+ ml_val = math.acos(np.max([np.min([np.dot(ml_proj, vertical_vec), 1]), -1]))
271
+
272
+ ap_angle = np.degrees(ap_val)
273
+ ml_angle = np.degrees(ml_val)
274
+
275
+ if ap_angle > 90:
276
+ ap_angle = 180 - ap_angle
277
+ if ml_angle > 90:
278
+ ml_angle = 180 - ml_angle
279
+
280
+ return ap_angle, ml_angle
281
+
282
+
283
+ def get_tilt_info(sp, ep):
284
+ if ep[1] < sp[1]:
285
+ ap_tilt = "posterior"
286
+ elif ep[1] > sp[1]:
287
+ ap_tilt = "anterior"
288
+ else:
289
+ ap_tilt = "no tilt"
290
+
291
+ if sp[0] > 0:
292
+ if ep[0] < sp[0]:
293
+ ml_tilt = "medial"
294
+ elif ep[0] > sp[0]:
295
+ ml_tilt = "lateral"
296
+ else:
297
+ ml_tilt = "no tilt"
298
+ elif sp[0] < 0:
299
+ if ep[0] < sp[0]:
300
+ ml_tilt = "lateral"
301
+ elif ep[0] > sp[0]:
302
+ ml_tilt = "medial"
303
+ else:
304
+ ml_tilt = "no tilt"
305
+ else:
306
+ if ep[0] != sp[0]:
307
+ ml_tilt = "lateral"
308
+ else:
309
+ ml_tilt = "no tilt"
310
+ return ap_tilt, ml_tilt
311
+
312
+
313
+ def get_tilt_sign(sp, ep):
314
+ if ep[1] < sp[1]:
315
+ ap_tilt = -1
316
+ elif ep[1] > sp[1]:
317
+ ap_tilt = 1
318
+ else:
319
+ ap_tilt = 0
320
+
321
+ if ep[0] < sp[0]:
322
+ ml_tilt = -1
323
+ elif ep[0] > sp[0]:
324
+ ml_tilt = 1
325
+ else:
326
+ ml_tilt = 0
327
+ return ap_tilt, ml_tilt
328
+
329
+
330
+ def pandas_to_str(label_name, label_ano, length, channels):
331
+ df = pd.DataFrame(
332
+ {
333
+ "Brain Regions": label_name,
334
+ "Ano": label_ano,
335
+ "Probe Length": length,
336
+ "Probe Channels": channels,
337
+ }
338
+ )
339
+ return df.to_string(col_space=30, justify="justify")
340
+
341
+
342
+ def correct_start_pnt(
343
+ label_data, start_pnt, start_vox, direction, bregma, verbose=False
344
+ ):
345
+ error_index = 0
346
+ # print('start_vox', start_vox)
347
+ direction = direction / np.linalg.norm(direction)
348
+ if not coordinates_in_bounds(start_vox, label_data.shape):
349
+ return start_pnt.copy(), PROBE_COORDINATES_OUTSIDE_ATLAS
350
+ check_vox = start_vox.astype(int)
351
+
352
+ check_vec = label_data[check_vox[0], check_vox[1], :]
353
+ top_vox = np.where(check_vec != 0)[0]
354
+ # print(np.where(check_vec != 0))
355
+ if len(top_vox) == 0:
356
+ return start_pnt.copy(), PROBE_COORDINATES_OUTSIDE_ATLAS
357
+ else:
358
+ top_vox = top_vox[-1]
359
+ new_sp = start_pnt.copy()
360
+
361
+ # if int(start_vox[2]) < top_vox:
362
+ # steps = 0
363
+ # while label_data[check_vox[0], check_vox[1], check_vox[2]] != 0:
364
+ # steps += 1
365
+ # new_sp = start_pnt - steps * direction
366
+ # check_vox = new_sp + bregma
367
+ # check_vox = check_vox.astype(int)
368
+ # if steps == 1000:
369
+ # raise Exception('higher limit, please contact maintainer')
370
+ # elif int(start_vox[2]) > top_vox:
371
+ # steps = 0
372
+ # while label_data[check_vox[0], check_vox[1], check_vox[2]] == 0:
373
+ # steps += 1
374
+ # new_sp = start_pnt + steps * direction
375
+ # check_vox = new_sp + bregma
376
+ # check_vox = check_vox.astype(int)
377
+ # if steps == 1000:
378
+ # raise Exception('higher limit, please contact maintainer')
379
+ # else:
380
+ # new_sp = start_pnt
381
+ enter_label = label_data[check_vox[0], check_vox[1], check_vox[2]]
382
+ z_diff = new_sp[2] + bregma[2] - top_vox
383
+ if z_diff >= 1:
384
+ sign_flag = -1
385
+ if enter_label != 0:
386
+ error_index = 10
387
+ return new_sp, error_index
388
+ elif z_diff < 0:
389
+ sign_flag = 1
390
+ if enter_label == 0:
391
+ error_index = 11
392
+ return new_sp, error_index
393
+ else:
394
+ sign_flag = 0
395
+
396
+ # ToDo: the following code only works when there is no 0 label inside the brain (label data)
397
+ # that means label 0 only indicates the area outside the brain
398
+ # if some atlas uses label 0 to indicate the unspecified area inside the brain, this code may have problems
399
+ # should make a pseudo label index (xxxx) inside the brain for unspecified regions ===> need to re-process atlas
400
+
401
+ stop_steps = 0
402
+ if sign_flag != 0:
403
+ enter_condition = (enter_label == 0) if z_diff >= 1 else (enter_label != 0)
404
+ while enter_condition:
405
+ stop_steps += 1
406
+ new_sp = start_pnt - sign_flag * stop_steps * direction
407
+ check_vox = new_sp + bregma
408
+ check_vox = check_vox.astype(int)
409
+ if not coordinates_in_bounds(check_vox, label_data.shape):
410
+ error_index = PROBE_COORDINATES_OUTSIDE_ATLAS
411
+ break
412
+ enter_label = label_data[check_vox[0], check_vox[1], check_vox[2]]
413
+ enter_condition = (enter_label == 0) if z_diff >= 1 else (enter_label != 0)
414
+ if error_index != 0:
415
+ return new_sp, error_index
416
+
417
+ new_sp = (
418
+ start_pnt - sign_flag * (stop_steps - 1) * direction
419
+ if z_diff < 0
420
+ else new_sp
421
+ )
422
+ if verbose:
423
+ print("correct enter pnt with {} steps".format(stop_steps))
424
+ # print('old sp', start_pnt)
425
+ # print('new pc_sp', new_sp)
426
+
427
+ return new_sp, error_index
428
+
429
+
430
+ def correct_end_point(
431
+ sp, ep, direction, vox_size, tip_length, max_probe_length, verbose=False
432
+ ):
433
+ probe_length_with_tip = np.sqrt(np.sum((sp - ep) ** 2)) * vox_size # in um
434
+ probe_length_without_tip = probe_length_with_tip - tip_length # in um
435
+ if max_probe_length is not None:
436
+ if probe_length_with_tip > max_probe_length:
437
+ probe_length_with_tip = max_probe_length
438
+ probe_length_without_tip = probe_length_with_tip - tip_length
439
+ new_ep = sp + direction * probe_length_with_tip / vox_size
440
+ else:
441
+ new_ep = ep
442
+ else:
443
+ new_ep = ep
444
+ if verbose:
445
+ print("old pc_ep", ep)
446
+ print("corrected pc_ep", new_ep)
447
+ print("old probe length with tip", probe_length_with_tip)
448
+ print("corrected probe length with tip", probe_length_with_tip)
449
+ return new_ep, probe_length_with_tip, probe_length_without_tip
450
+
451
+
452
+ def check_parallel_to_z(direction):
453
+ is_parallel = False
454
+ if abs(abs(direction[2]) - 1) < 1e-6:
455
+ is_parallel = True
456
+ return is_parallel
457
+
458
+
459
+ def angle_between_2vectors(vector1, vector2):
460
+ unit_vector_1 = vector1 / np.linalg.norm(vector1)
461
+ unit_vector_2 = vector2 / np.linalg.norm(vector2)
462
+ dot_product = np.dot(unit_vector_1, unit_vector_2)
463
+ angle = np.arccos(dot_product)
464
+ return angle
465
+
466
+
467
+ def get_probe_info(probe_type):
468
+ if probe_type == 0:
469
+ tip_length = 175
470
+ channel_size = 20
471
+ channel_number_in_banks = (384, 384, 192)
472
+ elif probe_type == 1:
473
+ tip_length = 175
474
+ channel_size = 15
475
+ channel_number_in_banks = (384, 384, 192)
476
+ else:
477
+ tip_length = 0
478
+ channel_size = None
479
+ channel_number_in_banks = None
480
+ return tip_length, channel_size, channel_number_in_banks
481
+
482
+
483
+ def group_labels(fine_label_mat, verbose=False):
484
+ n_row, n_column = fine_label_mat.shape
485
+ group_mat = np.zeros(fine_label_mat.shape)
486
+ group_mat[:] = np.nan
487
+ group_id = 0
488
+ group_id_label = []
489
+
490
+ previous_row = np.zeros(n_column)
491
+ previous_row[:] = np.nan
492
+ for base_level in range(0, n_row):
493
+ check_label = fine_label_mat[base_level]
494
+ unique_label = np.unique(check_label)
495
+ for da_label in unique_label:
496
+ if da_label in previous_row:
497
+ continue
498
+ add_row = 0
499
+ row_index = add_row + base_level
500
+ label_check = fine_label_mat[row_index] == da_label
501
+ group_mat[row_index, label_check] = group_id
502
+ while np.any(label_check):
503
+ add_row += 1
504
+ row_index = add_row + base_level
505
+ if row_index == n_row:
506
+ break
507
+ label_check = fine_label_mat[row_index] == da_label
508
+ group_mat[row_index, label_check] = group_id
509
+ group_id_label.append(da_label)
510
+ group_id += 1
511
+ previous_row = fine_label_mat[base_level]
512
+
513
+ if verbose:
514
+ print("group mat nan index", np.where(np.isnan(group_mat)))
515
+ print("group id label", group_id_label)
516
+ print("group_mat")
517
+ indexs = np.arange(0, len(group_mat), 20)
518
+ for i in range(len(indexs) - 1):
519
+ print(group_mat[indexs[i] : indexs[i + 1]])
520
+ print(group_mat[indexs[-1] : len(group_mat)])
521
+ print(len(group_mat))
522
+
523
+ return group_mat, group_id_label
524
+
525
+
526
+ def get_column_grouped_info(group_column, column_bound):
527
+ diff_labels = np.where(np.diff(group_column) != 0)[0]
528
+ if len(diff_labels) != 0:
529
+ change_index = np.append(np.array([0]), diff_labels + 1)
530
+ group_id = group_column[change_index]
531
+ start_loc = column_bound[change_index]
532
+ end_loc = column_bound[
533
+ np.append(change_index[1:], np.array([len(group_column)]))
534
+ ]
535
+ else:
536
+ group_id = np.asarray([group_column[0]])
537
+ start_loc = np.asarray([column_bound[0]])
538
+ end_loc = np.asarray([column_bound[-1]])
539
+
540
+ group_column_length = np.abs(end_loc - start_loc)
541
+
542
+ return group_id, start_loc, end_loc, group_column_length
543
+
544
+
545
+ def get_vis_data(group_mat, column_loc, sites_loc, sites_line_count, vox_size):
546
+ n_column = len(column_loc)
547
+
548
+ p_bounds = np.array([0])
549
+ p_bounds = np.append(
550
+ p_bounds, column_loc[0][:-1, 0] + np.diff(column_loc[0][:, 0]) * 0.5
551
+ )
552
+ p_bounds = np.append(p_bounds, column_loc[0][-1, 0])
553
+
554
+ vis_data = []
555
+ column_group_length = []
556
+ column_n_sites = []
557
+ for i in range(n_column):
558
+ sites_column = sites_loc[i][:, 0]
559
+ # print(sites_column)
560
+ if sites_line_count is None:
561
+ line_counts = np.ones(len(sites_column))
562
+ else:
563
+ line_counts = np.ravel(sites_line_count)
564
+ group_column = group_mat[:, i]
565
+ group_id, start_loc, end_loc, gc_length = get_column_grouped_info(
566
+ group_column, p_bounds
567
+ )
568
+ n_sites_group = []
569
+ for j in range(len(group_id)):
570
+ valid_sites_lines = np.logical_and(
571
+ sites_column < end_loc[j], sites_column >= start_loc[j]
572
+ )
573
+ n_sites_group.append(np.sum(valid_sites_lines * line_counts))
574
+ # print('n_sites_group', n_sites_group)
575
+ vis_column = {
576
+ "group_id": group_id,
577
+ "start_loc": start_loc / vox_size,
578
+ "end_loc": end_loc / vox_size,
579
+ "sites": sites_column / vox_size,
580
+ }
581
+ column_group_length.append(gc_length)
582
+ column_n_sites.append(np.ravel(n_sites_group))
583
+ vis_data.append(vis_column)
584
+ # print(vis_data)
585
+ n_column = len(vis_data)
586
+ unique_groups = np.unique(group_mat)
587
+ group_length = []
588
+ group_n_sites = []
589
+ for group_ind in unique_groups:
590
+ temp = []
591
+ temp_sites = []
592
+ for i in range(n_column):
593
+ valid_ind = np.where(vis_data[i]["group_id"] == group_ind)[0]
594
+ if len(valid_ind) != 0:
595
+ valid_length = np.sum(column_group_length[i][valid_ind])
596
+ valid_sites = np.sum(column_n_sites[i][valid_ind])
597
+ temp.append(valid_length)
598
+ temp_sites.append(valid_sites)
599
+ group_length.append(np.sum(temp) / n_column)
600
+ group_n_sites.append(np.sum(temp_sites))
601
+
602
+ text_loc = []
603
+ for group_ind in unique_groups:
604
+ inds = np.where(group_mat == group_ind)[0]
605
+ low_level = np.min(inds)
606
+ high_level = np.max(inds)
607
+ text_loc.append(
608
+ (
609
+ p_bounds[low_level]
610
+ + 0.7 * (p_bounds[high_level + 1] - p_bounds[low_level])
611
+ )
612
+ / vox_size
613
+ )
614
+ return vis_data, group_length, group_n_sites, text_loc
615
+
616
+
617
+ def get_n_sites_in_region(gr_start, gr_end, plot_sites_column):
618
+ n_groups, n_column = gr_start.shape
619
+
620
+ region_site_num = []
621
+ for i in range(n_groups):
622
+ n_sites = 0
623
+ for j in range(n_column):
624
+ if np.isnan(gr_start[i, j]):
625
+ continue
626
+ valid_ind = np.logical_and(
627
+ plot_sites_column[j] >= gr_start[i, j],
628
+ plot_sites_column[j] < gr_end[i, j],
629
+ )
630
+ n_sites += np.sum(valid_ind)
631
+ region_site_num.append(n_sites)
632
+
633
+ return region_site_num
634
+
635
+
636
+ def get_label_name(label_info, region_label):
637
+ label_names = []
638
+ label_acronym = []
639
+ label_color = []
640
+ for i in range(len(region_label)):
641
+ if region_label[i] == 0:
642
+ label_names.append(" ")
643
+ label_acronym.append(" ")
644
+ label_color.append((128, 128, 128))
645
+ else:
646
+ da_ind = np.where(np.ravel(label_info["index"]) == region_label[i])[0][0]
647
+ label_names.append(label_info["label"][da_ind])
648
+ label_acronym.append(label_info["abbrev"][da_ind])
649
+ label_color.append(label_info["color"][da_ind])
650
+
651
+ label_color = np.asarray(label_color)
652
+ return label_names, label_acronym, label_color
653
+
654
+
655
+ def get_sites_loc_related_to_base_center(probe_settings, probe_length_without_tip_um):
656
+ probe_type_name = probe_settings["probe_type_name"]
657
+ probe_thickness_um = probe_settings["probe_thickness"]
658
+ per_max_sites = probe_settings["per_max_sites"]
659
+ sites_distance_um = probe_settings["sites_distance"]
660
+ x_bias_um = probe_settings["x_bias"]
661
+ y_bias_um = probe_settings["y_bias"]
662
+
663
+ if probe_type_name == "Tetrode":
664
+ sites_loc = [np.array([[0, 0, 0]]) for _ in range(4)]
665
+ else:
666
+ sites_loc = [] # inverse r-vals, u-vals, n-vals
667
+ n_val = 0.5 * probe_thickness_um
668
+ for i in range(len(x_bias_um)):
669
+ possible_n_sites = int(
670
+ (probe_length_without_tip_um - y_bias_um[i]) / sites_distance_um[i]
671
+ )
672
+ if possible_n_sites <= per_max_sites[i]:
673
+ r_vals = (
674
+ np.arange(possible_n_sites) * sites_distance_um[i] + y_bias_um[i]
675
+ )
676
+ else:
677
+ r_vals = (
678
+ np.arange(per_max_sites[i]) * sites_distance_um[i] + y_bias_um[i]
679
+ )
680
+ num_sites = len(r_vals)
681
+ u_vals = np.repeat(x_bias_um[i], num_sites)
682
+ n_vals = np.repeat(n_val, num_sites)
683
+ temp = np.stack([r_vals, u_vals, n_vals], axis=1)
684
+ sites_loc.append(temp)
685
+ return sites_loc
686
+
687
+
688
+ def merge_sites_into_line(sites_loc):
689
+ y_vec = []
690
+ for i in range(len(sites_loc)):
691
+ y_vec.append(sites_loc[i][:, 0])
692
+ y_vec = np.concatenate(y_vec)
693
+ y_vals = np.unique(y_vec)
694
+ sites_loc_line = []
695
+ sites_count = []
696
+ temp = []
697
+ for i in range(len(y_vals)):
698
+ n = len(np.where(y_vec == y_vals[i])[0])
699
+ temp.append(np.array([y_vals[i], 0, 0]))
700
+ sites_count.append(n)
701
+ temp = np.asarray(temp)
702
+ sites_loc_line.append(temp)
703
+
704
+ return sites_loc_line, sites_count
705
+
706
+
707
+ def get_pnt_from_loc(sct, loc, n_vec, u_vec, r_vec, bregma, vox_size):
708
+ pnt = []
709
+ pnt_vox = []
710
+ for i in range(len(loc)):
711
+ temp = [
712
+ sct * vox_size
713
+ + r_vec * loc[i][ind, 0]
714
+ + u_vec * loc[i][ind, 1]
715
+ + n_vec * loc[i][ind, 2]
716
+ for ind in range(len(loc[i]))
717
+ ]
718
+ temp = np.asarray(temp)
719
+ temp = temp / vox_size
720
+ pnt.append(temp)
721
+ vox_temp = temp + bregma
722
+ vox_temp = vox_temp.astype(int)
723
+ pnt_vox.append(vox_temp)
724
+
725
+ return pnt, pnt_vox
726
+
727
+
728
+ def get_column_loc(
729
+ sites_loc_to_base, probe_length_without_tip_um, vxsize_um, verbose=False
730
+ ):
731
+ sites_r_vals = []
732
+ for i in range(len(sites_loc_to_base)):
733
+ sites_r_vals.append(sites_loc_to_base[i][:, 0])
734
+ sites_r_vals = np.concatenate(sites_r_vals)
735
+ uni_sites_r_vals = np.unique(sites_r_vals)
736
+
737
+ if len(uni_sites_r_vals) > 1:
738
+ if uni_sites_r_vals[0] == 0:
739
+ vis_r_vals = np.arange(uni_sites_r_vals[0], uni_sites_r_vals[1], vxsize_um)
740
+ for i in range(1, len(uni_sites_r_vals) - 1):
741
+ vis_r_vals = np.append(
742
+ vis_r_vals,
743
+ np.arange(uni_sites_r_vals[i], uni_sites_r_vals[i + 1], vxsize_um),
744
+ )
745
+ else:
746
+ vis_r_vals = np.arange(0, uni_sites_r_vals[0], vxsize_um)
747
+ for i in range(len(uni_sites_r_vals) - 1):
748
+ vis_r_vals = np.append(
749
+ vis_r_vals,
750
+ np.arange(uni_sites_r_vals[i], uni_sites_r_vals[i + 1], vxsize_um),
751
+ )
752
+
753
+ if uni_sites_r_vals[-1] != probe_length_without_tip_um:
754
+ vis_r_vals = np.append(
755
+ vis_r_vals,
756
+ np.arange(uni_sites_r_vals[-1], probe_length_without_tip_um, vxsize_um),
757
+ )
758
+ vis_r_vals = np.append(vis_r_vals, probe_length_without_tip_um)
759
+ else:
760
+ vis_r_vals = np.arange(0, probe_length_without_tip_um, vxsize_um)
761
+
762
+ column_loc = []
763
+ for i in range(len(sites_loc_to_base)):
764
+ temp = np.zeros((len(vis_r_vals), 3))
765
+ temp[:, 0] = vis_r_vals
766
+ temp[:, 1] = sites_loc_to_base[i][0, 1]
767
+ temp[:, 2] = sites_loc_to_base[i][0, 2]
768
+ column_loc.append(temp)
769
+
770
+ if verbose:
771
+ print("uni_sites_r_vals")
772
+ print(uni_sites_r_vals)
773
+
774
+ print("vis_r_vals")
775
+ print(vis_r_vals)
776
+
777
+ print("column_loc")
778
+ print(column_loc)
779
+
780
+ return column_loc
781
+
782
+
783
+ def get_loc_related_to_start(loc_to_base, probe_length_without_tip_um):
784
+ loc_to_start = [] # inverse r-vals, u-vals, n-vals
785
+ for i in range(len(loc_to_base)):
786
+ temp_loc = loc_to_base[i].copy()
787
+ temp_loc[:, 0] = probe_length_without_tip_um - temp_loc[:, 0]
788
+ loc_to_start.append(temp_loc)
789
+ return loc_to_start
790
+
791
+
792
+ def get_fine_label_matrix(column_vox, label_data, verbose=False):
793
+ fine_label_mat = []
794
+ for i in range(len(column_vox)):
795
+ fine_label_mat.append(
796
+ label_data[column_vox[i][:, 0], column_vox[i][:, 1], column_vox[i][:, 2]]
797
+ )
798
+ fine_label_mat = np.asarray(fine_label_mat).T
799
+
800
+ if verbose:
801
+ # print out fine label matrix
802
+ indexs = np.arange(0, len(fine_label_mat), 20)
803
+ for i in range(len(indexs) - 1):
804
+ print(fine_label_mat[indexs[i] : indexs[i + 1]])
805
+ print(fine_label_mat[indexs[-1] : len(fine_label_mat)])
806
+
807
+ return fine_label_mat
808
+
809
+
810
+ def calculate_probe_info(
811
+ data_list,
812
+ pieces_names,
813
+ label_data,
814
+ label_info,
815
+ vxsize_um,
816
+ probe_settings,
817
+ merge_sites,
818
+ bregma,
819
+ site_face,
820
+ n_hat,
821
+ pre_plan,
822
+ atlas_metadata=None,
823
+ ):
824
+ """
825
+
826
+ :param data: 3d coordinates for all the points
827
+ :param label_data: original brain region segmentation
828
+ :param label_info:
829
+ :param vxsize_um:
830
+ :param tip_length:
831
+ :param channel_size:
832
+ :param bregma:
833
+ :return:
834
+ """
835
+
836
+ data_dict = None
837
+ data = data_list[0]
838
+ for i in range(1, len(data_list)):
839
+ data = np.vstack([data, data_list[i]])
840
+ # print('data', data)
841
+
842
+ # start_pnt and end_pnt are coordinates related to the given Bregma
843
+ probe_type_name = probe_settings["probe_type_name"]
844
+ probe_max_length_um = probe_settings["probe_length"]
845
+ tip_length_um = probe_settings["tip_length"]
846
+
847
+ # get direction and probe center start and end (pc - probe center)
848
+ (
849
+ pc_start_pnt,
850
+ pc_end_pnt,
851
+ avg,
852
+ direction,
853
+ fit_diagnostics,
854
+ ) = line_fit(data, return_diagnostics=True)
855
+
856
+ # print('direction', direction)
857
+
858
+ # get angels
859
+ ap_angle, ml_angle = get_angles(direction)
860
+ # print('ap_angle', ap_angle)
861
+ # print(ml_angle)
862
+
863
+ # print('check-start_pnt', pc_start_pnt)
864
+ # # print(pc_start_vox)
865
+ # correct probe center start point
866
+ pc_sp, error_index = find_probe_surface_entry(
867
+ label_data, avg, direction, bregma
868
+ )
869
+ if error_index != 0:
870
+ return data_dict, error_index
871
+
872
+ pc_ep, probe_length_with_tip_um, probe_length_without_tip_um = correct_end_point(
873
+ pc_sp,
874
+ pc_end_pnt,
875
+ direction,
876
+ vxsize_um,
877
+ tip_length_um,
878
+ probe_max_length_um,
879
+ verbose=False,
880
+ )
881
+ trajectory_fit = {
882
+ "method": fit_diagnostics["method"],
883
+ "surface_method": "3D fitted-line intersection with atlas brain mask",
884
+ "point_count": fit_diagnostics["point_count"],
885
+ "inlier_count": fit_diagnostics["inlier_count"],
886
+ "inlier_mask": fit_diagnostics["inlier_mask"],
887
+ "residual_um": fit_diagnostics["residual_vox"] * vxsize_um,
888
+ "rms_error_um": fit_diagnostics["rms_error_vox"] * vxsize_um,
889
+ "max_error_um": fit_diagnostics["max_error_vox"] * vxsize_um,
890
+ "explained_fraction": fit_diagnostics["explained_fraction"],
891
+ "surface_adjustment_um": float(
892
+ np.linalg.norm(pc_sp - pc_start_pnt) * vxsize_um
893
+ ),
894
+ }
895
+
896
+ pv_sp = pc_sp + bregma
897
+ pv_sp = pv_sp.astype(int)
898
+ pv_ep = pc_ep + bregma
899
+ pv_ep = pv_ep.astype(int)
900
+
901
+ enter_coords = pc_sp * vxsize_um
902
+ end_coords = pc_ep * vxsize_um
903
+
904
+ # print('')
905
+
906
+ dv = (pc_sp[2] - pc_ep[2]) * vxsize_um
907
+ ap_tilt, ml_tilt = get_tilt_info(pc_sp, pc_ep)
908
+
909
+ if pre_plan:
910
+ r_hat = direction.copy()
911
+ u_hat = np.cross(n_hat, r_hat)
912
+
913
+ n_vec, u_vec = get_vector_according_to_site_face(n_hat, u_hat, site_face)
914
+ else:
915
+ r_hat, u_vec, n_vec = calculate_vector_according_to_site_face(
916
+ direction, site_face
917
+ )
918
+
919
+ # sites location, list of [(n_sites, 3),...], in um
920
+ sites_loc_to_base_temp = get_sites_loc_related_to_base_center(
921
+ probe_settings, probe_length_without_tip_um
922
+ )
923
+ # print('sites_loc_to_base')
924
+ # print(sites_loc_to_base)
925
+
926
+ # Preserve every physical contact independently of the display option that
927
+ # merges contacts at the same axial depth into one line.
928
+ if probe_type_name != "Tetrode":
929
+ contact_loc = get_loc_related_to_start(
930
+ sites_loc_to_base_temp, probe_length_without_tip_um
931
+ )
932
+ contact_pnt, contact_vox = get_pnt_from_loc(
933
+ pc_sp, contact_loc, n_vec, u_vec, r_hat, bregma, vxsize_um
934
+ )
935
+ else:
936
+ contact_pnt = [np.asarray([pc_ep], dtype=float) for _ in range(4)]
937
+ contact_vox = [
938
+ np.asarray([pc_ep + bregma], dtype=float).astype(int) for _ in range(4)
939
+ ]
940
+
941
+ if not coordinate_groups_in_bounds(contact_vox, label_data.shape):
942
+ return data_dict, PROBE_COORDINATES_OUTSIDE_ATLAS
943
+
944
+ contact_labels = [
945
+ label_data[group[:, 0], group[:, 1], group[:, 2]] for group in contact_vox
946
+ ]
947
+
948
+ if merge_sites or probe_type_name == "Tetrode":
949
+ sites_loc_to_base, sites_line_count = merge_sites_into_line(
950
+ sites_loc_to_base_temp
951
+ )
952
+ else:
953
+ sites_loc_to_base = sites_loc_to_base_temp.copy()
954
+ sites_line_count = None
955
+
956
+ # column location list of [(n_locs, 3), ...], in um
957
+ column_loc_to_base = get_column_loc(
958
+ sites_loc_to_base, probe_length_without_tip_um, vxsize_um, verbose=False
959
+ )
960
+
961
+ # column location relative to the start, from bottom to top, in um
962
+ column_loc = get_loc_related_to_start(
963
+ column_loc_to_base, probe_length_without_tip_um
964
+ )
965
+ # print('column_loc')
966
+ # print(column_loc)
967
+
968
+ # column points (related to bregma) and column vox
969
+ column_pnt, column_vox = get_pnt_from_loc(
970
+ pc_sp, column_loc, n_vec, u_vec, r_hat, bregma, vxsize_um
971
+ )
972
+ # print('column_pnt')
973
+ # print(column_pnt)
974
+ # print('column_vox')
975
+ # print(column_vox)
976
+
977
+ if not coordinate_groups_in_bounds(column_vox, label_data.shape):
978
+ return data_dict, PROBE_COORDINATES_OUTSIDE_ATLAS
979
+
980
+ fine_label_mat = get_fine_label_matrix(column_vox, label_data, verbose=False)
981
+ group_mat, group_id_label = group_labels(fine_label_mat)
982
+ label_names, label_acronym, label_color = get_label_name(label_info, group_id_label)
983
+
984
+ if probe_type_name != "Tetrode":
985
+ # sites loc related to the start, in um
986
+ sites_loc = get_loc_related_to_start(
987
+ sites_loc_to_base, probe_length_without_tip_um
988
+ )
989
+ # print(sites_loc)
990
+
991
+ sites_pnt, sites_vox = get_pnt_from_loc(
992
+ pc_sp, sites_loc, n_vec, u_vec, r_hat, bregma, vxsize_um
993
+ )
994
+ # print(sites_pnt)
995
+ # print('sites_vox')
996
+ # print(sites_vox)
997
+ else:
998
+ sites_pnt = [np.array([pc_ep])]
999
+ sites_vox_temp = sites_pnt[0] + bregma
1000
+ sites_vox = [sites_vox_temp.astype(int)]
1001
+
1002
+ if not coordinate_groups_in_bounds(sites_vox, label_data.shape):
1003
+ return data_dict, PROBE_COORDINATES_OUTSIDE_ATLAS
1004
+
1005
+ sites_label = []
1006
+ for i in range(len(sites_vox)):
1007
+ sites_label.append(
1008
+ label_data[sites_vox[i][:, 0], sites_vox[i][:, 1], sites_vox[i][:, 2]]
1009
+ )
1010
+
1011
+ # print('sites_label')
1012
+ # print(sites_label)
1013
+
1014
+ vis_data, region_length, region_site_num, region_text_loc = get_vis_data(
1015
+ group_mat, column_loc_to_base, sites_loc_to_base, sites_line_count, vxsize_um
1016
+ )
1017
+
1018
+ data_dict = {
1019
+ "object_name": "probe",
1020
+ "probe_type_name": probe_type_name,
1021
+ "data": data_list,
1022
+ "pieces_names": pieces_names,
1023
+ "ap_tilt": ap_tilt,
1024
+ "ml_tilt": ml_tilt,
1025
+ "insertion_coords_3d": pc_sp,
1026
+ "terminus_coords_3d": pc_ep,
1027
+ "direction": direction,
1028
+ "probe_length": probe_length_with_tip_um,
1029
+ "dv": dv,
1030
+ "ap_angle": ap_angle,
1031
+ "ml_angle": ml_angle,
1032
+ "insertion_coords": enter_coords,
1033
+ "insertion_vox": pv_sp,
1034
+ "terminus_coords": end_coords,
1035
+ "terminus_vox": pv_ep,
1036
+ "sites_label": sites_label,
1037
+ "sites_loc_b": sites_pnt,
1038
+ "sites_vox": sites_vox,
1039
+ "region_label": group_id_label,
1040
+ "region_length": region_length,
1041
+ "region_sites": region_site_num,
1042
+ "text_loc": region_text_loc,
1043
+ "label_name": label_names,
1044
+ "label_acronym": label_acronym,
1045
+ "label_color": label_color,
1046
+ "vis_data": vis_data,
1047
+ "probe_settings": probe_settings,
1048
+ "site_face": site_face,
1049
+ "display_sites_merged": bool(merge_sites),
1050
+ "trajectory_fit": trajectory_fit,
1051
+ }
1052
+
1053
+ atlas_metadata = atlas_metadata or {}
1054
+ data_dict["reconstruction"] = build_probe_reconstruction(
1055
+ insertion_bregma_vox=pc_sp,
1056
+ terminus_bregma_vox=pc_ep,
1057
+ insertion_vox_index=pv_sp,
1058
+ terminus_vox_index=pv_ep,
1059
+ contact_bregma_vox=contact_pnt,
1060
+ contact_vox_index=contact_vox,
1061
+ contact_structure_ids=contact_labels,
1062
+ contact_local_from_tip_base_um=sites_loc_to_base_temp,
1063
+ probe_length_um=probe_length_with_tip_um,
1064
+ probe_settings=probe_settings,
1065
+ site_face=site_face,
1066
+ voxel_size_um=vxsize_um,
1067
+ bregma_herbs_vox=bregma,
1068
+ herbs_atlas_shape=label_data.shape,
1069
+ label_info=label_info,
1070
+ axis_info=atlas_metadata.get("axis_info"),
1071
+ atlas_identifier=atlas_metadata.get("identifier"),
1072
+ atlas_path=atlas_metadata.get("path"),
1073
+ software_version=__version__,
1074
+ trajectory_fit=trajectory_fit,
1075
+ )
1076
+ return data_dict, error_index
1077
+
1078
+
1079
+ def get_pre_multi_shank_vis_base(x_vals, y_vals):
1080
+ x_vals = np.ravel(x_vals)
1081
+ y_vals = np.ravel(y_vals)
1082
+ valid_ind = np.where(y_vals == 0)[0]
1083
+ if len(valid_ind) != 0:
1084
+ base_loc = x_vals[valid_ind]
1085
+ else:
1086
+ base_loc = np.array([0])
1087
+ # if site_face in [0, 1]:
1088
+ # valid_ind = np.where(y_vals == 0)[0]
1089
+ # if len(valid_ind) != 0:
1090
+ # base_loc = x_vals[valid_ind]
1091
+ # else:
1092
+ # base_loc = np.array([0])
1093
+ # else:
1094
+ # valid_ind = np.where(x_vals == 0)[0]
1095
+ # if len(valid_ind) != 0:
1096
+ # base_loc = y_vals[valid_ind]
1097
+ # else:
1098
+ # base_loc = np.array([0])
1099
+ return base_loc
1100
+
1101
+
1102
+ def get_vector_according_to_site_face(n_hat, u_hat, site_face):
1103
+ # for pre-plan - 2d plan
1104
+ if site_face == 0:
1105
+ # site face out, facing to you
1106
+ n_vec = n_hat.copy()
1107
+ u_vec = u_hat.copy()
1108
+ elif site_face == 1:
1109
+ # site face in, facing away from you
1110
+ n_vec = -n_hat
1111
+ u_vec = -u_hat
1112
+ elif site_face == 2:
1113
+ # site face left, facing to you left-hand side
1114
+ n_vec = -u_hat
1115
+ u_vec = n_hat.copy()
1116
+ elif site_face == 3:
1117
+ # site face right, facing to you right-hand side
1118
+ n_vec = u_hat.copy()
1119
+ u_vec = -n_hat
1120
+ else:
1121
+ raise ValueError("no such site face, stupid!!!!!!")
1122
+
1123
+ # print('n_vec', n_vec)
1124
+ # print('u_vec', u_vec)
1125
+
1126
+ return n_vec, u_vec
1127
+
1128
+
1129
+ def calculate_vector_according_to_site_face(direction, site_face):
1130
+ # for after surgery
1131
+ r_hat = direction.copy()
1132
+ if check_parallel_to_z(direction):
1133
+ if site_face == 0:
1134
+ n_hat = np.array([0, 1, 0])
1135
+ u_hat = np.cross(n_hat, r_hat)
1136
+ elif site_face == 1:
1137
+ n_hat = np.array([0, -1, 0])
1138
+ u_hat = np.cross(n_hat, r_hat)
1139
+ elif site_face == 2:
1140
+ n_hat = np.array([-1, 0, 0])
1141
+ u_hat = np.cross(n_hat, r_hat)
1142
+ elif site_face == 3:
1143
+ n_hat = np.array([1, 0, 0])
1144
+ u_hat = np.cross(n_hat, r_hat)
1145
+ else:
1146
+ n_hat = None
1147
+ u_hat = None
1148
+ print("Site face can only be 0-Up, 1-Down, 2-Left, 3-Right.")
1149
+ else:
1150
+ if site_face == 0:
1151
+ t_hat = np.array([-r_hat[1], r_hat[0], 0])
1152
+ u_hat = t_hat / np.linalg.norm(t_hat)
1153
+ n_hat = np.cross(r_hat, u_hat)
1154
+ elif site_face == 1:
1155
+ t_hat = np.array([r_hat[1], r_hat[0], 0])
1156
+ u_hat = t_hat / np.linalg.norm(t_hat)
1157
+ n_hat = np.cross(r_hat, u_hat)
1158
+ elif site_face == 2:
1159
+ t_hat = np.array([-r_hat[1], r_hat[0], 0])
1160
+ n_hat = t_hat / np.linalg.norm(t_hat)
1161
+ u_hat = np.cross(n_hat, r_hat)
1162
+ elif site_face == 3:
1163
+ t_hat = np.array([r_hat[1], r_hat[0], 0])
1164
+ n_hat = t_hat / np.linalg.norm(t_hat)
1165
+ u_hat = np.cross(n_hat, r_hat)
1166
+ else:
1167
+ n_hat = None
1168
+ u_hat = None
1169
+ print("Site face can only be 0-Up, 1-Down, 2-Left, 3-Right.")
1170
+ # print('t_hat', t_hat)
1171
+ # print(n_hat)
1172
+ # print(u_hat)
1173
+ return r_hat, u_hat, n_hat
1174
+
1175
+
1176
+ def get_center_lines(
1177
+ pnts, r_hat, n_hat, u_hat, x_vals, y_vals, length, site_face, vox_size
1178
+ ):
1179
+ n_vec, u_vec = get_vector_according_to_site_face(n_hat, u_hat, site_face)
1180
+
1181
+ # print(x_vals)
1182
+ # print(y_vals)
1183
+
1184
+ line_data = []
1185
+ for i in range(len(x_vals)):
1186
+ s_val = u_vec * x_vals[i] + n_vec * y_vals[i]
1187
+ # print('s_val', s_val)
1188
+ temp = [s_val, s_val + r_hat * length * vox_size]
1189
+ # print(temp)
1190
+ line_data.append(pnts[0] + np.asarray(temp) / vox_size)
1191
+
1192
+ return line_data
1193
+
1194
+
1195
+ def get_pre_ms_vis_base(multi_shanks, site_face, atlas_display):
1196
+ if multi_shanks is None:
1197
+ base_loc = np.array([0])
1198
+ else:
1199
+ if atlas_display == "sagittal":
1200
+ if site_face not in [0, 1]:
1201
+ base_loc = np.ravel(multi_shanks)
1202
+ else:
1203
+ base_loc = np.array([0])
1204
+ else:
1205
+ if site_face in [0, 1]:
1206
+ base_loc = np.ravel(multi_shanks)
1207
+ else:
1208
+ base_loc = np.array([0])
1209
+ return base_loc
1210
+
1211
+
1212
+ def get_pre_mp_vis_base(base_loc, atlas_display):
1213
+ if atlas_display == "sagittal":
1214
+ valid_ind = np.where(base_loc[:, 1] == 0)[0]
1215
+ if len(valid_ind) == 0:
1216
+ vis_base = np.array([0])
1217
+ else:
1218
+ vis_base = base_loc[valid_ind, 1]
1219
+ else:
1220
+ valid_ind = np.where(base_loc[:, 0] == 0)[0]
1221
+ if len(valid_ind) == 0:
1222
+ vis_base = np.array([0])
1223
+ else:
1224
+ vis_base = base_loc[valid_ind, 0]
1225
+ return vis_base
1226
+
1227
+
1228
+ class Probe(object):
1229
+ def __init__(self):
1230
+ self.probe_length = None
1231
+ self.tip_length = None
1232
+ self.site_width = None
1233
+ self.site_height = None
1234
+ self.total_sites = None
1235
+ self.site_number_in_banks = None
1236
+ self.sites_distance = None
1237
+ self.probe_type = None
1238
+ self.x_bias = None
1239
+ self.per_max_sites = None
1240
+ self.sites_distance = None
1241
+ self.y_bias = None
1242
+ self.probe_type_name = None
1243
+ self.probe_thickness = None
1244
+ self.multi_shanks = None
1245
+ self.exist_probes = None
1246
+ self.faces = None
1247
+
1248
+ self.set_np1()
1249
+
1250
+ def extend_exist_probes(self, prb):
1251
+ pass
1252
+
1253
+ def get_exist_probes(self):
1254
+ self.exist_probes = {"NP1.0": None, "NP2.0": None, "Tetrode": None}
1255
+
1256
+ def set_np2(self):
1257
+ self.probe_type = 1
1258
+ self.probe_type_name = "NP2.0"
1259
+ self.probe_thickness = 24
1260
+ self.probe_length = 10000
1261
+ self.tip_length = 175
1262
+ self.site_width = 16
1263
+ self.site_height = 15
1264
+ self.x_bias = [-8, 16]
1265
+ self.per_max_sites = [480, 480]
1266
+ self.sites_distance = [15, 15]
1267
+ self.y_bias = [7.5, 7.5]
1268
+ self.site_number_in_banks = (384, 384, 192)
1269
+ self.multi_shanks = [-375, -125, 125, 375]
1270
+ self.faces = "Front"
1271
+
1272
+ def set_np1(self):
1273
+ self.probe_type = 0
1274
+ self.probe_type_name = "NP1.0"
1275
+ self.probe_thickness = 24
1276
+ self.probe_length = 10000
1277
+ self.tip_length = 175
1278
+ self.site_width = 16
1279
+ self.site_height = 20
1280
+ self.x_bias = [-16, -8, 8, 16]
1281
+ self.per_max_sites = [240, 240, 240, 240]
1282
+ self.sites_distance = [40, 40, 40, 40]
1283
+ self.y_bias = [30, 10, 30, 10]
1284
+ self.site_number_in_banks = (384, 384, 192)
1285
+ self.multi_shanks = None
1286
+ self.faces = "Front"
1287
+
1288
+ def set_tetrode(self):
1289
+ self.probe_type = 3
1290
+ self.probe_type_name = "Tetrode"
1291
+ self.probe_thickness = 0
1292
+ self.probe_length = None
1293
+ self.tip_length = 0
1294
+ self.site_width = None
1295
+ self.site_height = None
1296
+ self.x_bias = 0
1297
+ self.per_max_sites = 4
1298
+ self.sites_distance = 0
1299
+ self.y_bias = 0
1300
+ self.site_number_in_banks = None
1301
+ self.multi_shanks = None
1302
+ self.faces = None
1303
+
1304
+ def set_linear_silicon(self, pss):
1305
+ self.probe_type = 2
1306
+ self.probe_type_name = "Linear-Silicon"
1307
+ self.probe_length = pss["probe_length"]
1308
+ self.probe_thickness = pss["probe_thickness"]
1309
+ self.tip_length = pss["tip_length"]
1310
+ self.site_width = pss["site_width"]
1311
+ self.site_height = pss["site_height"]
1312
+
1313
+ self.per_max_sites = pss["per_max_sites"]
1314
+ self.sites_distance = pss["sites_distance"]
1315
+ self.x_bias = pss["x_bias"]
1316
+ self.y_bias = pss["y_bias"]
1317
+ self.site_number_in_banks = None
1318
+ self.multi_shanks = None
1319
+ self.faces = "Front"
1320
+
1321
+ def get_settings(self):
1322
+ data = {
1323
+ "probe_type": self.probe_type,
1324
+ "probe_type_name": self.probe_type_name,
1325
+ "probe_thickness": self.probe_thickness,
1326
+ "probe_length": self.probe_length,
1327
+ "tip_length": self.tip_length,
1328
+ "site_height": self.site_height,
1329
+ "site_width": self.site_width,
1330
+ "per_max_sites": self.per_max_sites,
1331
+ "sites_distance": self.sites_distance,
1332
+ "x_bias": self.x_bias,
1333
+ "y_bias": self.y_bias,
1334
+ "site_number_in_banks": self.site_number_in_banks,
1335
+ "multi_shanks": self.multi_shanks,
1336
+ }
1337
+ return data
1338
+
1339
+ def probe_faces_changed(self, face_direction):
1340
+ self.faces = face_direction
1341
+
1342
+ def get_multi_shank_3d_base(self):
1343
+ if self.multi_shanks is None:
1344
+ return
1345
+ if self.faces in ["Front", "Back"]:
1346
+ points3 = np.zeros((len(self.multi_shanks), 3))
1347
+ points3[:, 1] = self.multi_shanks
1348
+ else:
1349
+ points3 = np.zeros((len(self.multi_shanks), 3))
1350
+ points3[:, 2] = self.multi_shanks
1351
+ return points3
1352
+
1353
+
1354
+ class MultiProbes(object):
1355
+ def __init__(self):
1356
+ self.x_vals = None
1357
+ self.y_vals = None
1358
+ self.faces = None
1359
+
1360
+ def set_multi_probes(self, multi_settings):
1361
+ self.x_vals = multi_settings["x_vals"]
1362
+ self.y_vals = multi_settings["y_vals"]
1363
+ self.faces = multi_settings["faces"]
1364
+
1365
+ def get_multi_settings(self):
1366
+ if self.x_vals is None:
1367
+ multi_settings = None
1368
+ else:
1369
+ if not isinstance(self.x_vals, list):
1370
+ x_vals = self.x_vals.tolist()
1371
+ else:
1372
+ x_vals = self.x_vals.copy()
1373
+
1374
+ if not isinstance(self.y_vals, list):
1375
+ y_vals = self.y_vals.tolist()
1376
+ else:
1377
+ y_vals = self.y_vals.copy()
1378
+
1379
+ if not isinstance(self.faces, list):
1380
+ faces = self.faces.tolist()
1381
+ else:
1382
+ faces = self.faces.copy()
1383
+ multi_settings = {"x_vals": x_vals, "y_vals": y_vals, "faces": faces}
1384
+ return multi_settings
1385
+
1386
+ def check_multi_settings(self):
1387
+ x_unique = np.unique(self.x_vals)
1388
+
1389
+ for x_val in x_unique:
1390
+ v_ind = np.where(np.ravel(self.x_vals) == x_val)[0]
1391
+ if len(v_ind) > 1:
1392
+ if len(np.unique(np.ravel(self.y_vals)[v_ind])) != len(v_ind):
1393
+ msg = "There are at least 2 probes located at the same location."
1394
+ return msg
1395
+
1396
+ return
1397
+
1398
+ # def get_base_loc_3d(self, multi_shank):
1399
+ # if multi_shank is None:
1400
+ # points3 = np.zeros((len(self.x_vals), 3))
1401
+ # points3[:, 1] = self.x_vals
1402
+ # points3[:, 2] = self.y_vals
1403
+ # else:
1404
+ # n_shank = len(multi_shank)
1405
+ # points3 = []
1406
+ # if self.faces[0] in ['Top', 'Bottom']:
1407
+ # for i in range(len(self.x_vals)):
1408
+ # p3 = np.zeros((n_shank, 3))
1409
+ # p3[:, 1] = multi_shank + self.x_vals[i]
1410
+ # p3[:, 2] = self.y_vals[i]
1411
+ # points3.append(p3)
1412
+ # else:
1413
+ # for i in range(len(self.x_vals)):
1414
+ # p3 = np.zeros((n_shank, 3))
1415
+ # p3[:, 1] = self.x_vals[i]
1416
+ # p3[:, 2] = multi_shank + self.y_vals[i]
1417
+ # points3.append(p3)
1418
+ # points3 = np.concatenate(points3)
1419
+ # return points3
1420
+ #
1421
+ # def get_base_loc_2d(self, multi_shank):
1422
+ # if multi_shank is None:
1423
+ # points2 = np.zeros((len(self.x_vals), 2))
1424
+ # points2[:, 0] = self.x_vals
1425
+ # points2[:, 1] = self.y_vals
1426
+ # else:
1427
+ # n_shank = len(multi_shank)
1428
+ # points2 = []
1429
+ # if self.faces[0] in ['Top', 'Bottom']:
1430
+ # for i in range(len(self.x_vals)):
1431
+ # p2 = np.zeros((n_shank, 2))
1432
+ # p2[:, 0] = multi_shank + self.x_vals[i]
1433
+ # p2[:, 1] = self.y_vals[i]
1434
+ # points2.append(p2)
1435
+ # else:
1436
+ # for i in range(len(self.x_vals)):
1437
+ # p2 = np.zeros((n_shank, 2))
1438
+ # p2[:, 0] = self.x_vals[i]
1439
+ # p2[:, 1] = multi_shank + self.y_vals[i]
1440
+ # points2.append(p2)
1441
+ # points2 = np.concatenate(points2)
1442
+ # return points2