neuro-sam 0.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 (93) hide show
  1. neuro_sam/__init__.py +1 -0
  2. neuro_sam/brightest_path_lib/__init__.py +5 -0
  3. neuro_sam/brightest_path_lib/algorithm/__init__.py +3 -0
  4. neuro_sam/brightest_path_lib/algorithm/astar.py +586 -0
  5. neuro_sam/brightest_path_lib/algorithm/waypointastar.py +449 -0
  6. neuro_sam/brightest_path_lib/algorithm/waypointastar_speedup.py +1007 -0
  7. neuro_sam/brightest_path_lib/connected_componen.py +329 -0
  8. neuro_sam/brightest_path_lib/cost/__init__.py +8 -0
  9. neuro_sam/brightest_path_lib/cost/cost.py +33 -0
  10. neuro_sam/brightest_path_lib/cost/reciprocal.py +90 -0
  11. neuro_sam/brightest_path_lib/cost/reciprocal_transonic.py +86 -0
  12. neuro_sam/brightest_path_lib/heuristic/__init__.py +2 -0
  13. neuro_sam/brightest_path_lib/heuristic/euclidean.py +101 -0
  14. neuro_sam/brightest_path_lib/heuristic/heuristic.py +29 -0
  15. neuro_sam/brightest_path_lib/image/__init__.py +1 -0
  16. neuro_sam/brightest_path_lib/image/stats.py +197 -0
  17. neuro_sam/brightest_path_lib/input/__init__.py +1 -0
  18. neuro_sam/brightest_path_lib/input/inputs.py +14 -0
  19. neuro_sam/brightest_path_lib/node/__init__.py +2 -0
  20. neuro_sam/brightest_path_lib/node/bidirectional_node.py +240 -0
  21. neuro_sam/brightest_path_lib/node/node.py +125 -0
  22. neuro_sam/brightest_path_lib/visualization/__init__.py +4 -0
  23. neuro_sam/brightest_path_lib/visualization/flythrough.py +133 -0
  24. neuro_sam/brightest_path_lib/visualization/flythrough_all.py +394 -0
  25. neuro_sam/brightest_path_lib/visualization/tube_data.py +385 -0
  26. neuro_sam/brightest_path_lib/visualization/tube_flythrough.py +227 -0
  27. neuro_sam/napari_utils/anisotropic_scaling.py +503 -0
  28. neuro_sam/napari_utils/color_utils.py +135 -0
  29. neuro_sam/napari_utils/contrasting_color_system.py +169 -0
  30. neuro_sam/napari_utils/main_widget.py +1016 -0
  31. neuro_sam/napari_utils/path_tracing_module.py +1016 -0
  32. neuro_sam/napari_utils/punet_widget.py +424 -0
  33. neuro_sam/napari_utils/segmentation_model.py +769 -0
  34. neuro_sam/napari_utils/segmentation_module.py +649 -0
  35. neuro_sam/napari_utils/visualization_module.py +574 -0
  36. neuro_sam/plugin.py +260 -0
  37. neuro_sam/punet/__init__.py +0 -0
  38. neuro_sam/punet/deepd3_model.py +231 -0
  39. neuro_sam/punet/prob_unet_deepd3.py +431 -0
  40. neuro_sam/punet/prob_unet_with_tversky.py +375 -0
  41. neuro_sam/punet/punet_inference.py +236 -0
  42. neuro_sam/punet/run_inference.py +145 -0
  43. neuro_sam/punet/unet_blocks.py +81 -0
  44. neuro_sam/punet/utils.py +52 -0
  45. neuro_sam-0.1.0.dist-info/METADATA +269 -0
  46. neuro_sam-0.1.0.dist-info/RECORD +93 -0
  47. neuro_sam-0.1.0.dist-info/WHEEL +5 -0
  48. neuro_sam-0.1.0.dist-info/entry_points.txt +2 -0
  49. neuro_sam-0.1.0.dist-info/licenses/LICENSE +21 -0
  50. neuro_sam-0.1.0.dist-info/top_level.txt +2 -0
  51. sam2/__init__.py +11 -0
  52. sam2/automatic_mask_generator.py +454 -0
  53. sam2/benchmark.py +92 -0
  54. sam2/build_sam.py +174 -0
  55. sam2/configs/sam2/sam2_hiera_b+.yaml +113 -0
  56. sam2/configs/sam2/sam2_hiera_l.yaml +117 -0
  57. sam2/configs/sam2/sam2_hiera_s.yaml +116 -0
  58. sam2/configs/sam2/sam2_hiera_t.yaml +118 -0
  59. sam2/configs/sam2.1/sam2.1_hiera_b+.yaml +116 -0
  60. sam2/configs/sam2.1/sam2.1_hiera_l.yaml +120 -0
  61. sam2/configs/sam2.1/sam2.1_hiera_s.yaml +119 -0
  62. sam2/configs/sam2.1/sam2.1_hiera_t.yaml +121 -0
  63. sam2/configs/sam2.1_training/sam2.1_hiera_b+_MOSE_finetune.yaml +339 -0
  64. sam2/configs/train.yaml +335 -0
  65. sam2/modeling/__init__.py +5 -0
  66. sam2/modeling/backbones/__init__.py +5 -0
  67. sam2/modeling/backbones/hieradet.py +317 -0
  68. sam2/modeling/backbones/image_encoder.py +134 -0
  69. sam2/modeling/backbones/utils.py +93 -0
  70. sam2/modeling/memory_attention.py +169 -0
  71. sam2/modeling/memory_encoder.py +181 -0
  72. sam2/modeling/position_encoding.py +239 -0
  73. sam2/modeling/sam/__init__.py +5 -0
  74. sam2/modeling/sam/mask_decoder.py +295 -0
  75. sam2/modeling/sam/prompt_encoder.py +202 -0
  76. sam2/modeling/sam/transformer.py +311 -0
  77. sam2/modeling/sam2_base.py +911 -0
  78. sam2/modeling/sam2_utils.py +323 -0
  79. sam2/sam2.1_hiera_b+.yaml +116 -0
  80. sam2/sam2.1_hiera_l.yaml +120 -0
  81. sam2/sam2.1_hiera_s.yaml +119 -0
  82. sam2/sam2.1_hiera_t.yaml +121 -0
  83. sam2/sam2_hiera_b+.yaml +113 -0
  84. sam2/sam2_hiera_l.yaml +117 -0
  85. sam2/sam2_hiera_s.yaml +116 -0
  86. sam2/sam2_hiera_t.yaml +118 -0
  87. sam2/sam2_image_predictor.py +475 -0
  88. sam2/sam2_video_predictor.py +1222 -0
  89. sam2/sam2_video_predictor_legacy.py +1172 -0
  90. sam2/utils/__init__.py +5 -0
  91. sam2/utils/amg.py +348 -0
  92. sam2/utils/misc.py +349 -0
  93. sam2/utils/transforms.py +118 -0
@@ -0,0 +1,449 @@
1
+ """Advanced optimized A* search implementation with waypoint support for finding brightest paths.
2
+ This extends the BidirectionalAStarSearch to support user-defined auxiliary waypoints with
3
+ performance optimizations matching the core algorithm.
4
+ """
5
+
6
+ import heapq
7
+ import math
8
+ import numpy as np
9
+ from collections import defaultdict
10
+ from typing import List, Tuple, Dict, Set, Any, Optional
11
+ import numba as nb
12
+ from numba import njit, prange, jit
13
+
14
+ from neuro_sam.brightest_path_lib.algorithm.astar import (
15
+ BidirectionalAStarSearch, array_equal, euclidean_distance_scaled,
16
+ find_2D_neighbors_optimized, find_3D_neighbors_optimized
17
+ )
18
+ from neuro_sam.brightest_path_lib.cost import Reciprocal
19
+ from neuro_sam.brightest_path_lib.heuristic import Euclidean
20
+ from neuro_sam.brightest_path_lib.image import ImageStats
21
+ from neuro_sam.brightest_path_lib.input import CostFunction, HeuristicFunction
22
+ from neuro_sam.brightest_path_lib.node import Node
23
+
24
+
25
+ # Numba-optimized function to check if all points are on the same z-plane
26
+ @nb.njit(cache=True)
27
+ def check_same_z_plane(start_point, goal_point, waypoints):
28
+ """Check if all points are on the same z-plane for 3D optimization"""
29
+ # If points are 2D, return True
30
+ if len(start_point) == 2:
31
+ return True
32
+
33
+ # For 3D points, check z-coordinates
34
+ if len(start_point) == 3:
35
+ z_val = start_point[0]
36
+
37
+ # Check if goal has same z
38
+ if goal_point[0] != z_val:
39
+ return False
40
+
41
+ # Check all waypoints
42
+ for i in range(len(waypoints)):
43
+ if waypoints[i][0] != z_val:
44
+ return False
45
+
46
+ # All points have same z
47
+ return True
48
+
49
+ # For higher dimensions, don't use the optimization
50
+ return False
51
+
52
+
53
+ # Numba-optimized function to create 2D points from 3D points
54
+ @nb.njit(cache=True)
55
+ def convert_3d_to_2d_points(start_point, goal_point, waypoints):
56
+ """Convert 3D points to 2D by removing z-coordinate"""
57
+ start_2d = np.array([start_point[1], start_point[2]], dtype=np.int32)
58
+ goal_2d = np.array([goal_point[1], goal_point[2]], dtype=np.int32)
59
+
60
+ # Convert waypoints
61
+ waypoints_2d = np.empty((len(waypoints), 2), dtype=np.int32)
62
+ for i in range(len(waypoints)):
63
+ waypoints_2d[i, 0] = waypoints[i][1]
64
+ waypoints_2d[i, 1] = waypoints[i][2]
65
+
66
+ return start_2d, goal_2d, waypoints_2d
67
+
68
+
69
+ # Numba-optimized function to convert 2D path back to 3D
70
+ @nb.njit(cache=True)
71
+ def convert_2d_path_to_3d(path_2d, z_value):
72
+ """Convert a 2D path back to 3D by adding z-coordinate"""
73
+ path_3d = np.empty((len(path_2d), 3), dtype=np.int32)
74
+
75
+ for i in range(len(path_2d)):
76
+ path_3d[i, 0] = z_value
77
+ path_3d[i, 1] = path_2d[i][0]
78
+ path_3d[i, 2] = path_2d[i][1]
79
+
80
+ return path_3d
81
+
82
+
83
+ class WaypointBidirectionalAStarSearch:
84
+ """Advanced bidirectional A* search implementation with waypoint support
85
+
86
+ This implementation allows users to specify intermediate points that
87
+ the path should pass through, breaking down a complex search into
88
+ multiple simpler searches between consecutive points.
89
+
90
+ Performance optimization: When waypoints are on the same z-plane,
91
+ the algorithm automatically uses 2D pathfinding instead of 3D.
92
+ """
93
+
94
+ def __init__(
95
+ self,
96
+ image: np.ndarray,
97
+ start_point: np.ndarray,
98
+ goal_point: np.ndarray,
99
+ waypoints: List[np.ndarray] = None,
100
+ scale: Tuple = (1.0, 1.0),
101
+ cost_function: CostFunction = CostFunction.RECIPROCAL,
102
+ heuristic_function: HeuristicFunction = HeuristicFunction.EUCLIDEAN,
103
+ open_nodes=None,
104
+ use_hierarchical: bool = False,
105
+ weight_heuristic: float = 1.0
106
+ ):
107
+ """Initialize waypoint-enabled A* search
108
+
109
+ Parameters
110
+ ----------
111
+ image : numpy ndarray
112
+ The image to search
113
+ start_point, goal_point : numpy ndarray
114
+ Start and goal coordinates
115
+ waypoints : List[numpy ndarray], optional
116
+ List of intermediate points that the path must pass through,
117
+ in the order they should be visited
118
+ scale : tuple
119
+ Image scale factors
120
+ cost_function, heuristic_function : Enum
121
+ Functions to use for cost and heuristic
122
+ open_nodes : Queue, optional
123
+ Queue for visualization
124
+ use_hierarchical : bool
125
+ Whether to use hierarchical search for large images
126
+ weight_heuristic : float
127
+ Weight for heuristic (> 1.0 makes search faster but less optimal)
128
+ """
129
+ self._validate_inputs(image, start_point, goal_point, waypoints)
130
+
131
+ # Basic parameters
132
+ self.image = image
133
+ self.image_stats = ImageStats(image)
134
+ self.start_point = np.round(start_point).astype(np.int32)
135
+ self.goal_point = np.round(goal_point).astype(np.int32)
136
+
137
+ # Process and validate waypoints if provided
138
+ if waypoints and len(waypoints) > 0:
139
+ self.waypoints = np.array([np.round(wp).astype(np.int32) for wp in waypoints])
140
+ else:
141
+ # Empty array with correct shape for numba compatibility
142
+ self.waypoints = np.empty((0, len(self.start_point)), dtype=np.int32)
143
+
144
+ self.scale = scale
145
+ self.open_nodes = open_nodes
146
+ self.use_hierarchical = use_hierarchical
147
+ self.weight_heuristic = weight_heuristic
148
+
149
+ if cost_function == CostFunction.RECIPROCAL:
150
+ self.cost_function = Reciprocal(
151
+ min_intensity=self.image_stats.min_intensity,
152
+ max_intensity=self.image_stats.max_intensity)
153
+
154
+ if heuristic_function == HeuristicFunction.EUCLIDEAN:
155
+ self.heuristic_function = Euclidean(scale=self.scale)
156
+
157
+ # State variables
158
+ self.is_canceled = False
159
+ self.found_path = False
160
+ self.evaluated_nodes = 0
161
+ self.result = []
162
+ self.segment_results = [] # Store individual segment paths
163
+ self.segment_evaluated_nodes = [] # Track nodes evaluated per segment
164
+
165
+ # Determine if we should use 2D mode (all points have same z-coordinate)
166
+ self.use_2d_mode = check_same_z_plane(
167
+ self.start_point, self.goal_point, self.waypoints)
168
+
169
+ # For 2D optimization when applicable
170
+ self.z_value = self.start_point[0] if len(self.start_point) == 3 else None
171
+
172
+ def _validate_inputs(
173
+ self,
174
+ image: np.ndarray,
175
+ start_point: np.ndarray,
176
+ goal_point: np.ndarray,
177
+ waypoints: Optional[List[np.ndarray]] = None
178
+ ):
179
+ """Validate input parameters"""
180
+ if image is None or start_point is None or goal_point is None:
181
+ raise TypeError("Image, start_point, and goal_point cannot be None")
182
+ if len(image) == 0 or len(start_point) == 0 or len(goal_point) == 0:
183
+ raise ValueError("Image, start_point, and goal_point cannot be empty")
184
+
185
+ # Validate waypoints if provided
186
+ if waypoints:
187
+ for i, wp in enumerate(waypoints):
188
+ if wp is None:
189
+ raise TypeError(f"Waypoint {i} cannot be None")
190
+ if len(wp) == 0:
191
+ raise ValueError(f"Waypoint {i} cannot be empty")
192
+ if len(wp) != len(start_point):
193
+ raise ValueError(f"Waypoint {i} dimensions must match start_point dimensions")
194
+
195
+ @property
196
+ def found_path(self) -> bool:
197
+ return self._found_path
198
+
199
+ @found_path.setter
200
+ def found_path(self, value: bool):
201
+ if value is None:
202
+ raise TypeError
203
+ self._found_path = value
204
+
205
+ @property
206
+ def is_canceled(self) -> bool:
207
+ return self._is_canceled
208
+
209
+ @is_canceled.setter
210
+ def is_canceled(self, value: bool):
211
+ if value is None:
212
+ raise TypeError
213
+ self._is_canceled = value
214
+
215
+ def search(self, verbose: bool = False) -> List[np.ndarray]:
216
+ """Perform A* search with waypoints
217
+
218
+ This method breaks down the search into multiple segments:
219
+ start→waypoint₁, waypoint₁→waypoint₂, ..., waypointₙ→goal
220
+
221
+ Performance optimization: Uses 2D mode when all points are on the same z-plane
222
+
223
+ Returns
224
+ -------
225
+ List[np.ndarray]
226
+ Complete path from start to goal through all waypoints
227
+ """
228
+ # Reset state
229
+ self.result = []
230
+ self.segment_results = []
231
+ self.segment_evaluated_nodes = []
232
+ self.evaluated_nodes = 0
233
+
234
+ # Check if we can use 2D mode for performance optimization
235
+ if self.use_2d_mode and len(self.start_point) == 3:
236
+ if verbose:
237
+ print("Using 2D mode optimization (all points on same z-plane)")
238
+ return self._search_2d_mode(verbose)
239
+ else:
240
+ return self._search_normal_mode(verbose)
241
+
242
+ def _search_2d_mode(self, verbose: bool = False) -> List[np.ndarray]:
243
+ """Perform search in 2D mode for performance optimization
244
+
245
+ This extracts a 2D slice from the 3D image when all points are on the same z-plane
246
+ """
247
+ # Extract 2D slice and convert points to 2D
248
+ image_2d = self.image[self.z_value]
249
+ start_2d, goal_2d, waypoints_2d = convert_3d_to_2d_points(
250
+ self.start_point, self.goal_point, self.waypoints)
251
+
252
+ # Create a list of all points in order, including start and goal
253
+ wp_list = [waypoints_2d[i] for i in range(len(waypoints_2d))]
254
+ all_points_2d = [start_2d] + wp_list + [goal_2d]
255
+
256
+ # Track overall success
257
+ overall_success = True
258
+
259
+ if verbose:
260
+ if len(wp_list) > 0:
261
+ print(f"Starting 2D search with {len(wp_list)} waypoints")
262
+ else:
263
+ print("Starting 2D search (no waypoints)")
264
+
265
+ # Process each segment (start to first waypoint, waypoint to waypoint, last waypoint to goal)
266
+ for i in range(len(all_points_2d) - 1):
267
+ if self.is_canceled:
268
+ return []
269
+
270
+ point_a = all_points_2d[i]
271
+ point_b = all_points_2d[i+1]
272
+
273
+ if verbose:
274
+ if i == 0:
275
+ print(f"Searching from start to {'waypoint' if i+1 < len(all_points_2d)-1 else 'goal'} {i+1}")
276
+ elif i+1 == len(all_points_2d)-1:
277
+ print(f"Searching from waypoint {i} to goal")
278
+ else:
279
+ print(f"Searching from waypoint {i} to waypoint {i+1}")
280
+
281
+ # Create A* search for this segment
282
+ segment_search = BidirectionalAStarSearch(
283
+ image=image_2d,
284
+ start_point=point_a,
285
+ goal_point=point_b,
286
+ scale=self.scale[:2], # Only use x,y scale
287
+ cost_function=CostFunction.RECIPROCAL,
288
+ heuristic_function=HeuristicFunction.EUCLIDEAN,
289
+ open_nodes=self.open_nodes,
290
+ use_hierarchical=self.use_hierarchical,
291
+ weight_heuristic=self.weight_heuristic
292
+ )
293
+
294
+ # Run the search for this segment
295
+ segment_path_2d = segment_search.search(verbose=verbose)
296
+
297
+ # Track stats and results
298
+ self.segment_evaluated_nodes.append(segment_search.evaluated_nodes)
299
+ self.evaluated_nodes += segment_search.evaluated_nodes
300
+
301
+ # Check if segment search was successful
302
+ if segment_search.found_path and len(segment_path_2d) > 0:
303
+ # Convert 2D path back to 3D
304
+ segment_path_3d = convert_2d_path_to_3d(segment_path_2d, self.z_value)
305
+ # Store as list of arrays for compatibility
306
+ segment_path_list = [segment_path_3d[i] for i in range(len(segment_path_3d))]
307
+ self.segment_results.append(segment_path_list)
308
+
309
+ if verbose:
310
+ print(f"Found path for segment {i+1} ({len(segment_path_2d)} points, {segment_search.evaluated_nodes} nodes evaluated)")
311
+ else:
312
+ if verbose:
313
+ print(f"Failed to find path for segment {i+1}")
314
+ overall_success = False
315
+ break
316
+
317
+ # If all segments were successful, combine the paths
318
+ if overall_success:
319
+ self._construct_complete_path()
320
+ self.found_path = True
321
+
322
+ if verbose:
323
+ print(f"Complete path found: {len(self.result)} points, {self.evaluated_nodes} total nodes evaluated")
324
+ else:
325
+ if verbose:
326
+ print("Failed to find a complete path through all waypoints")
327
+
328
+ return self.result
329
+
330
+ def _search_normal_mode(self, verbose: bool = False) -> List[np.ndarray]:
331
+ """Perform search in normal mode (without special 2D optimization)"""
332
+ # Create a list of all points in order, including start and goal
333
+ wp_list = [self.waypoints[i] for i in range(len(self.waypoints))]
334
+ all_points = [self.start_point] + wp_list + [self.goal_point]
335
+
336
+ # Track overall success
337
+ overall_success = True
338
+
339
+ if verbose:
340
+ if len(wp_list) > 0:
341
+ print(f"Starting search with {len(wp_list)} waypoints")
342
+ else:
343
+ print("Starting search (no waypoints)")
344
+
345
+ # Process each segment (start to first waypoint, waypoint to waypoint, last waypoint to goal)
346
+ for i in range(len(all_points) - 1):
347
+ if self.is_canceled:
348
+ return []
349
+
350
+ point_a = all_points[i]
351
+ point_b = all_points[i+1]
352
+
353
+ if verbose:
354
+ if i == 0:
355
+ print(f"Searching from start to {'waypoint' if i+1 < len(all_points)-1 else 'goal'} {i+1}")
356
+ elif i+1 == len(all_points)-1:
357
+ print(f"Searching from waypoint {i} to goal")
358
+ else:
359
+ print(f"Searching from waypoint {i} to waypoint {i+1}")
360
+
361
+ # Create A* search for this segment
362
+ segment_search = BidirectionalAStarSearch(
363
+ image=self.image,
364
+ start_point=point_a,
365
+ goal_point=point_b,
366
+ scale=self.scale,
367
+ cost_function=CostFunction.RECIPROCAL,
368
+ heuristic_function=HeuristicFunction.EUCLIDEAN,
369
+ open_nodes=self.open_nodes,
370
+ use_hierarchical=self.use_hierarchical,
371
+ weight_heuristic=self.weight_heuristic
372
+ )
373
+
374
+ # Run the search for this segment
375
+ segment_path = segment_search.search(verbose=verbose)
376
+
377
+ # Track stats and results
378
+ self.segment_evaluated_nodes.append(segment_search.evaluated_nodes)
379
+ self.evaluated_nodes += segment_search.evaluated_nodes
380
+
381
+ # Check if segment search was successful
382
+ if segment_search.found_path and len(segment_path) > 0:
383
+ self.segment_results.append(segment_path)
384
+
385
+ if verbose:
386
+ print(f"Found path for segment {i+1} ({len(segment_path)} points, {segment_search.evaluated_nodes} nodes evaluated)")
387
+ else:
388
+ if verbose:
389
+ print(f"Failed to find path for segment {i+1}")
390
+ overall_success = False
391
+ break
392
+
393
+ # If all segments were successful, combine the paths
394
+ if overall_success:
395
+ self._construct_complete_path()
396
+ self.found_path = True
397
+
398
+ if verbose:
399
+ print(f"Complete path found: {len(self.result)} points, {self.evaluated_nodes} total nodes evaluated")
400
+ else:
401
+ if verbose:
402
+ print("Failed to find a complete path through all waypoints")
403
+
404
+ return self.result
405
+
406
+ def _construct_complete_path(self):
407
+ """Combine segment paths into a complete path
408
+
409
+ This handles removing duplicate points at segment boundaries.
410
+ """
411
+ if not self.segment_results:
412
+ return
413
+
414
+ # Start with the first segment
415
+ self.result = self.segment_results[0].copy()
416
+
417
+ # Add each subsequent segment (skipping the first point to avoid duplication)
418
+ for i in range(1, len(self.segment_results)):
419
+ segment = self.segment_results[i]
420
+
421
+ # Skip the first point of each subsequent segment as it should
422
+ # be the same as the last point of the previous segment
423
+ # But verify they're actually the same
424
+ if len(segment) > 1 and np.array_equal(self.result[-1], segment[0]):
425
+ self.result.extend(segment[1:])
426
+ else:
427
+ # Something's wrong - just append everything
428
+ self.result.extend(segment)
429
+
430
+ def get_segment_info(self):
431
+ """Get information about each segment of the path
432
+
433
+ Returns
434
+ -------
435
+ dict
436
+ Dictionary with information about each path segment
437
+ """
438
+ if not self.segment_results:
439
+ return None
440
+
441
+ info = {
442
+ 'num_segments': len(self.segment_results),
443
+ 'segment_lengths': [len(path) for path in self.segment_results],
444
+ 'segment_evaluated_nodes': self.segment_evaluated_nodes,
445
+ 'total_path_length': len(self.result),
446
+ 'total_evaluated_nodes': self.evaluated_nodes,
447
+ 'used_2d_optimization': self.use_2d_mode and len(self.start_point) == 3
448
+ }
449
+ return info