Merge branch 'visit-secondary-locations-first' into 'v3'
Visit secondary locations first See merge request openflexure/openflexure-microscope-server!444
This commit is contained in:
commit
d3bd921db2
5 changed files with 63 additions and 21 deletions
|
|
@ -409,7 +409,9 @@ class SmartSpiral(ScanPlanner):
|
|||
self._add_surrounding_positions(xy_pos)
|
||||
else:
|
||||
self._add_intermediate_positions(xy_pos)
|
||||
self._re_sort_remaining_locations(xy_pos)
|
||||
# Don't re-sort after imaging a secondary location or it can cause scan
|
||||
# direction to reverse, breaking the spiral.
|
||||
self._re_sort_remaining_locations(xy_pos)
|
||||
|
||||
def _add_surrounding_positions(self, xy_pos: XYPos) -> None:
|
||||
"""Add the 4 surrounding positions to the list of remaining locations to visit.
|
||||
|
|
@ -502,8 +504,9 @@ class SmartSpiral(ScanPlanner):
|
|||
"""Sort the remaining positions based on the current location."""
|
||||
|
||||
# Defined rather than use a lambda for readability
|
||||
def sort_key(pos: FutureScanLocation) -> tuple[float, float, float]:
|
||||
def sort_key(pos: FutureScanLocation) -> tuple[bool, float, float, float]:
|
||||
return (
|
||||
self._is_primary_location(pos), # False sorts low
|
||||
self.moves_between(current_pos, pos),
|
||||
self.moves_between(self._initial_position, pos),
|
||||
distance_between(current_pos, pos),
|
||||
|
|
|
|||
Binary file not shown.
Binary file not shown.
Binary file not shown.
77
tests/utilities/scan_test_helpers.py
Normal file → Executable file
77
tests/utilities/scan_test_helpers.py
Normal file → Executable file
|
|
@ -1,9 +1,12 @@
|
|||
#! /usr/bin/env python3
|
||||
|
||||
"""Utility functions for testing scan planners.
|
||||
|
||||
These including fake sample creation, scan path visualisation, and
|
||||
persistent storage of expected scan paths for samples.
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import os
|
||||
import pickle
|
||||
|
||||
|
|
@ -18,6 +21,8 @@ from openflexure_microscope_server import scan_planners
|
|||
|
||||
THIS_DIR = os.path.dirname(os.path.realpath(__file__))
|
||||
|
||||
ALL_SAMPLE_NAMES = ("lobed", "regular", "core")
|
||||
|
||||
|
||||
class FakeSample:
|
||||
"""A fake sample to test scan algorithms.
|
||||
|
|
@ -62,7 +67,10 @@ def visualise_scan(sample: FakeSample, planner: scan_planners.ScanPlanner) -> Fi
|
|||
ax.add_artist(sample.patch)
|
||||
xh, yh = zip(*planner.path_history, strict=True)
|
||||
xi, yi, _zi = zip(*planner.imaged_locations, strict=True)
|
||||
xs, ys, _zs = zip(*planner.secondary_locations, strict=True)
|
||||
if planner.secondary_locations:
|
||||
xs, ys, _zs = zip(*planner.secondary_locations, strict=True)
|
||||
else:
|
||||
xs, ys, _zs = [], [], []
|
||||
|
||||
# convert history to numpy array so can calculate quiver arrows
|
||||
xh = np.array(xh)
|
||||
|
|
@ -135,33 +143,33 @@ def example_smart_spiral(
|
|||
return sample, planner
|
||||
|
||||
|
||||
def profile_and_save_plot_for_example_smart_spiral():
|
||||
"""Run the example scan and save a plot and the profile data.
|
||||
|
||||
Also print the cumulative stats.
|
||||
|
||||
This runs if you run this file directly.
|
||||
"""
|
||||
def profile_example_smart_spiral():
|
||||
"""Profile running an example scan and print the cumulative profile stats."""
|
||||
import pstats
|
||||
import cProfile
|
||||
|
||||
profiler = cProfile.Profile()
|
||||
sample, planner = profiler.runcall(example_smart_spiral)
|
||||
profiler.runcall(example_smart_spiral)
|
||||
|
||||
stats_fname = os.path.join(THIS_DIR, "scan_example_stats.pstats")
|
||||
profiler.dump_stats(stats_fname)
|
||||
|
||||
png_fname = os.path.join(THIS_DIR, "scan_example_plot.png")
|
||||
fig = visualise_scan(sample, planner)
|
||||
fig.savefig(png_fname, dpi=200)
|
||||
|
||||
run_stats = pstats.Stats(stats_fname)
|
||||
run_stats.strip_dirs()
|
||||
run_stats.sort_stats("cumulative")
|
||||
run_stats.print_stats("scan_planners.py")
|
||||
|
||||
|
||||
def update_example_smart_spiral_pickle(sample_name: str):
|
||||
def plot_all_examples():
|
||||
"""Plot all examples as png files."""
|
||||
for sample_name in ALL_SAMPLE_NAMES:
|
||||
sample, planner = example_smart_spiral(sample_name)
|
||||
png_fname = os.path.join(THIS_DIR, f"scan_example_plot_{sample_name}.png")
|
||||
fig = visualise_scan(sample, planner)
|
||||
fig.savefig(png_fname, dpi=200)
|
||||
|
||||
|
||||
def update_example_smart_spiral_pickles():
|
||||
"""Pickle the ScanPlanner for the example_smart_spiral().
|
||||
|
||||
This is done so the history can be compared by testing to check
|
||||
|
|
@ -173,10 +181,11 @@ def update_example_smart_spiral_pickle(sample_name: str):
|
|||
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, f"example_smart_spiral_{sample_name}.pkl")
|
||||
_, planner = example_smart_spiral(sample_name)
|
||||
with open(pkl_fname, "wb") as pkl_file_obj:
|
||||
pickle.dump(planner, pkl_file_obj, pickle.HIGHEST_PROTOCOL)
|
||||
for sample_name in ALL_SAMPLE_NAMES:
|
||||
pkl_fname = os.path.join(THIS_DIR, f"example_smart_spiral_{sample_name}.pkl")
|
||||
_, planner = example_smart_spiral(sample_name)
|
||||
with open(pkl_fname, "wb") as pkl_file_obj:
|
||||
pickle.dump(planner, pkl_file_obj, pickle.HIGHEST_PROTOCOL)
|
||||
|
||||
|
||||
def get_expected_result_for_example_smart_spiral(
|
||||
|
|
@ -227,5 +236,35 @@ def load_sample_points(sample_name: str):
|
|||
return sample_options[sample_name]
|
||||
|
||||
|
||||
def main():
|
||||
"""Run the profiler, the plotting, or update the pickles based on command line input.
|
||||
|
||||
This only runs if run as a command line script.
|
||||
"""
|
||||
parser = argparse.ArgumentParser(description="Simulated scan-planning utility")
|
||||
|
||||
subparsers = parser.add_subparsers(dest="command", required=True)
|
||||
|
||||
# profile
|
||||
subparsers.add_parser("profile", help="Run simulation under cProfile")
|
||||
|
||||
# plot
|
||||
subparsers.add_parser("plot", help="Run simulation and produce plots")
|
||||
|
||||
# update
|
||||
subparsers.add_parser("update", help="Update stored reference pickles")
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
if args.command == "profile":
|
||||
profile_example_smart_spiral()
|
||||
elif args.command == "plot":
|
||||
plot_all_examples()
|
||||
elif args.command == "update":
|
||||
update_example_smart_spiral_pickles()
|
||||
else:
|
||||
parser.error(f"Unknown command: {args.command}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
profile_and_save_plot_for_example_smart_spiral()
|
||||
main()
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue