Image intermediate locations between sample and background
This commit is contained in:
parent
39a04df5be
commit
f781e6b674
6 changed files with 90 additions and 23 deletions
|
|
@ -341,6 +341,19 @@ class SmartSpiral(ScanPlanner):
|
||||||
super().__init__(initial_position, planner_settings)
|
super().__init__(initial_position, planner_settings)
|
||||||
self._distance_cutoff: float = max([self._dx, self._dy]) * 1.1
|
self._distance_cutoff: float = max([self._dx, self._dy]) * 1.1
|
||||||
|
|
||||||
|
def _is_primary_location(self, location: FutureScanLocation) -> bool:
|
||||||
|
"""Return True if input is a primary location not a secondary (intermediate) location."""
|
||||||
|
return location.planner_data["primary"]
|
||||||
|
|
||||||
|
@property
|
||||||
|
def secondary_locations(self) -> XYZPosList:
|
||||||
|
"""A list of all secondary (intermediate) locations."""
|
||||||
|
return [
|
||||||
|
loc.xyz_tuple
|
||||||
|
for loc in self._path_history
|
||||||
|
if not self._is_primary_location(loc)
|
||||||
|
]
|
||||||
|
|
||||||
def _parse(self, planner_settings: Optional[dict] = None) -> None:
|
def _parse(self, planner_settings: Optional[dict] = None) -> None:
|
||||||
"""Parse SmartSpiral Settings dictionary.
|
"""Parse SmartSpiral Settings dictionary.
|
||||||
|
|
||||||
|
|
@ -366,7 +379,7 @@ class SmartSpiral(ScanPlanner):
|
||||||
|
|
||||||
For smart spiral this is just the first point
|
For smart spiral this is just the first point
|
||||||
"""
|
"""
|
||||||
return [FutureScanLocation(self._initial_position)]
|
return [FutureScanLocation(self._initial_position, primary=True)]
|
||||||
|
|
||||||
def mark_location_visited(
|
def mark_location_visited(
|
||||||
self, xyz_pos: XYZPos, imaged: bool = True, focused: bool = True
|
self, xyz_pos: XYZPos, imaged: bool = True, focused: bool = True
|
||||||
|
|
@ -381,8 +394,11 @@ class SmartSpiral(ScanPlanner):
|
||||||
super().mark_location_visited(xyz_pos, imaged, focused)
|
super().mark_location_visited(xyz_pos, imaged, focused)
|
||||||
|
|
||||||
xy_pos = enforce_xy_tuple(xyz_pos[:2])
|
xy_pos = enforce_xy_tuple(xyz_pos[:2])
|
||||||
if imaged:
|
if self._is_primary_location(self._path_history[-1]):
|
||||||
self._add_surrounding_positions(xy_pos)
|
if imaged:
|
||||||
|
self._add_surrounding_positions(xy_pos)
|
||||||
|
else:
|
||||||
|
self._add_intermediate_positions(xy_pos)
|
||||||
self._re_sort_remaining_locations(xy_pos)
|
self._re_sort_remaining_locations(xy_pos)
|
||||||
|
|
||||||
def _add_surrounding_positions(self, xy_pos: XYPos) -> None:
|
def _add_surrounding_positions(self, xy_pos: XYPos) -> None:
|
||||||
|
|
@ -394,24 +410,81 @@ class SmartSpiral(ScanPlanner):
|
||||||
* too far away
|
* too far away
|
||||||
* already planned
|
* already planned
|
||||||
* already visited
|
* already visited
|
||||||
|
|
||||||
|
If the already visited position was not imaged then an intermediate location is
|
||||||
|
added. See also self._add_intermediate_positions() for adding intermediate
|
||||||
|
locations after visiting a location that was not imaged.
|
||||||
"""
|
"""
|
||||||
new_positions = [
|
new_positions = self._adjacent_positions(xy_pos)
|
||||||
FutureScanLocation((xy_pos[0] - self._dx, xy_pos[1])),
|
|
||||||
FutureScanLocation((xy_pos[0] + self._dx, xy_pos[1])),
|
|
||||||
FutureScanLocation((xy_pos[0], xy_pos[1] - self._dy)),
|
|
||||||
FutureScanLocation((xy_pos[0], xy_pos[1] + self._dy)),
|
|
||||||
]
|
|
||||||
|
|
||||||
for new_pos in new_positions:
|
for new_pos in new_positions:
|
||||||
# Skip position if already planned or visited
|
# Skip position if already planned
|
||||||
if self.position_planned(new_pos) or self.position_visited(new_pos):
|
if self.position_planned(new_pos):
|
||||||
|
continue
|
||||||
|
if self.position_visited(new_pos):
|
||||||
|
# Get the VisitedScanLocation object if already visited
|
||||||
|
visited = self._path_history[self._path_history.index(new_pos)]
|
||||||
|
if visited.imaged:
|
||||||
|
# If this adjacent position was imaged succsfully already then skip.
|
||||||
|
continue
|
||||||
|
# If it wasn't imaged add an intimediate location between the
|
||||||
|
# last imaged position and this one.
|
||||||
|
i_pos = self._intermediate_position(xy_pos, new_pos)
|
||||||
|
# Set primary=False surrounding images are not added once imaged.
|
||||||
|
i_loc = FutureScanLocation(i_pos, primary=False)
|
||||||
|
# Append instantly without checking max_distance as this is between
|
||||||
|
# imaged points.
|
||||||
|
self._remaining_locations.append(i_loc)
|
||||||
continue
|
continue
|
||||||
|
|
||||||
dist = distance_between(new_pos, self._initial_position)
|
dist = distance_between(new_pos, self._initial_position)
|
||||||
if dist > self._max_dist:
|
if dist > self._max_dist:
|
||||||
LOGGER.debug("Rejected moving to %s as it is out of range", new_pos)
|
LOGGER.debug("Rejected moving to %s as it is out of range", new_pos)
|
||||||
continue
|
continue
|
||||||
self._remaining_locations.append(new_pos)
|
new_loc = FutureScanLocation(new_pos, primary=True)
|
||||||
|
self._remaining_locations.append(new_loc)
|
||||||
|
|
||||||
|
def _add_intermediate_positions(self, xy_pos: XYPos) -> None:
|
||||||
|
"""Add intermediate points after locating a background location.
|
||||||
|
|
||||||
|
This is called after an image is recorded that was background. Intermediate
|
||||||
|
locations are added between any adjacent locations that were successfully
|
||||||
|
imaged.
|
||||||
|
|
||||||
|
Note that in the case that an imaged location has an adjacent background image
|
||||||
|
then adding the intermediate image will be handled by
|
||||||
|
_add_surrounding_positions().
|
||||||
|
"""
|
||||||
|
surrounding_positions = self._adjacent_positions(xy_pos)
|
||||||
|
|
||||||
|
for surr_pos in surrounding_positions:
|
||||||
|
if self.position_visited(surr_pos):
|
||||||
|
# Get the VisitedScanLocation object if already visited
|
||||||
|
visited = self._path_history[self._path_history.index(surr_pos)]
|
||||||
|
if not visited.imaged:
|
||||||
|
# If it wasn't imaged then skip this position
|
||||||
|
continue
|
||||||
|
# If surrpounding location was imaged add an intimediate location
|
||||||
|
# between the most recent position and this imaged position.
|
||||||
|
i_pos = self._intermediate_position(xy_pos, surr_pos)
|
||||||
|
# Set primary=False surrounding images are not added once imaged.
|
||||||
|
i_loc = FutureScanLocation(i_pos, primary=False)
|
||||||
|
# Append instantly without checking max_distance as this is between
|
||||||
|
# imaged points.
|
||||||
|
self._remaining_locations.append(i_loc)
|
||||||
|
|
||||||
|
def _adjacent_positions(self, xy_pos: XYPos) -> XYPosList:
|
||||||
|
"""Return 4 points +/-dx and +/-dy from the input location."""
|
||||||
|
return [
|
||||||
|
(xy_pos[0] - self._dx, xy_pos[1]),
|
||||||
|
(xy_pos[0] + self._dx, xy_pos[1]),
|
||||||
|
(xy_pos[0], xy_pos[1] - self._dy),
|
||||||
|
(xy_pos[0], xy_pos[1] + self._dy),
|
||||||
|
]
|
||||||
|
|
||||||
|
def _intermediate_position(self, xy_pos1: XYPos, xy_pos2: XYPos) -> XYPos:
|
||||||
|
"""Return an (x,y) position halfway between two input positions."""
|
||||||
|
return tuple((i + j) // 2 for i, j in zip(xy_pos1, xy_pos2, strict=True))
|
||||||
|
|
||||||
def _re_sort_remaining_locations(self, current_pos: XYPos) -> None:
|
def _re_sort_remaining_locations(self, current_pos: XYPos) -> None:
|
||||||
"""Sort the remaining positions based on the current location."""
|
"""Sort the remaining positions based on the current location."""
|
||||||
|
|
|
||||||
|
|
@ -302,14 +302,6 @@ def test_example_smart_spiral():
|
||||||
expected_planner = (
|
expected_planner = (
|
||||||
scan_test_helpers.get_expected_result_for_example_smart_spiral(sample_name)
|
scan_test_helpers.get_expected_result_for_example_smart_spiral(sample_name)
|
||||||
)
|
)
|
||||||
# Use hasattr to check if the saved test data is the old object where imaged
|
|
||||||
# locations were saved directly as a list of tuples rather than being generated
|
assert planner.path_history == expected_planner.path_history
|
||||||
# from a history of VisitedScanLocation objects.
|
assert planner.imaged_locations == expected_planner.imaged_locations
|
||||||
# This "if" section of this if-else block can be deleted once we are confident
|
|
||||||
# of the new format and the pickles are updated
|
|
||||||
if hasattr(expected_planner, "_imaged_locations"):
|
|
||||||
assert planner.path_history == expected_planner._path_history
|
|
||||||
assert planner.imaged_locations == expected_planner._imaged_locations
|
|
||||||
else:
|
|
||||||
assert planner.path_history == expected_planner.path_history
|
|
||||||
assert planner.imaged_locations == expected_planner.imaged_locations
|
|
||||||
|
|
|
||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
|
@ -62,6 +62,7 @@ def visualise_scan(sample: FakeSample, planner: scan_planners.ScanPlanner) -> Fi
|
||||||
ax.add_artist(sample.patch)
|
ax.add_artist(sample.patch)
|
||||||
xh, yh = zip(*planner.path_history, strict=True)
|
xh, yh = zip(*planner.path_history, strict=True)
|
||||||
xi, yi, _zi = zip(*planner.imaged_locations, strict=True)
|
xi, yi, _zi = zip(*planner.imaged_locations, strict=True)
|
||||||
|
xs, ys, _zs = zip(*planner.secondary_locations, strict=True)
|
||||||
|
|
||||||
# convert history to numpy array so can calculate quiver arrows
|
# convert history to numpy array so can calculate quiver arrows
|
||||||
xh = np.array(xh)
|
xh = np.array(xh)
|
||||||
|
|
@ -78,6 +79,7 @@ def visualise_scan(sample: FakeSample, planner: scan_planners.ScanPlanner) -> Fi
|
||||||
)
|
)
|
||||||
plt.plot(xh, yh, "r.")
|
plt.plot(xh, yh, "r.")
|
||||||
plt.plot(xi, yi, "g*")
|
plt.plot(xi, yi, "g*")
|
||||||
|
plt.plot(xs, ys, "o", mfc="none", mec="blue")
|
||||||
ax.axis("equal")
|
ax.axis("equal")
|
||||||
return fig
|
return fig
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue