Update testing for multiple sample shapes
This commit is contained in:
parent
7373424fcf
commit
b414697cfb
7 changed files with 61 additions and 142 deletions
|
|
@ -240,129 +240,6 @@ class ScanPlanner:
|
||||||
|
|
||||||
|
|
||||||
class SmartSpiral(ScanPlanner):
|
class SmartSpiral(ScanPlanner):
|
||||||
"""
|
|
||||||
This is a smart spiral scan that spirals out from the centre.
|
|
||||||
|
|
||||||
Each time and image is taken the four neighbouring images are added
|
|
||||||
to the list of poisitions to image (unless they are already listed or
|
|
||||||
tried). However if a location is not imaged due no sample being detected
|
|
||||||
then neibouring positions are not imaged.
|
|
||||||
|
|
||||||
The next image taken is the closes to the centre (considering the largest
|
|
||||||
of vertical or horizontal distance), ties are broken by the distance from
|
|
||||||
the current position.
|
|
||||||
"""
|
|
||||||
|
|
||||||
_max_dist: int = 0
|
|
||||||
_dx: int = 0
|
|
||||||
_dy: int = 0
|
|
||||||
|
|
||||||
def _parse(self, planner_settings: Optional[dict] = None) -> None:
|
|
||||||
"""
|
|
||||||
Parse SmartSpiral Settings. This should be a dictionary
|
|
||||||
|
|
||||||
"dx" - the movement size in x
|
|
||||||
"dy" - the movement size in y
|
|
||||||
"max_dist" - The maximum distance to a location can be from the centre.
|
|
||||||
"""
|
|
||||||
|
|
||||||
expected_keys = ["max_dist", "dx", "dy"]
|
|
||||||
invalid_msg = "SmartSpiral requires a planner_settings dictionary with keys: "
|
|
||||||
if not planner_settings:
|
|
||||||
raise ValueError(invalid_msg + ",".join(expected_keys))
|
|
||||||
if not all(keys in planner_settings for keys in expected_keys):
|
|
||||||
raise KeyError(invalid_msg + ",".join(expected_keys))
|
|
||||||
|
|
||||||
self._dx = int(planner_settings["dx"])
|
|
||||||
self._dy = int(planner_settings["dy"])
|
|
||||||
self._max_dist = int(planner_settings["max_dist"])
|
|
||||||
|
|
||||||
def _intial_location_list(self) -> XYPosList:
|
|
||||||
"""
|
|
||||||
Called on initalisation. Sets the initial list of locations for this scan planner
|
|
||||||
|
|
||||||
For smart spiral this is just the first point
|
|
||||||
"""
|
|
||||||
return [self._initial_position]
|
|
||||||
|
|
||||||
def mark_location_visited(
|
|
||||||
self, xyz_pos: XYZPos, imaged: bool = True, focused: bool = True
|
|
||||||
) -> None:
|
|
||||||
"""
|
|
||||||
Mark the location as visited. Adjust extra positions accordingly
|
|
||||||
|
|
||||||
Args:
|
|
||||||
xyz_pos: the x_y_z position
|
|
||||||
imaged: true if an image was taken, false if not (due to background detect)
|
|
||||||
focused: true if autofocus completed successfully
|
|
||||||
"""
|
|
||||||
# First call the base class to update the positions
|
|
||||||
super().mark_location_visited(xyz_pos, imaged, focused)
|
|
||||||
|
|
||||||
xy_pos = enforce_xy_tuple(xyz_pos[:2])
|
|
||||||
if imaged:
|
|
||||||
self._add_surrounding_positions(xy_pos)
|
|
||||||
self._re_sort_remaining_locations(xy_pos)
|
|
||||||
|
|
||||||
def _add_surrounding_positions(self, xy_pos: XYPos) -> None:
|
|
||||||
"""
|
|
||||||
This adds the surrounding (4 point connectivity) positions
|
|
||||||
to the remaining locations if they are not too far away or
|
|
||||||
already planned or already visited
|
|
||||||
"""
|
|
||||||
new_positions = [
|
|
||||||
(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),
|
|
||||||
]
|
|
||||||
|
|
||||||
for new_pos in new_positions:
|
|
||||||
# Skip position if already planned or visited
|
|
||||||
if self.position_planned(new_pos) or self.position_visited(new_pos):
|
|
||||||
continue
|
|
||||||
|
|
||||||
dist = distance_between(new_pos, self._initial_position)
|
|
||||||
if dist > self._max_dist:
|
|
||||||
LOGGER.debug("Rejected moving to %s as it is out of range", new_pos)
|
|
||||||
continue
|
|
||||||
self._remaining_locations.append(new_pos)
|
|
||||||
|
|
||||||
def _re_sort_remaining_locations(self, current_pos: XYPos) -> None:
|
|
||||||
"""
|
|
||||||
Sort the remaining positions besed on the current location
|
|
||||||
"""
|
|
||||||
|
|
||||||
# Defined rather than use a lambda for readability
|
|
||||||
def sort_key(pos):
|
|
||||||
return self.moves_from_centre(pos), distance_between(current_pos, pos)
|
|
||||||
|
|
||||||
self._remaining_locations.sort(key=sort_key)
|
|
||||||
|
|
||||||
def moves_from_centre(
|
|
||||||
self,
|
|
||||||
xy_pos: XYPos,
|
|
||||||
) -> float:
|
|
||||||
"""
|
|
||||||
Return the number of moves from the centre in the x or y direction
|
|
||||||
whichever is largest
|
|
||||||
|
|
||||||
Args:
|
|
||||||
xy_pos: the position
|
|
||||||
|
|
||||||
Note this has been renamed from `steps_from_centre` as that implied
|
|
||||||
stepper motor steps not number of moves in a scan
|
|
||||||
"""
|
|
||||||
move_size = np.array([self._dx, self._dy])
|
|
||||||
starting_pos = np.array(self._initial_position, dtype="float64")
|
|
||||||
current_pos = np.array(xy_pos, dtype="float64")
|
|
||||||
|
|
||||||
displacement_in_moves = (current_pos - starting_pos) / move_size
|
|
||||||
|
|
||||||
return np.max(np.abs(displacement_in_moves))
|
|
||||||
|
|
||||||
|
|
||||||
class ShortSmartSpiral(ScanPlanner):
|
|
||||||
"""
|
"""
|
||||||
This is a smart spiral scan that spirals out from the centre, but prioritises
|
This is a smart spiral scan that spirals out from the centre, but prioritises
|
||||||
short moves over rigidly sticking to minimising radius from the centre of the
|
short moves over rigidly sticking to minimising radius from the centre of the
|
||||||
|
|
|
||||||
|
|
@ -253,7 +253,22 @@ def test_closest_focus_wth_large_numbers():
|
||||||
|
|
||||||
|
|
||||||
def test_example_smart_spiral():
|
def test_example_smart_spiral():
|
||||||
_, planner = scan_test_helpers.example_smart_spiral()
|
"""Test the smart spiral scan algorithm on the sample types listed
|
||||||
expected_planner = scan_test_helpers.get_expected_result_for_example_smart_spiral()
|
below and defined in scan_test_helpers.load_sample_points
|
||||||
assert planner.path_history == expected_planner.path_history
|
|
||||||
assert planner.imaged_locations == expected_planner.imaged_locations
|
Will fail if the locations or path between locations visited has changed
|
||||||
|
for any of the samples listed"""
|
||||||
|
example_samples = [
|
||||||
|
"regular",
|
||||||
|
"lobed",
|
||||||
|
"core",
|
||||||
|
]
|
||||||
|
for sample_type in example_samples:
|
||||||
|
_, planner = scan_test_helpers.example_smart_spiral(sample=sample_type)
|
||||||
|
expected_planner = (
|
||||||
|
scan_test_helpers.get_expected_result_for_example_smart_spiral(
|
||||||
|
sample=sample_type
|
||||||
|
)
|
||||||
|
)
|
||||||
|
assert planner.path_history == expected_planner.path_history
|
||||||
|
assert planner.imaged_locations == expected_planner.imaged_locations
|
||||||
|
|
|
||||||
Binary file not shown.
BIN
tests/utilities/example_smart_spiral_core.pkl
Normal file
BIN
tests/utilities/example_smart_spiral_core.pkl
Normal file
Binary file not shown.
BIN
tests/utilities/example_smart_spiral_lobed.pkl
Normal file
BIN
tests/utilities/example_smart_spiral_lobed.pkl
Normal file
Binary file not shown.
BIN
tests/utilities/example_smart_spiral_regular.pkl
Normal file
BIN
tests/utilities/example_smart_spiral_regular.pkl
Normal file
Binary file not shown.
|
|
@ -111,23 +111,19 @@ def interp_closed_path(xy_points: list[tuple[int, int]], n_points: int) -> MatPa
|
||||||
return MatPath(path_points, closed=True)
|
return MatPath(path_points, closed=True)
|
||||||
|
|
||||||
|
|
||||||
def example_smart_spiral() -> tuple[FakeSample, scan_planners.ScanPlanner]:
|
def example_smart_spiral(
|
||||||
|
sample: str = "lobed",
|
||||||
|
) -> tuple[FakeSample, scan_planners.ScanPlanner]:
|
||||||
"""
|
"""
|
||||||
Run an example scan and return the sample scanned and the planner object
|
Run an example scan and return the sample scanned and the planner object
|
||||||
after scan is complete
|
after scan is complete
|
||||||
"""
|
"""
|
||||||
xy_sample_points = [
|
xy_sample_points = load_sample_points(sample=sample)
|
||||||
(-5000, -5000),
|
|
||||||
(-2000, 10000),
|
|
||||||
(1000, 2000),
|
|
||||||
(6000, 7000),
|
|
||||||
(9000, 2000),
|
|
||||||
]
|
|
||||||
sample = FakeSample(xy_sample_points)
|
sample = FakeSample(xy_sample_points)
|
||||||
img_size = (1000, 1000)
|
img_size = (1000, 1000)
|
||||||
intial_position = (0, 0)
|
intial_position = (0, 0)
|
||||||
planner_settings = {"dx": 700, "dy": 700, "max_dist": 100000}
|
planner_settings = {"dx": 1200, "dy": 800, "max_dist": 100000}
|
||||||
planner = scan_planners.ShortSmartSpiral(
|
planner = scan_planners.SmartSpiral(
|
||||||
intial_position=intial_position, planner_settings=planner_settings
|
intial_position=intial_position, planner_settings=planner_settings
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -166,7 +162,7 @@ def profile_and_save_plot_for_example_smart_spiral():
|
||||||
run_stats.print_stats("scan_planners.py")
|
run_stats.print_stats("scan_planners.py")
|
||||||
|
|
||||||
|
|
||||||
def update_example_smart_spiral_pickle():
|
def update_example_smart_spiral_pickle(sample: str):
|
||||||
"""
|
"""
|
||||||
Pickle the ScanPlanner for the example_smart_spiral(),
|
Pickle the ScanPlanner for the example_smart_spiral(),
|
||||||
this is done so the history can be compared by testing to check
|
this is done so the history can be compared by testing to check
|
||||||
|
|
@ -174,23 +170,54 @@ def update_example_smart_spiral_pickle():
|
||||||
|
|
||||||
If the algorithm is purposefully changed then this will need to be
|
If the algorithm is purposefully changed then this will need to be
|
||||||
run to update the pickle for the test to pass.
|
run to update the pickle for the test to pass.
|
||||||
|
|
||||||
|
Takes sample, the sample type we have generated, so we can make a
|
||||||
|
pickle for each sample type
|
||||||
"""
|
"""
|
||||||
pkl_fname = os.path.join(THIS_DIR, "example_smart_spiral.pkl")
|
pkl_fname = os.path.join(THIS_DIR, f"example_smart_spiral_{sample}.pkl")
|
||||||
with open(pkl_fname, "wb") as pkl_file_obj:
|
with open(pkl_fname, "wb") as pkl_file_obj:
|
||||||
_, planner = example_smart_spiral()
|
_, planner = example_smart_spiral(sample=sample)
|
||||||
pickle.dump(planner, pkl_file_obj, pickle.HIGHEST_PROTOCOL)
|
pickle.dump(planner, pkl_file_obj, pickle.HIGHEST_PROTOCOL)
|
||||||
|
|
||||||
|
|
||||||
def get_expected_result_for_example_smart_spiral():
|
def get_expected_result_for_example_smart_spiral(sample: str):
|
||||||
"""
|
"""
|
||||||
Return the expected ScanPlanner object for the example_smart_spiral(),
|
Return the expected ScanPlanner object for the example_smart_spiral(),
|
||||||
this is pickled, so that it can be committed.
|
this is pickled, so that it can be committed.
|
||||||
"""
|
"""
|
||||||
pkl_fname = os.path.join(THIS_DIR, "example_smart_spiral.pkl")
|
pkl_fname = os.path.join(THIS_DIR, f"example_smart_spiral_{sample}.pkl")
|
||||||
with open(pkl_fname, "rb") as pkl_file_obj:
|
with open(pkl_fname, "rb") as pkl_file_obj:
|
||||||
planner = pickle.load(pkl_file_obj)
|
planner = pickle.load(pkl_file_obj)
|
||||||
return planner
|
return planner
|
||||||
|
|
||||||
|
|
||||||
|
def load_sample_points(sample: str):
|
||||||
|
"""Returns the points to generate a FakeSample of sample type "sample" """
|
||||||
|
sample_options = {
|
||||||
|
"lobed": [
|
||||||
|
(-5000, -5000),
|
||||||
|
(-2000, 16000),
|
||||||
|
(1000, 2000),
|
||||||
|
(6000, 7000),
|
||||||
|
(9000, 2000),
|
||||||
|
],
|
||||||
|
"regular": [
|
||||||
|
(-5000, -5000),
|
||||||
|
(-5000, 5000),
|
||||||
|
(5000, 5000),
|
||||||
|
(5000, -5000),
|
||||||
|
],
|
||||||
|
"core": [
|
||||||
|
(-12000, 2000),
|
||||||
|
(-12000, 1000),
|
||||||
|
(0, -2000),
|
||||||
|
(10000, -2000),
|
||||||
|
(10000, -1000),
|
||||||
|
(1000, 0),
|
||||||
|
],
|
||||||
|
}
|
||||||
|
return sample_options[sample]
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
profile_and_save_plot_for_example_smart_spiral()
|
profile_and_save_plot_for_example_smart_spiral()
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue