From 090e4e0d1d8108dfd8f680e8c0d045236d014c5a Mon Sep 17 00:00:00 2001 From: Martin Hjelmare Date: Sun, 10 Nov 2019 00:20:29 +0100 Subject: [PATCH 01/76] Add production plugin --- camacqplugins/production/__init__.py | 38 ++++++++++++++++++++++++++++ requirements.txt | 2 +- setup.cfg | 2 +- setup.py | 1 + 4 files changed, 41 insertions(+), 2 deletions(-) create mode 100644 camacqplugins/production/__init__.py diff --git a/camacqplugins/production/__init__.py b/camacqplugins/production/__init__.py new file mode 100644 index 0000000..7770ded --- /dev/null +++ b/camacqplugins/production/__init__.py @@ -0,0 +1,38 @@ +"""Provide a plugin for production standard flow.""" +import asyncio +import logging + +import voluptuous as vol + +from camacq.plugins.sample import ACTION_TO_METHOD +from camacq.util import read_csv + +_LOGGER = logging.getLogger(__name__) + +SAMPLE_STATE_FILE = "state_file" + + +async def setup_module(center, config): + """Set up Leica api package.""" + print("Production plugin setup!") + + conf = config["production"] + state_file = conf.get(SAMPLE_STATE_FILE) if conf else None + if state_file is None: + return + state_data = await center.add_executor_job(read_csv, state_file) + tasks = [] + for data in state_data: + for action_id, options in ACTION_TO_METHOD.items(): + schema = options["schema"] + try: + schema(data) + except vol.Invalid as exc: + _LOGGER.debug("Skipping action %s: %s", action_id, exc) + continue + tasks.append( + center.create_task(center.actions.call("sample", action_id, **data)) + ) + + if tasks: + await asyncio.wait(tasks) diff --git a/requirements.txt b/requirements.txt index 552706a..438a380 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1 +1 @@ -camacq==0.4.0 \ No newline at end of file +camacq==0.4.0 diff --git a/setup.cfg b/setup.cfg index eca367a..9d95385 100644 --- a/setup.cfg +++ b/setup.cfg @@ -2,4 +2,4 @@ max-line-length = 88 [pydocstyle] -add-ignore = D202 \ No newline at end of file +add-ignore = D202 diff --git a/setup.py b/setup.py index 0ffa448..8aada30 100644 --- a/setup.py +++ b/setup.py @@ -23,6 +23,7 @@ packages=setuptools.find_packages(), python_requires=">=3.6", install_requires=REQUIRES, + entry_points={"camacq.plugins": ["production = camacqplugins.production",],}, classifiers=[ "Development Status :: 2 - Pre-Alpha", "Programming Language :: Python", From 2ea476d69ffa3947c222defb5fc5ca3ff19b0b42 Mon Sep 17 00:00:00 2001 From: Martin Hjelmare Date: Sun, 10 Nov 2019 04:10:24 +0100 Subject: [PATCH 02/76] Add pylintrc --- pylintrc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pylintrc b/pylintrc index 5f3bd50..199fca4 100644 --- a/pylintrc +++ b/pylintrc @@ -7,4 +7,4 @@ disable= bad-continuation, duplicate-code, locally-disabled, - unused-argument, \ No newline at end of file + unused-argument, From 1cecc00478dcd57d45895eb889407df7524b0635 Mon Sep 17 00:00:00 2001 From: Martin Hjelmare Date: Sun, 10 Nov 2019 04:11:38 +0100 Subject: [PATCH 03/76] Add more event handlers --- camacqplugins/production/__init__.py | 68 ++++++++++++++++++++++------ 1 file changed, 55 insertions(+), 13 deletions(-) diff --git a/camacqplugins/production/__init__.py b/camacqplugins/production/__init__.py index 7770ded..ab2abbe 100644 --- a/camacqplugins/production/__init__.py +++ b/camacqplugins/production/__init__.py @@ -4,7 +4,7 @@ import voluptuous as vol -from camacq.plugins.sample import ACTION_TO_METHOD +from camacq.helper.template import TemplateFunctions from camacq.util import read_csv _LOGGER = logging.getLogger(__name__) @@ -18,21 +18,63 @@ async def setup_module(center, config): conf = config["production"] state_file = conf.get(SAMPLE_STATE_FILE) if conf else None - if state_file is None: - return + if state_file is not None: + await load_sample(center, state_file) + else: + start_exp(center) + + add_next_well(center) + + +async def load_sample(center, state_file): + """Load sample state from file.""" state_data = await center.add_executor_job(read_csv, state_file) tasks = [] for data in state_data: - for action_id, options in ACTION_TO_METHOD.items(): - schema = options["schema"] - try: - schema(data) - except vol.Invalid as exc: - _LOGGER.debug("Skipping action %s: %s", action_id, exc) - continue - tasks.append( - center.create_task(center.actions.call("sample", action_id, **data)) - ) + for action in center.actions.sample.values(): + tasks.append(center.create_task(action(silent=True, **data))) if tasks: await asyncio.wait(tasks) + + +def start_exp(center): + """Trigger on start experiment.""" + + async def start(center, event): + """Run on start event.""" + print("Camacq started!!!") + + await center.actions.sample.set_well(plate_name="00", well_x=0, well_y=0) + + center.bus.register("camacq_start_event", start) + + +def add_next_well(center): + """Add next well.""" + + async def well_event(center, event): + """Run on well event.""" + print("Well event!!!") + if not match_event(event, field_x=1, field_y=2, well_img_ok=True): + return + + plate_name = "00" + sample_helper = TemplateFunctions(center) + well_x, well_y = sample_helper.next_well_xy(plate_name, x_wells=12, y_wells=8) + + await center.actions.sample.set_well( + plate_name=plate_name, well_x=well_x, well_y=well_y + ) + + center.bus.register("well_event", well_event) + + +def match_event(event, **event_data): + """Return True if event matches.""" + if not event_data or all( + val == getattr(event, key, None) for key, val in event_data.items() + ): + return True + + return False From 2d8caed3ae6f417b6c4e124554e829a8de31f9ab Mon Sep 17 00:00:00 2001 From: Martin Hjelmare Date: Sun, 10 Nov 2019 14:23:45 +0100 Subject: [PATCH 04/76] Import event matcher --- camacqplugins/production/__init__.py | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/camacqplugins/production/__init__.py b/camacqplugins/production/__init__.py index ab2abbe..5acd578 100644 --- a/camacqplugins/production/__init__.py +++ b/camacqplugins/production/__init__.py @@ -4,6 +4,7 @@ import voluptuous as vol +from camacq.event import match_event from camacq.helper.template import TemplateFunctions from camacq.util import read_csv @@ -68,13 +69,3 @@ async def well_event(center, event): ) center.bus.register("well_event", well_event) - - -def match_event(event, **event_data): - """Return True if event matches.""" - if not event_data or all( - val == getattr(event, key, None) for key, val in event_data.items() - ): - return True - - return False From 9994fac85748e07524480f6bfd9768b4d0a5d447 Mon Sep 17 00:00:00 2001 From: Martin Hjelmare Date: Sun, 10 Nov 2019 14:44:52 +0100 Subject: [PATCH 05/76] Use sample helper --- camacqplugins/production/__init__.py | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/camacqplugins/production/__init__.py b/camacqplugins/production/__init__.py index 5acd578..3a3929c 100644 --- a/camacqplugins/production/__init__.py +++ b/camacqplugins/production/__init__.py @@ -6,6 +6,7 @@ from camacq.event import match_event from camacq.helper.template import TemplateFunctions +from camacq.plugins.sample.helper import next_well_xy from camacq.util import read_csv _LOGGER = logging.getLogger(__name__) @@ -44,8 +45,6 @@ def start_exp(center): async def start(center, event): """Run on start event.""" - print("Camacq started!!!") - await center.actions.sample.set_well(plate_name="00", well_x=0, well_y=0) center.bus.register("camacq_start_event", start) @@ -56,13 +55,12 @@ def add_next_well(center): async def well_event(center, event): """Run on well event.""" - print("Well event!!!") if not match_event(event, field_x=1, field_y=2, well_img_ok=True): return + # TODO: Make plate and well coordinates configurable. plate_name = "00" - sample_helper = TemplateFunctions(center) - well_x, well_y = sample_helper.next_well_xy(plate_name, x_wells=12, y_wells=8) + well_x, well_y = next_well_xy(center.sample, plate_name, x_wells=12, y_wells=8) await center.actions.sample.set_well( plate_name=plate_name, well_x=well_x, well_y=well_y From dee5216bfb5ce53042f3be268cf4c91f3f2db8c4 Mon Sep 17 00:00:00 2001 From: Martin Hjelmare Date: Sun, 10 Nov 2019 14:51:53 +0100 Subject: [PATCH 06/76] Clean up --- camacqplugins/production/__init__.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/camacqplugins/production/__init__.py b/camacqplugins/production/__init__.py index 3a3929c..8fb1a91 100644 --- a/camacqplugins/production/__init__.py +++ b/camacqplugins/production/__init__.py @@ -2,10 +2,7 @@ import asyncio import logging -import voluptuous as vol - from camacq.event import match_event -from camacq.helper.template import TemplateFunctions from camacq.plugins.sample.helper import next_well_xy from camacq.util import read_csv From 724b0b32d9dda4ccb039f0a27638ac9597b9f89f Mon Sep 17 00:00:00 2001 From: Martin Hjelmare Date: Sun, 10 Nov 2019 14:53:40 +0100 Subject: [PATCH 07/76] Fix stale docstring --- camacqplugins/production/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/camacqplugins/production/__init__.py b/camacqplugins/production/__init__.py index 8fb1a91..19ca564 100644 --- a/camacqplugins/production/__init__.py +++ b/camacqplugins/production/__init__.py @@ -12,7 +12,7 @@ async def setup_module(center, config): - """Set up Leica api package.""" + """Set up production plugin.""" print("Production plugin setup!") conf = config["production"] From ee502c3e1c6aa7ecf6ff429f964d905d0b59d804 Mon Sep 17 00:00:00 2001 From: Martin Hjelmare Date: Tue, 12 Nov 2019 14:57:59 +0100 Subject: [PATCH 08/76] Add stop trigger --- camacqplugins/production/__init__.py | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/camacqplugins/production/__init__.py b/camacqplugins/production/__init__.py index 19ca564..3ed5ed5 100644 --- a/camacqplugins/production/__init__.py +++ b/camacqplugins/production/__init__.py @@ -23,6 +23,7 @@ async def setup_module(center, config): start_exp(center) add_next_well(center) + stop_exp(center) async def load_sample(center, state_file): @@ -64,3 +65,22 @@ async def well_event(center, event): ) center.bus.register("well_event", well_event) + + +def stop_exp(center): + """Trigger to stop experiment.""" + + async def stop(center, event): + """Run to stop the experiment.""" + next_well_x, _ = next_well_xy("00", 2, 2) + + if ( + not match_event(event, field_x=1, field_y=2, well_img_ok=True) + or next_well_x is not None + ): + return + + await center.actions.automations.delay(seconds=2.0) + await center.actions.api.stop_imaging() + + center.bus.register("well_event", stop) From e8acede89f9bd1b739e9ddad1263246c25d3baca Mon Sep 17 00:00:00 2001 From: Martin Hjelmare Date: Tue, 12 Nov 2019 20:57:17 +0100 Subject: [PATCH 09/76] Add image next well --- camacqplugins/production/__init__.py | 41 +++++++++++++++++++++++----- 1 file changed, 34 insertions(+), 7 deletions(-) diff --git a/camacqplugins/production/__init__.py b/camacqplugins/production/__init__.py index 3ed5ed5..81d401f 100644 --- a/camacqplugins/production/__init__.py +++ b/camacqplugins/production/__init__.py @@ -3,6 +3,7 @@ import logging from camacq.event import match_event +from camacq.plugins.leica.command import cam_com, del_com from camacq.plugins.sample.helper import next_well_xy from camacq.util import read_csv @@ -23,6 +24,7 @@ async def setup_module(center, config): start_exp(center) add_next_well(center) + image_next_well(center) stop_exp(center) @@ -41,17 +43,17 @@ async def load_sample(center, state_file): def start_exp(center): """Trigger on start experiment.""" - async def start(center, event): + async def set_start_well(center, event): """Run on start event.""" await center.actions.sample.set_well(plate_name="00", well_x=0, well_y=0) - center.bus.register("camacq_start_event", start) + center.bus.register("camacq_start_event", set_start_well) def add_next_well(center): """Add next well.""" - async def well_event(center, event): + async def set_next_well(center, event): """Run on well event.""" if not match_event(event, field_x=1, field_y=2, well_img_ok=True): return @@ -64,13 +66,37 @@ async def well_event(center, event): plate_name=plate_name, well_x=well_x, well_y=well_y ) - center.bus.register("well_event", well_event) + center.bus.register("well_event", set_next_well) + + +def image_next_well(center): + """Image next well.""" + + async def send_cam_job(center, event): + """Run on well event.""" + if event.well.images: + return + + await center.actions.command.send(command=del_com()) + # TODO: Make exp job configurable. + command = cam_com("p10xgain", event.well.x, event.well.y, 0, 1, 0, 0) + await center.actions.command.send(command=command) + command = cam_com("p10xgain", event.well.x, event.well.y, 1, 1, 0, 0) + await center.actions.command.send(command=command) + + # TODO: Unregister rename image and set img ok. + + await center.actions.command.start_imaging() + await asyncio.sleep(2.0) + await center.actions.command.send(command="/cmd:startcamscan") + + center.bus.register("well_event", send_cam_job) def stop_exp(center): """Trigger to stop experiment.""" - async def stop(center, event): + async def stop_imaging(center, event): """Run to stop the experiment.""" next_well_x, _ = next_well_xy("00", 2, 2) @@ -80,7 +106,8 @@ async def stop(center, event): ): return - await center.actions.automations.delay(seconds=2.0) + # Sleep to let images be completely scanned before stopping. + await asyncio.sleep(2.0) await center.actions.api.stop_imaging() - center.bus.register("well_event", stop) + center.bus.register("well_event", stop_imaging) From d8342591ddefd50917217082138c9b86ce2ca568 Mon Sep 17 00:00:00 2001 From: Martin Hjelmare Date: Tue, 12 Nov 2019 21:13:59 +0100 Subject: [PATCH 10/76] Add todo comments --- camacqplugins/production/__init__.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/camacqplugins/production/__init__.py b/camacqplugins/production/__init__.py index 81d401f..b0925f1 100644 --- a/camacqplugins/production/__init__.py +++ b/camacqplugins/production/__init__.py @@ -55,10 +55,11 @@ def add_next_well(center): async def set_next_well(center, event): """Run on well event.""" + # TODO: Make stop field coordinates configurable. if not match_event(event, field_x=1, field_y=2, well_img_ok=True): return - # TODO: Make plate and well coordinates configurable. + # TODO: Make well coordinates configurable. plate_name = "00" well_x, well_y = next_well_xy(center.sample, plate_name, x_wells=12, y_wells=8) @@ -78,7 +79,7 @@ async def send_cam_job(center, event): return await center.actions.command.send(command=del_com()) - # TODO: Make exp job configurable. + # TODO: Make exp job and field coordinates configurable. command = cam_com("p10xgain", event.well.x, event.well.y, 0, 1, 0, 0) await center.actions.command.send(command=command) command = cam_com("p10xgain", event.well.x, event.well.y, 1, 1, 0, 0) @@ -98,7 +99,8 @@ def stop_exp(center): async def stop_imaging(center, event): """Run to stop the experiment.""" - next_well_x, _ = next_well_xy("00", 2, 2) + # TODO: Make well layout and stop field coordinates configurable. + next_well_x, _ = next_well_xy("00", 12, 8) if ( not match_event(event, field_x=1, field_y=2, well_img_ok=True) From 0f05cea020a3a02d6e74070b11f48a6a3812b4d5 Mon Sep 17 00:00:00 2001 From: Martin Hjelmare Date: Tue, 12 Nov 2019 21:23:24 +0100 Subject: [PATCH 11/76] Finish add next well --- camacqplugins/production/__init__.py | 22 +++++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/camacqplugins/production/__init__.py b/camacqplugins/production/__init__.py index b0925f1..47aefbb 100644 --- a/camacqplugins/production/__init__.py +++ b/camacqplugins/production/__init__.py @@ -55,13 +55,25 @@ def add_next_well(center): async def set_next_well(center, event): """Run on well event.""" - # TODO: Make stop field coordinates configurable. - if not match_event(event, field_x=1, field_y=2, well_img_ok=True): + # TODO: Make well layout and stop field coordinates configurable. + plate_name = "00" + x_wells = 12 + y_wells = 8 + next_well_x, _ = next_well_xy(plate_name, x_wells, y_wells) + + if ( + not match_event(event, field_x=1, field_y=2, well_img_ok=True) + or next_well_x is None + ): return - # TODO: Make well coordinates configurable. - plate_name = "00" - well_x, well_y = next_well_xy(center.sample, plate_name, x_wells=12, y_wells=8) + await asyncio.sleep(2.0) + await center.actions.command.stop_imaging() + await asyncio.sleep(4.0) + + well_x, well_y = next_well_xy( + center.sample, plate_name, x_wells=x_wells, y_wells=y_wells + ) await center.actions.sample.set_well( plate_name=plate_name, well_x=well_x, well_y=well_y From 65ae1481feddf91e4b6ee1153e22f04a2733c883 Mon Sep 17 00:00:00 2001 From: Martin Hjelmare Date: Tue, 12 Nov 2019 21:25:43 +0100 Subject: [PATCH 12/76] Extract delay time to constant --- camacqplugins/production/__init__.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/camacqplugins/production/__init__.py b/camacqplugins/production/__init__.py index 47aefbb..84627d5 100644 --- a/camacqplugins/production/__init__.py +++ b/camacqplugins/production/__init__.py @@ -10,6 +10,7 @@ _LOGGER = logging.getLogger(__name__) SAMPLE_STATE_FILE = "state_file" +START_STOP_DELAY = 2.0 async def setup_module(center, config): @@ -67,9 +68,9 @@ async def set_next_well(center, event): ): return - await asyncio.sleep(2.0) + await asyncio.sleep(START_STOP_DELAY) await center.actions.command.stop_imaging() - await asyncio.sleep(4.0) + await asyncio.sleep(2 * START_STOP_DELAY) well_x, well_y = next_well_xy( center.sample, plate_name, x_wells=x_wells, y_wells=y_wells @@ -100,7 +101,7 @@ async def send_cam_job(center, event): # TODO: Unregister rename image and set img ok. await center.actions.command.start_imaging() - await asyncio.sleep(2.0) + await asyncio.sleep(START_STOP_DELAY) await center.actions.command.send(command="/cmd:startcamscan") center.bus.register("well_event", send_cam_job) @@ -121,7 +122,7 @@ async def stop_imaging(center, event): return # Sleep to let images be completely scanned before stopping. - await asyncio.sleep(2.0) + await asyncio.sleep(START_STOP_DELAY) await center.actions.api.stop_imaging() center.bus.register("well_event", stop_imaging) From 8062bf951d135136d3d71a506dfb50c8107fcbec Mon Sep 17 00:00:00 2001 From: Martin Hjelmare Date: Tue, 12 Nov 2019 21:50:59 +0100 Subject: [PATCH 13/76] Add calc gain --- camacqplugins/production/__init__.py | 44 ++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/camacqplugins/production/__init__.py b/camacqplugins/production/__init__.py index 84627d5..4e7ebd7 100644 --- a/camacqplugins/production/__init__.py +++ b/camacqplugins/production/__init__.py @@ -1,6 +1,8 @@ """Provide a plugin for production standard flow.""" import asyncio import logging +import tempfile +from pathlib import Path from camacq.event import match_event from camacq.plugins.leica.command import cam_com, del_com @@ -26,6 +28,7 @@ async def setup_module(center, config): add_next_well(center) image_next_well(center) + analyze_gain(center) stop_exp(center) @@ -107,6 +110,47 @@ async def send_cam_job(center, event): center.bus.register("well_event", send_cam_job) +def analyze_gain(center): + """Analyze gain.""" + + async def calc_gain(center, event): + """Calculate correct gain.""" + # TODO: Make event field coordinates, job id and save_path configurable. + field_x = 1 + field_y = 1 + job_id = 3 + channel_id = 31 + if not match_event( + event, + field_x=field_x, + field_y=field_y, + job_id=job_id, + channel_id=channel_id, + ): + return + + await asyncio.sleep(START_STOP_DELAY) + await center.actions.command.stop_imaging() + await asyncio.sleep(START_STOP_DELAY) + + # This should be a path to a base file name, not to an actual dir or file. + save_path = ( + Path(tempfile.gettempdir()) + / event.plate_name + / f"{event.well_x}--{event.well_y}" + ) + + await center.actions.plugins.gain.calc_gain( + plate_name=event.plate_name, + well_x=event.well_x, + well_y=event.well_y, + make_plots=True, + save_path=save_path, + ) + + center.bus.register("image_event", calc_gain) + + def stop_exp(center): """Trigger to stop experiment.""" From 9ed3a18ca0a51b04fb5bebfc4230dff52bef2da7 Mon Sep 17 00:00:00 2001 From: Martin Hjelmare Date: Tue, 12 Nov 2019 21:58:09 +0100 Subject: [PATCH 14/76] Add fixme comment --- camacqplugins/production/__init__.py | 1 + 1 file changed, 1 insertion(+) diff --git a/camacqplugins/production/__init__.py b/camacqplugins/production/__init__.py index 4e7ebd7..5e52a04 100644 --- a/camacqplugins/production/__init__.py +++ b/camacqplugins/production/__init__.py @@ -140,6 +140,7 @@ async def calc_gain(center, event): / f"{event.well_x}--{event.well_y}" ) + # FIXME: Adjust the action type for plugins.gain to avoid period in the name. await center.actions.plugins.gain.calc_gain( plate_name=event.plate_name, well_x=event.well_x, From 43b51d8320814965fd1b0eec6184835b5784766c Mon Sep 17 00:00:00 2001 From: Martin Hjelmare Date: Tue, 12 Nov 2019 22:25:58 +0100 Subject: [PATCH 15/76] Add set_exp_gain --- camacqplugins/production/__init__.py | 47 +++++++++++++++++++++++++++- 1 file changed, 46 insertions(+), 1 deletion(-) diff --git a/camacqplugins/production/__init__.py b/camacqplugins/production/__init__.py index 5e52a04..2b13be4 100644 --- a/camacqplugins/production/__init__.py +++ b/camacqplugins/production/__init__.py @@ -5,7 +5,7 @@ from pathlib import Path from camacq.event import match_event -from camacq.plugins.leica.command import cam_com, del_com +from camacq.plugins.leica.command import cam_com, del_com, gain_com from camacq.plugins.sample.helper import next_well_xy from camacq.util import read_csv @@ -29,6 +29,7 @@ async def setup_module(center, config): add_next_well(center) image_next_well(center) analyze_gain(center) + set_exp_gain(center) stop_exp(center) @@ -152,6 +153,50 @@ async def calc_gain(center, event): center.bus.register("image_event", calc_gain) +def set_exp_gain(center): + """Set experiment gain.""" + + async def set_gain(center, event): + """Set pmt gain.""" + # TODO: Make exp job names configurable. + exp_job_1 = "exp_job_1" + exp_job_2 = "exp_job_2" + exp_job_3 = "exp_job_3" + + if event.channel_name == "green": + exp = exp_job_1 + num = 1 + gain = min(event.gain or 800, 800) + elif event.channel_name == "blue": + exp = exp_job_2 + num = 1 + gain = min(event.gain or 505, 610) + elif event.channel_name == "yellow": + exp = exp_job_2 + num = 2 + gain = min(event.gain or 655, 760) + elif event.channel_name == "red": + exp = exp_job_3 + num = 2 + gain = event.gain or 630 + gain = min(gain + 25, 735) + + command = gain_com(exp=exp, num=num, value=gain) + + # Set the gain at the microscope. + await center.actions.command.send(command=command) + # Set the gain in the sample state. + await center.actions.sample.set_channel( + plate_name=event.plate_name, + well_x=event.well_x, + well_y=event.well_y, + channel_name=event.channel_name, + gain=gain, + ) + + center.bus.register("gain_calc_event", set_gain) + + def stop_exp(center): """Trigger to stop experiment.""" From f07f801a0519a20a6a27f6ab4fa6f1879f2131d3 Mon Sep 17 00:00:00 2001 From: Martin Hjelmare Date: Wed, 13 Nov 2019 12:56:46 +0100 Subject: [PATCH 16/76] Add exp job --- camacqplugins/production/__init__.py | 30 ++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/camacqplugins/production/__init__.py b/camacqplugins/production/__init__.py index 2b13be4..794c081 100644 --- a/camacqplugins/production/__init__.py +++ b/camacqplugins/production/__init__.py @@ -30,6 +30,7 @@ async def setup_module(center, config): image_next_well(center) analyze_gain(center) set_exp_gain(center) + add_exp_job(center) stop_exp(center) @@ -197,6 +198,35 @@ async def set_gain(center, event): center.bus.register("gain_calc_event", set_gain) +def add_exp_job(center): + """Add experiment job.""" + + async def add_cam_job(center, event): + """Add an experiment job to the cam list.""" + # TODO: Make channels layout configurable. + if not match_event(event, channel_name="red") or len(event.well.channels) != 4: + return + + # TODO: Make well layout configurable. + commands = [] + for field_x in range(2): + for field_y in range(3): + cmd = cam_com( + "p10xexp", event.well_x, event.well_y, field_x, field_y, 0, 0 + ) + commands.append(cmd) + + await center.actions.command.send(command=del_com()) + await center.actions.command.send_many(commands=commands) + + # TODO: Turn on rename image and set_img_ok during experiment job phase. + + await center.actions.command.start_imaging() + await center.actions.command.send(command="/cmd:startcamscan") + + center.bus.register("channel_event", add_cam_job) + + def stop_exp(center): """Trigger to stop experiment.""" From 11a892668fac6ddd6aa3a29cb78a96b6be1a0f10 Mon Sep 17 00:00:00 2001 From: Martin Hjelmare Date: Wed, 13 Nov 2019 13:19:12 +0100 Subject: [PATCH 17/76] Add set img ok --- camacqplugins/production/__init__.py | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/camacqplugins/production/__init__.py b/camacqplugins/production/__init__.py index 794c081..b90e9e8 100644 --- a/camacqplugins/production/__init__.py +++ b/camacqplugins/production/__init__.py @@ -31,6 +31,7 @@ async def setup_module(center, config): analyze_gain(center) set_exp_gain(center) add_exp_job(center) + set_img_ok(center) stop_exp(center) @@ -227,6 +228,26 @@ async def add_cam_job(center, event): center.bus.register("channel_event", add_cam_job) +def set_img_ok(center): + """Set field as imaged ok.""" + + async def set_sample_img_ok(center, event): + """Set sample field img ok.""" + if not match_event(event, job_id=5): + return + + await center.actions.sample.set_field( + plate_name=event.plate_name, + well_x=event.well_x, + well_y=event.well_y, + field_x=event.field_x, + field_y=event.field_y, + img_ok=True, + ) + + return center.bus.register("image_event", set_sample_img_ok) + + def stop_exp(center): """Trigger to stop experiment.""" From 8598262bb3528f550a70be664f91133dfec4b871 Mon Sep 17 00:00:00 2001 From: Martin Hjelmare Date: Wed, 13 Nov 2019 15:09:14 +0100 Subject: [PATCH 18/76] Add rename image --- camacqplugins/production/__init__.py | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/camacqplugins/production/__init__.py b/camacqplugins/production/__init__.py index b90e9e8..1c9ffcf 100644 --- a/camacqplugins/production/__init__.py +++ b/camacqplugins/production/__init__.py @@ -32,6 +32,7 @@ async def setup_module(center, config): set_exp_gain(center) add_exp_job(center) set_img_ok(center) + rename_exp_image(center) stop_exp(center) @@ -248,6 +249,33 @@ async def set_sample_img_ok(center, event): return center.bus.register("image_event", set_sample_img_ok) +def rename_exp_image(center): + """Rename an experiment image.""" + + async def rename_image(center, event): + """Rename an image.""" + if event.job_id not in (3, 4, 6): + return + + if event.job_id == 3: + channel_id = event.channel_id + elif event.job_id == 4 and event.channel_id == 0: + channel_id = 1 + elif event.job_id == 4 and event.channel_id == 1: + channel_id = 2 + elif event.job_id == 6: + channel_id = 3 + + new_name = ( + f"U{event.well_x:03}--V{event.well_y}--E{event.job_id}--X{event.field_x}" + f"--Y{event.field_y}--Z{event.z_slice}--C{channel_id}.ome.tif" + ) + + center.actions.rename(old_path=event.path, new_name=new_name) + + center.bus.register("image_event", rename_image) + + def stop_exp(center): """Trigger to stop experiment.""" From 23c20ec538e290cae780823fe745657f5c816a99 Mon Sep 17 00:00:00 2001 From: Martin Hjelmare Date: Thu, 14 Nov 2019 11:05:51 +0100 Subject: [PATCH 19/76] Add flake8 ignore --- setup.cfg | 1 + 1 file changed, 1 insertion(+) diff --git a/setup.cfg b/setup.cfg index 9d95385..b3d11de 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,4 +1,5 @@ [flake8] +ignore = E231, W503 max-line-length = 88 [pydocstyle] From 3002399ce7d24f987518bf7cca675273d1523665 Mon Sep 17 00:00:00 2001 From: Martin Hjelmare Date: Fri, 15 Nov 2019 15:00:06 +0100 Subject: [PATCH 20/76] Fix next well xy --- camacqplugins/production/__init__.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/camacqplugins/production/__init__.py b/camacqplugins/production/__init__.py index 1c9ffcf..8f1b3af 100644 --- a/camacqplugins/production/__init__.py +++ b/camacqplugins/production/__init__.py @@ -26,6 +26,8 @@ async def setup_module(center, config): else: start_exp(center) + # TODO: Only add wells if not loading sample state from file. + add_next_well(center) image_next_well(center) analyze_gain(center) @@ -67,7 +69,7 @@ async def set_next_well(center, event): plate_name = "00" x_wells = 12 y_wells = 8 - next_well_x, _ = next_well_xy(plate_name, x_wells, y_wells) + next_well_x, _ = next_well_xy(center.sample, plate_name, x_wells, y_wells) if ( not match_event(event, field_x=1, field_y=2, well_img_ok=True) @@ -79,9 +81,7 @@ async def set_next_well(center, event): await center.actions.command.stop_imaging() await asyncio.sleep(2 * START_STOP_DELAY) - well_x, well_y = next_well_xy( - center.sample, plate_name, x_wells=x_wells, y_wells=y_wells - ) + well_x, well_y = next_well_xy(center.sample, plate_name, x_wells, y_wells) await center.actions.sample.set_well( plate_name=plate_name, well_x=well_x, well_y=well_y @@ -282,7 +282,7 @@ def stop_exp(center): async def stop_imaging(center, event): """Run to stop the experiment.""" # TODO: Make well layout and stop field coordinates configurable. - next_well_x, _ = next_well_xy("00", 12, 8) + next_well_x, _ = next_well_xy(center.sample, "00", 12, 8) if ( not match_event(event, field_x=1, field_y=2, well_img_ok=True) From fa6f347c5c8d43a5046e6ce575df9b6d5251b6ba Mon Sep 17 00:00:00 2001 From: Martin Hjelmare Date: Fri, 15 Nov 2019 15:50:40 +0100 Subject: [PATCH 21/76] Image next well based on sample --- camacqplugins/production/__init__.py | 52 ++++++++++++++++++++++------ 1 file changed, 41 insertions(+), 11 deletions(-) diff --git a/camacqplugins/production/__init__.py b/camacqplugins/production/__init__.py index 8f1b3af..1ab8697 100644 --- a/camacqplugins/production/__init__.py +++ b/camacqplugins/production/__init__.py @@ -23,13 +23,12 @@ async def setup_module(center, config): state_file = conf.get(SAMPLE_STATE_FILE) if conf else None if state_file is not None: await load_sample(center, state_file) + image_next_well_on_sample(center) else: start_exp(center) + add_next_well(center) + image_next_well_on_event(center) - # TODO: Only add wells if not loading sample state from file. - - add_next_well(center) - image_next_well(center) analyze_gain(center) set_exp_gain(center) add_exp_job(center) @@ -41,13 +40,11 @@ async def setup_module(center, config): async def load_sample(center, state_file): """Load sample state from file.""" state_data = await center.add_executor_job(read_csv, state_file) - tasks = [] for data in state_data: - for action in center.actions.sample.values(): - tasks.append(center.create_task(action(silent=True, **data))) - - if tasks: - await asyncio.wait(tasks) + await center.actions.sample.set_plate(silent=True, **data) + await center.actions.sample.set_well(silent=True, **data) + await center.actions.sample.set_channel(silent=True, **data) + await center.actions.sample.set_field(silent=True, **data) def start_exp(center): @@ -90,7 +87,40 @@ async def set_next_well(center, event): center.bus.register("well_event", set_next_well) -def image_next_well(center): +def image_next_well_on_sample(center): + """Image next well in existing sample.""" + + async def send_cam_job(center, event): + """Run on well event.""" + # TODO: Make stop field coordinates configurable. + plate_name = "00" + next_well_x, next_well_y = next_well_xy(center.sample, plate_name) + + if ( + not match_event(event, event_type="camacq_start_event") + and not match_event(event, field_x=1, field_y=2, well_img_ok=True) + or next_well_x is None + ): + return + + await center.actions.command.send(command=del_com()) + # TODO: Make exp job and field coordinates configurable. + command = cam_com("p10xgain", next_well_x, next_well_y, 0, 1, 0, 0) + await center.actions.command.send(command=command) + command = cam_com("p10xgain", next_well_x, next_well_y, 1, 1, 0, 0) + await center.actions.command.send(command=command) + + # TODO: Unregister rename image and set img ok. + + await center.actions.command.start_imaging() + await asyncio.sleep(START_STOP_DELAY) + await center.actions.command.send(command="/cmd:startcamscan") + + center.bus.register("camacq_start_event", send_cam_job) + center.bus.register("well_event", send_cam_job) + + +def image_next_well_on_event(center): """Image next well.""" async def send_cam_job(center, event): From 4e1b61b49f3bfc171e547b30ea536e45da1f21be Mon Sep 17 00:00:00 2001 From: Martin Hjelmare Date: Fri, 15 Nov 2019 16:01:37 +0100 Subject: [PATCH 22/76] Make job names configurable --- camacqplugins/production/__init__.py | 51 ++++++++++++++-------------- 1 file changed, 25 insertions(+), 26 deletions(-) diff --git a/camacqplugins/production/__init__.py b/camacqplugins/production/__init__.py index 1ab8697..a84de24 100644 --- a/camacqplugins/production/__init__.py +++ b/camacqplugins/production/__init__.py @@ -17,21 +17,25 @@ async def setup_module(center, config): """Set up production plugin.""" - print("Production plugin setup!") - conf = config["production"] + gain_job = conf["gain_job_name"] + green_job = conf["green_job_name"] + blue_yellow_job = conf["blue_yellow_job_name"] + red_job = conf["red_job_name"] + exp_pattern = conf["exp_pattern_name"] + state_file = conf.get(SAMPLE_STATE_FILE) if conf else None if state_file is not None: await load_sample(center, state_file) - image_next_well_on_sample(center) + image_next_well_on_sample(center, gain_job) else: start_exp(center) add_next_well(center) - image_next_well_on_event(center) + image_next_well_on_event(center, gain_job) analyze_gain(center) - set_exp_gain(center) - add_exp_job(center) + set_exp_gain(center, green_job, blue_yellow_job, red_job) + add_exp_job(center, exp_pattern) set_img_ok(center) rename_exp_image(center) stop_exp(center) @@ -87,7 +91,7 @@ async def set_next_well(center, event): center.bus.register("well_event", set_next_well) -def image_next_well_on_sample(center): +def image_next_well_on_sample(center, gain_job): """Image next well in existing sample.""" async def send_cam_job(center, event): @@ -104,10 +108,10 @@ async def send_cam_job(center, event): return await center.actions.command.send(command=del_com()) - # TODO: Make exp job and field coordinates configurable. - command = cam_com("p10xgain", next_well_x, next_well_y, 0, 1, 0, 0) + # TODO: Make field coordinates configurable. + command = cam_com(gain_job, next_well_x, next_well_y, 0, 1, 0, 0) await center.actions.command.send(command=command) - command = cam_com("p10xgain", next_well_x, next_well_y, 1, 1, 0, 0) + command = cam_com(gain_job, next_well_x, next_well_y, 1, 1, 0, 0) await center.actions.command.send(command=command) # TODO: Unregister rename image and set img ok. @@ -120,7 +124,7 @@ async def send_cam_job(center, event): center.bus.register("well_event", send_cam_job) -def image_next_well_on_event(center): +def image_next_well_on_event(center, gain_job): """Image next well.""" async def send_cam_job(center, event): @@ -129,10 +133,10 @@ async def send_cam_job(center, event): return await center.actions.command.send(command=del_com()) - # TODO: Make exp job and field coordinates configurable. - command = cam_com("p10xgain", event.well.x, event.well.y, 0, 1, 0, 0) + # TODO: Make field coordinates configurable. + command = cam_com(gain_job, event.well.x, event.well.y, 0, 1, 0, 0) await center.actions.command.send(command=command) - command = cam_com("p10xgain", event.well.x, event.well.y, 1, 1, 0, 0) + command = cam_com(gain_job, event.well.x, event.well.y, 1, 1, 0, 0) await center.actions.command.send(command=command) # TODO: Unregister rename image and set img ok. @@ -186,30 +190,25 @@ async def calc_gain(center, event): center.bus.register("image_event", calc_gain) -def set_exp_gain(center): +def set_exp_gain(center, green_job, blue_yellow_job, red_job): """Set experiment gain.""" async def set_gain(center, event): """Set pmt gain.""" - # TODO: Make exp job names configurable. - exp_job_1 = "exp_job_1" - exp_job_2 = "exp_job_2" - exp_job_3 = "exp_job_3" - if event.channel_name == "green": - exp = exp_job_1 + exp = green_job num = 1 gain = min(event.gain or 800, 800) elif event.channel_name == "blue": - exp = exp_job_2 + exp = blue_yellow_job num = 1 gain = min(event.gain or 505, 610) elif event.channel_name == "yellow": - exp = exp_job_2 + exp = blue_yellow_job num = 2 gain = min(event.gain or 655, 760) elif event.channel_name == "red": - exp = exp_job_3 + exp = red_job num = 2 gain = event.gain or 630 gain = min(gain + 25, 735) @@ -230,7 +229,7 @@ async def set_gain(center, event): center.bus.register("gain_calc_event", set_gain) -def add_exp_job(center): +def add_exp_job(center, exp_pattern): """Add experiment job.""" async def add_cam_job(center, event): @@ -244,7 +243,7 @@ async def add_cam_job(center, event): for field_x in range(2): for field_y in range(3): cmd = cam_com( - "p10xexp", event.well_x, event.well_y, field_x, field_y, 0, 0 + exp_pattern, event.well_x, event.well_y, field_x, field_y, 0, 0 ) commands.append(cmd) From 9090d0257ed77d45e1fbc8e73cb08c86790095e0 Mon Sep 17 00:00:00 2001 From: Martin Hjelmare Date: Fri, 15 Nov 2019 16:24:23 +0100 Subject: [PATCH 23/76] Register and unregister img ok and rename img --- camacqplugins/production/__init__.py | 40 +++++++++++++++++++--------- 1 file changed, 27 insertions(+), 13 deletions(-) diff --git a/camacqplugins/production/__init__.py b/camacqplugins/production/__init__.py index a84de24..880d8ab 100644 --- a/camacqplugins/production/__init__.py +++ b/camacqplugins/production/__init__.py @@ -7,7 +7,7 @@ from camacq.event import match_event from camacq.plugins.leica.command import cam_com, del_com, gain_com from camacq.plugins.sample.helper import next_well_xy -from camacq.util import read_csv +from camacq.util import dotdict, read_csv _LOGGER = logging.getLogger(__name__) @@ -23,21 +23,22 @@ async def setup_module(center, config): blue_yellow_job = conf["blue_yellow_job_name"] red_job = conf["red_job_name"] exp_pattern = conf["exp_pattern_name"] + subscriptions = dotdict() state_file = conf.get(SAMPLE_STATE_FILE) if conf else None if state_file is not None: await load_sample(center, state_file) - image_next_well_on_sample(center, gain_job) + image_next_well_on_sample(center, gain_job, subscriptions) else: start_exp(center) add_next_well(center) - image_next_well_on_event(center, gain_job) + image_next_well_on_event(center, gain_job, subscriptions) analyze_gain(center) set_exp_gain(center, green_job, blue_yellow_job, red_job) - add_exp_job(center, exp_pattern) - set_img_ok(center) - rename_exp_image(center) + add_exp_job(center, exp_pattern, subscriptions) + subscriptions.set_img_ok = set_img_ok(center) + subscriptions.rename_exp_image = rename_exp_image(center) stop_exp(center) @@ -91,7 +92,7 @@ async def set_next_well(center, event): center.bus.register("well_event", set_next_well) -def image_next_well_on_sample(center, gain_job): +def image_next_well_on_sample(center, gain_job, subscriptions): """Image next well in existing sample.""" async def send_cam_job(center, event): @@ -114,7 +115,12 @@ async def send_cam_job(center, event): command = cam_com(gain_job, next_well_x, next_well_y, 1, 1, 0, 0) await center.actions.command.send(command=command) - # TODO: Unregister rename image and set img ok. + if subscriptions.set_img_ok is not None: + subscriptions.set_img_ok() + subscriptions.set_img_ok = None + if subscriptions.rename_exp_image is not None: + subscriptions.rename_exp_image() + subscriptions.rename_exp_image = None await center.actions.command.start_imaging() await asyncio.sleep(START_STOP_DELAY) @@ -124,7 +130,7 @@ async def send_cam_job(center, event): center.bus.register("well_event", send_cam_job) -def image_next_well_on_event(center, gain_job): +def image_next_well_on_event(center, gain_job, subscriptions): """Image next well.""" async def send_cam_job(center, event): @@ -139,7 +145,12 @@ async def send_cam_job(center, event): command = cam_com(gain_job, event.well.x, event.well.y, 1, 1, 0, 0) await center.actions.command.send(command=command) - # TODO: Unregister rename image and set img ok. + if subscriptions.set_img_ok is not None: + subscriptions.set_img_ok() + subscriptions.set_img_ok = None + if subscriptions.rename_exp_image is not None: + subscriptions.rename_exp_image() + subscriptions.rename_exp_image = None await center.actions.command.start_imaging() await asyncio.sleep(START_STOP_DELAY) @@ -229,7 +240,7 @@ async def set_gain(center, event): center.bus.register("gain_calc_event", set_gain) -def add_exp_job(center, exp_pattern): +def add_exp_job(center, exp_pattern, subscriptions): """Add experiment job.""" async def add_cam_job(center, event): @@ -250,7 +261,10 @@ async def add_cam_job(center, event): await center.actions.command.send(command=del_com()) await center.actions.command.send_many(commands=commands) - # TODO: Turn on rename image and set_img_ok during experiment job phase. + if subscriptions.set_img_ok is not None: + subscriptions.set_img_ok = set_img_ok(center) + if subscriptions.rename_exp_image is None: + subscriptions.rename_exp_image = rename_exp_image(center) await center.actions.command.start_imaging() await center.actions.command.send(command="/cmd:startcamscan") @@ -302,7 +316,7 @@ async def rename_image(center, event): center.actions.rename(old_path=event.path, new_name=new_name) - center.bus.register("image_event", rename_image) + return center.bus.register("image_event", rename_image) def stop_exp(center): From 66a2c5165251622cd5047c81acf9b00798416dd4 Mon Sep 17 00:00:00 2001 From: Martin Hjelmare Date: Fri, 15 Nov 2019 16:51:52 +0100 Subject: [PATCH 24/76] Fix action calls after renaming action types --- camacqplugins/production/__init__.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/camacqplugins/production/__init__.py b/camacqplugins/production/__init__.py index 880d8ab..8b2b424 100644 --- a/camacqplugins/production/__init__.py +++ b/camacqplugins/production/__init__.py @@ -189,8 +189,7 @@ async def calc_gain(center, event): / f"{event.well_x}--{event.well_y}" ) - # FIXME: Adjust the action type for plugins.gain to avoid period in the name. - await center.actions.plugins.gain.calc_gain( + await center.actions.gain.calc_gain( plate_name=event.plate_name, well_x=event.well_x, well_y=event.well_y, @@ -314,7 +313,7 @@ async def rename_image(center, event): f"--Y{event.field_y}--Z{event.z_slice}--C{channel_id}.ome.tif" ) - center.actions.rename(old_path=event.path, new_name=new_name) + center.actions.rename_image.rename_image(old_path=event.path, new_name=new_name) return center.bus.register("image_event", rename_image) From 223f1dda4daa52230727b0785b95bb17b99581c5 Mon Sep 17 00:00:00 2001 From: Martin Hjelmare Date: Fri, 15 Nov 2019 18:53:53 +0100 Subject: [PATCH 25/76] Fix plot save path --- camacqplugins/production/__init__.py | 44 ++++++++++++++++++---------- 1 file changed, 28 insertions(+), 16 deletions(-) diff --git a/camacqplugins/production/__init__.py b/camacqplugins/production/__init__.py index 8b2b424..73f636f 100644 --- a/camacqplugins/production/__init__.py +++ b/camacqplugins/production/__init__.py @@ -4,6 +4,8 @@ import tempfile from pathlib import Path +import voluptuous as vol + from camacq.event import match_event from camacq.plugins.leica.command import cam_com, del_com, gain_com from camacq.plugins.sample.helper import next_well_xy @@ -14,27 +16,33 @@ SAMPLE_STATE_FILE = "state_file" START_STOP_DELAY = 2.0 +CONFIG_SCHEMA = vol.Schema({"plot_save_path": vol.IsDir()}, extra=vol.ALLOW_EXTRA) + async def setup_module(center, config): """Set up production plugin.""" conf = config["production"] - gain_job = conf["gain_job_name"] + + conf = CONFIG_SCHEMA(conf) + + gain_pattern = conf["gain_pattern_name"] green_job = conf["green_job_name"] blue_yellow_job = conf["blue_yellow_job_name"] red_job = conf["red_job_name"] exp_pattern = conf["exp_pattern_name"] + plot_save_path = conf.get("plot_save_path") subscriptions = dotdict() state_file = conf.get(SAMPLE_STATE_FILE) if conf else None if state_file is not None: await load_sample(center, state_file) - image_next_well_on_sample(center, gain_job, subscriptions) + image_next_well_on_sample(center, gain_pattern, subscriptions) else: start_exp(center) add_next_well(center) - image_next_well_on_event(center, gain_job, subscriptions) + image_next_well_on_event(center, gain_pattern, subscriptions) - analyze_gain(center) + analyze_gain(center, plot_save_path) set_exp_gain(center, green_job, blue_yellow_job, red_job) add_exp_job(center, exp_pattern, subscriptions) subscriptions.set_img_ok = set_img_ok(center) @@ -92,7 +100,7 @@ async def set_next_well(center, event): center.bus.register("well_event", set_next_well) -def image_next_well_on_sample(center, gain_job, subscriptions): +def image_next_well_on_sample(center, gain_pattern, subscriptions): """Image next well in existing sample.""" async def send_cam_job(center, event): @@ -110,9 +118,9 @@ async def send_cam_job(center, event): await center.actions.command.send(command=del_com()) # TODO: Make field coordinates configurable. - command = cam_com(gain_job, next_well_x, next_well_y, 0, 1, 0, 0) + command = cam_com(gain_pattern, next_well_x, next_well_y, 0, 1, 0, 0) await center.actions.command.send(command=command) - command = cam_com(gain_job, next_well_x, next_well_y, 1, 1, 0, 0) + command = cam_com(gain_pattern, next_well_x, next_well_y, 1, 1, 0, 0) await center.actions.command.send(command=command) if subscriptions.set_img_ok is not None: @@ -130,7 +138,7 @@ async def send_cam_job(center, event): center.bus.register("well_event", send_cam_job) -def image_next_well_on_event(center, gain_job, subscriptions): +def image_next_well_on_event(center, gain_pattern, subscriptions): """Image next well.""" async def send_cam_job(center, event): @@ -140,9 +148,9 @@ async def send_cam_job(center, event): await center.actions.command.send(command=del_com()) # TODO: Make field coordinates configurable. - command = cam_com(gain_job, event.well.x, event.well.y, 0, 1, 0, 0) + command = cam_com(gain_pattern, event.well.x, event.well.y, 0, 1, 0, 0) await center.actions.command.send(command=command) - command = cam_com(gain_job, event.well.x, event.well.y, 1, 1, 0, 0) + command = cam_com(gain_pattern, event.well.x, event.well.y, 1, 1, 0, 0) await center.actions.command.send(command=command) if subscriptions.set_img_ok is not None: @@ -159,7 +167,7 @@ async def send_cam_job(center, event): center.bus.register("well_event", send_cam_job) -def analyze_gain(center): +def analyze_gain(center, save_path): """Analyze gain.""" async def calc_gain(center, event): @@ -182,12 +190,16 @@ async def calc_gain(center, event): await center.actions.command.stop_imaging() await asyncio.sleep(START_STOP_DELAY) + nonlocal save_path + if save_path is None: + save_path = Path(tempfile.gettempdir()) / event.plate_name + else: + save_path = Path(save_path) + if not save_path.exists(): + await center.add_executor_job(save_path.mkdir) + # This should be a path to a base file name, not to an actual dir or file. - save_path = ( - Path(tempfile.gettempdir()) - / event.plate_name - / f"{event.well_x}--{event.well_y}" - ) + save_path = save_path / f"{event.well_x}--{event.well_y}" await center.actions.gain.calc_gain( plate_name=event.plate_name, From 6397483fe4ed5ae2b03b590e3bfc5708afa8f3e2 Mon Sep 17 00:00:00 2001 From: Martin Hjelmare Date: Fri, 15 Nov 2019 20:31:42 +0100 Subject: [PATCH 26/76] Make stop work on state file --- camacqplugins/production/__init__.py | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/camacqplugins/production/__init__.py b/camacqplugins/production/__init__.py index 73f636f..715b4c1 100644 --- a/camacqplugins/production/__init__.py +++ b/camacqplugins/production/__init__.py @@ -16,6 +16,7 @@ SAMPLE_STATE_FILE = "state_file" START_STOP_DELAY = 2.0 +# pylint: disable=no-value-for-parameter CONFIG_SCHEMA = vol.Schema({"plot_save_path": vol.IsDir()}, extra=vol.ALLOW_EXTRA) @@ -35,11 +36,15 @@ async def setup_module(center, config): state_file = conf.get(SAMPLE_STATE_FILE) if conf else None if state_file is not None: + x_wells = None + y_wells = None await load_sample(center, state_file) image_next_well_on_sample(center, gain_pattern, subscriptions) else: + x_wells = 12 + y_wells = 8 start_exp(center) - add_next_well(center) + add_next_well(center, x_wells, y_wells) image_next_well_on_event(center, gain_pattern, subscriptions) analyze_gain(center, plot_save_path) @@ -47,7 +52,7 @@ async def setup_module(center, config): add_exp_job(center, exp_pattern, subscriptions) subscriptions.set_img_ok = set_img_ok(center) subscriptions.rename_exp_image = rename_exp_image(center) - stop_exp(center) + stop_exp(center, x_wells, y_wells) async def load_sample(center, state_file): @@ -70,15 +75,13 @@ async def set_start_well(center, event): center.bus.register("camacq_start_event", set_start_well) -def add_next_well(center): +def add_next_well(center, x_wells, y_wells): """Add next well.""" async def set_next_well(center, event): """Run on well event.""" # TODO: Make well layout and stop field coordinates configurable. plate_name = "00" - x_wells = 12 - y_wells = 8 next_well_x, _ = next_well_xy(center.sample, plate_name, x_wells, y_wells) if ( @@ -325,18 +328,20 @@ async def rename_image(center, event): f"--Y{event.field_y}--Z{event.z_slice}--C{channel_id}.ome.tif" ) - center.actions.rename_image.rename_image(old_path=event.path, new_name=new_name) + await center.actions.rename_image.rename_image( + old_path=event.path, new_name=new_name + ) return center.bus.register("image_event", rename_image) -def stop_exp(center): +def stop_exp(center, x_wells, y_wells): """Trigger to stop experiment.""" async def stop_imaging(center, event): """Run to stop the experiment.""" # TODO: Make well layout and stop field coordinates configurable. - next_well_x, _ = next_well_xy(center.sample, "00", 12, 8) + next_well_x, _ = next_well_xy(center.sample, "00", x_wells, y_wells) if ( not match_event(event, field_x=1, field_y=2, well_img_ok=True) From 93e1a926c34842a37fc7cddad9d79f61ba30f042 Mon Sep 17 00:00:00 2001 From: Martin Hjelmare Date: Fri, 15 Nov 2019 20:45:09 +0100 Subject: [PATCH 27/76] Add fixme comment --- camacqplugins/production/__init__.py | 1 + 1 file changed, 1 insertion(+) diff --git a/camacqplugins/production/__init__.py b/camacqplugins/production/__init__.py index 715b4c1..629624e 100644 --- a/camacqplugins/production/__init__.py +++ b/camacqplugins/production/__init__.py @@ -52,6 +52,7 @@ async def setup_module(center, config): add_exp_job(center, exp_pattern, subscriptions) subscriptions.set_img_ok = set_img_ok(center) subscriptions.rename_exp_image = rename_exp_image(center) + # FIXME: Make stop exp work for state file. stop_exp(center, x_wells, y_wells) From d78115e5bc33136be1d310e2a5e5993d80094bfe Mon Sep 17 00:00:00 2001 From: Martin Hjelmare Date: Sat, 16 Nov 2019 15:03:13 +0100 Subject: [PATCH 28/76] Fix set img ok --- camacqplugins/production/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/camacqplugins/production/__init__.py b/camacqplugins/production/__init__.py index 629624e..a45cafa 100644 --- a/camacqplugins/production/__init__.py +++ b/camacqplugins/production/__init__.py @@ -292,7 +292,7 @@ def set_img_ok(center): async def set_sample_img_ok(center, event): """Set sample field img ok.""" - if not match_event(event, job_id=5): + if not match_event(event, job_id=6): return await center.actions.sample.set_field( From d21b13ff2dcec8a5d603c260f5d13775133eec92 Mon Sep 17 00:00:00 2001 From: Martin Hjelmare Date: Sat, 16 Nov 2019 15:45:29 +0100 Subject: [PATCH 29/76] Remove start and stop delay --- camacqplugins/production/__init__.py | 9 --------- 1 file changed, 9 deletions(-) diff --git a/camacqplugins/production/__init__.py b/camacqplugins/production/__init__.py index a45cafa..9a0112d 100644 --- a/camacqplugins/production/__init__.py +++ b/camacqplugins/production/__init__.py @@ -1,5 +1,4 @@ """Provide a plugin for production standard flow.""" -import asyncio import logging import tempfile from pathlib import Path @@ -14,7 +13,6 @@ _LOGGER = logging.getLogger(__name__) SAMPLE_STATE_FILE = "state_file" -START_STOP_DELAY = 2.0 # pylint: disable=no-value-for-parameter CONFIG_SCHEMA = vol.Schema({"plot_save_path": vol.IsDir()}, extra=vol.ALLOW_EXTRA) @@ -91,9 +89,7 @@ async def set_next_well(center, event): ): return - await asyncio.sleep(START_STOP_DELAY) await center.actions.command.stop_imaging() - await asyncio.sleep(2 * START_STOP_DELAY) well_x, well_y = next_well_xy(center.sample, plate_name, x_wells, y_wells) @@ -135,7 +131,6 @@ async def send_cam_job(center, event): subscriptions.rename_exp_image = None await center.actions.command.start_imaging() - await asyncio.sleep(START_STOP_DELAY) await center.actions.command.send(command="/cmd:startcamscan") center.bus.register("camacq_start_event", send_cam_job) @@ -165,7 +160,6 @@ async def send_cam_job(center, event): subscriptions.rename_exp_image = None await center.actions.command.start_imaging() - await asyncio.sleep(START_STOP_DELAY) await center.actions.command.send(command="/cmd:startcamscan") center.bus.register("well_event", send_cam_job) @@ -190,9 +184,7 @@ async def calc_gain(center, event): ): return - await asyncio.sleep(START_STOP_DELAY) await center.actions.command.stop_imaging() - await asyncio.sleep(START_STOP_DELAY) nonlocal save_path if save_path is None: @@ -351,7 +343,6 @@ async def stop_imaging(center, event): return # Sleep to let images be completely scanned before stopping. - await asyncio.sleep(START_STOP_DELAY) await center.actions.api.stop_imaging() center.bus.register("well_event", stop_imaging) From cbd3d333c1071558339a91689649a2e029249855 Mon Sep 17 00:00:00 2001 From: Martin Hjelmare Date: Sat, 16 Nov 2019 16:33:26 +0100 Subject: [PATCH 30/76] Extend config schema --- camacqplugins/production/__init__.py | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/camacqplugins/production/__init__.py b/camacqplugins/production/__init__.py index 9a0112d..0ae2ac9 100644 --- a/camacqplugins/production/__init__.py +++ b/camacqplugins/production/__init__.py @@ -15,15 +15,22 @@ SAMPLE_STATE_FILE = "state_file" # pylint: disable=no-value-for-parameter -CONFIG_SCHEMA = vol.Schema({"plot_save_path": vol.IsDir()}, extra=vol.ALLOW_EXTRA) +CONFIG_SCHEMA = vol.Schema( + { + vol.Required("gain_pattern_name"): vol.Coerce(str), + vol.Required("green_job_name"): vol.Coerce(str), + vol.Required("blue_yellow_job_name"): vol.Coerce(str), + vol.Required("red_job_name"): vol.Coerce(str), + vol.Required("exp_pattern_name"): vol.Coerce(str), + "plot_save_path": vol.IsDir(), + }, + extra=vol.ALLOW_EXTRA, +) async def setup_module(center, config): """Set up production plugin.""" conf = config["production"] - - conf = CONFIG_SCHEMA(conf) - gain_pattern = conf["gain_pattern_name"] green_job = conf["green_job_name"] blue_yellow_job = conf["blue_yellow_job_name"] From b50643d70af5a7436bf6fdc48e26e5eb5e3e81fe Mon Sep 17 00:00:00 2001 From: Martin Hjelmare Date: Sun, 17 Nov 2019 14:44:39 +0100 Subject: [PATCH 31/76] Update readme --- README.md | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/README.md b/README.md index 1309904..2970a9f 100644 --- a/README.md +++ b/README.md @@ -4,6 +4,36 @@ Plugins for camacq ## Usage +To allow the user to set up the sample state before starting an +experiment, camacq can load the sample state from a file. In the production +configuration section there is an option to specify a path to a csv +file. + +```yaml +production: + state_file: '/sample_state.csv' +``` + +Each row in the csv file should represent a state of a sample container, +ie plate, well, field or channel. The csv file should also have a +header. See below. + +``` +plate_name,well_x,well_y,channel_name,gain +00,1,1,blue,600 +``` + +This example will set create a plate '00', a well (1, 1), a blue channel +and set the gain of the blue channel to 600. + +``` +plate_name,well_x,well_y,field_x,field_y +00,1,1,1,1 +``` + +This example will create a plate '00' a well (1, 1) and a field (1, 1) +in the sample state. + ## Installation ### Requirements From 64f32833caec045a56a87546a8be0db3b14bb18f Mon Sep 17 00:00:00 2001 From: Martin Hjelmare Date: Sun, 17 Nov 2019 15:16:19 +0100 Subject: [PATCH 32/76] Add channels config --- camacqplugins/production/__init__.py | 57 +++++++++++++--------------- 1 file changed, 26 insertions(+), 31 deletions(-) diff --git a/camacqplugins/production/__init__.py b/camacqplugins/production/__init__.py index 0ae2ac9..5e7d9fd 100644 --- a/camacqplugins/production/__init__.py +++ b/camacqplugins/production/__init__.py @@ -18,13 +18,19 @@ CONFIG_SCHEMA = vol.Schema( { vol.Required("gain_pattern_name"): vol.Coerce(str), - vol.Required("green_job_name"): vol.Coerce(str), - vol.Required("blue_yellow_job_name"): vol.Coerce(str), - vol.Required("red_job_name"): vol.Coerce(str), vol.Required("exp_pattern_name"): vol.Coerce(str), + vol.Required("channels"): [ + { + vol.Required("channel"): vol.Coerce(str), + vol.Required("job_name"): vol.Coerce(str), + vol.Required("detector_num"): vol.Coerce(int), + vol.Required("default_gain"): vol.Coerce(int), + vol.Required("max_gain"): vol.Coerce(int), + } + ], "plot_save_path": vol.IsDir(), + SAMPLE_STATE_FILE: vol.IsFile(), }, - extra=vol.ALLOW_EXTRA, ) @@ -32,14 +38,12 @@ async def setup_module(center, config): """Set up production plugin.""" conf = config["production"] gain_pattern = conf["gain_pattern_name"] - green_job = conf["green_job_name"] - blue_yellow_job = conf["blue_yellow_job_name"] - red_job = conf["red_job_name"] exp_pattern = conf["exp_pattern_name"] + channels = conf["channels"] plot_save_path = conf.get("plot_save_path") subscriptions = dotdict() - state_file = conf.get(SAMPLE_STATE_FILE) if conf else None + state_file = conf.get(SAMPLE_STATE_FILE) if state_file is not None: x_wells = None y_wells = None @@ -53,8 +57,8 @@ async def setup_module(center, config): image_next_well_on_event(center, gain_pattern, subscriptions) analyze_gain(center, plot_save_path) - set_exp_gain(center, green_job, blue_yellow_job, red_job) - add_exp_job(center, exp_pattern, subscriptions) + set_exp_gain(center, channels) + add_exp_job(center, channels, exp_pattern, subscriptions) subscriptions.set_img_ok = set_img_ok(center) subscriptions.rename_exp_image = rename_exp_image(center) # FIXME: Make stop exp work for state file. @@ -215,28 +219,17 @@ async def calc_gain(center, event): center.bus.register("image_event", calc_gain) -def set_exp_gain(center, green_job, blue_yellow_job, red_job): +def set_exp_gain(center, channels): """Set experiment gain.""" async def set_gain(center, event): """Set pmt gain.""" - if event.channel_name == "green": - exp = green_job - num = 1 - gain = min(event.gain or 800, 800) - elif event.channel_name == "blue": - exp = blue_yellow_job - num = 1 - gain = min(event.gain or 505, 610) - elif event.channel_name == "yellow": - exp = blue_yellow_job - num = 2 - gain = min(event.gain or 655, 760) - elif event.channel_name == "red": - exp = red_job - num = 2 - gain = event.gain or 630 - gain = min(gain + 25, 735) + for channel in channels: + if event.channel_name != channel["channel"]: + continue + exp = channel["job_name"] + num = channel["detector_num"] + gain = min(event.gain or channel["default_gain"], channel["max_gain"]) command = gain_com(exp=exp, num=num, value=gain) @@ -254,13 +247,15 @@ async def set_gain(center, event): center.bus.register("gain_calc_event", set_gain) -def add_exp_job(center, exp_pattern, subscriptions): +def add_exp_job(center, channels, exp_pattern, subscriptions): """Add experiment job.""" async def add_cam_job(center, event): """Add an experiment job to the cam list.""" - # TODO: Make channels layout configurable. - if not match_event(event, channel_name="red") or len(event.well.channels) != 4: + last_channel = channels[-1] + if not match_event(event, channel_name=last_channel["channel"]) or len( + event.well.channels + ) != len(channels): return # TODO: Make well layout configurable. From 542895c584c58f367e98c237d43ae03eb30da2ec Mon Sep 17 00:00:00 2001 From: Martin Hjelmare Date: Sun, 17 Nov 2019 15:19:13 +0100 Subject: [PATCH 33/76] Add todo comment --- camacqplugins/production/__init__.py | 1 + 1 file changed, 1 insertion(+) diff --git a/camacqplugins/production/__init__.py b/camacqplugins/production/__init__.py index 5e7d9fd..7ba6791 100644 --- a/camacqplugins/production/__init__.py +++ b/camacqplugins/production/__init__.py @@ -303,6 +303,7 @@ async def set_sample_img_ok(center, event): def rename_exp_image(center): """Rename an experiment image.""" + # TODO: Make experiment pattern job_ids configurable. async def rename_image(center, event): """Rename an image.""" From ee927b34754a68e5c6125df60a20a8e0e5854e9e Mon Sep 17 00:00:00 2001 From: Martin Hjelmare Date: Sun, 17 Nov 2019 15:48:45 +0100 Subject: [PATCH 34/76] Add well layout option --- camacqplugins/production/__init__.py | 57 ++++++++++++++++++---------- 1 file changed, 37 insertions(+), 20 deletions(-) diff --git a/camacqplugins/production/__init__.py b/camacqplugins/production/__init__.py index 7ba6791..ffe441f 100644 --- a/camacqplugins/production/__init__.py +++ b/camacqplugins/production/__init__.py @@ -28,6 +28,10 @@ vol.Required("max_gain"): vol.Coerce(int), } ], + vol.Required("well_layout"): { + vol.Required("x_fields"): vol.Coerce(int), + vol.Required("y_fields"): vol.Coerce(int), + }, "plot_save_path": vol.IsDir(), SAMPLE_STATE_FILE: vol.IsFile(), }, @@ -40,6 +44,7 @@ async def setup_module(center, config): gain_pattern = conf["gain_pattern_name"] exp_pattern = conf["exp_pattern_name"] channels = conf["channels"] + well_layout = conf["well_layout"] plot_save_path = conf.get("plot_save_path") subscriptions = dotdict() @@ -48,21 +53,21 @@ async def setup_module(center, config): x_wells = None y_wells = None await load_sample(center, state_file) - image_next_well_on_sample(center, gain_pattern, subscriptions) + image_next_well_on_sample(center, gain_pattern, subscriptions, well_layout) else: x_wells = 12 y_wells = 8 start_exp(center) - add_next_well(center, x_wells, y_wells) + add_next_well(center, x_wells, y_wells, well_layout) image_next_well_on_event(center, gain_pattern, subscriptions) analyze_gain(center, plot_save_path) set_exp_gain(center, channels) - add_exp_job(center, channels, exp_pattern, subscriptions) + add_exp_job(center, channels, exp_pattern, subscriptions, well_layout) subscriptions.set_img_ok = set_img_ok(center) subscriptions.rename_exp_image = rename_exp_image(center) # FIXME: Make stop exp work for state file. - stop_exp(center, x_wells, y_wells) + stop_exp(center, x_wells, y_wells, well_layout) async def load_sample(center, state_file): @@ -85,17 +90,21 @@ async def set_start_well(center, event): center.bus.register("camacq_start_event", set_start_well) -def add_next_well(center, x_wells, y_wells): +def add_next_well(center, x_wells, y_wells, well_layout): """Add next well.""" async def set_next_well(center, event): """Run on well event.""" - # TODO: Make well layout and stop field coordinates configurable. plate_name = "00" next_well_x, _ = next_well_xy(center.sample, plate_name, x_wells, y_wells) if ( - not match_event(event, field_x=1, field_y=2, well_img_ok=True) + not match_event( + event, + field_x=well_layout["x_fields"] - 1, + field_y=well_layout["y_fields"] - 1, + well_img_ok=True, + ) or next_well_x is None ): return @@ -111,24 +120,28 @@ async def set_next_well(center, event): center.bus.register("well_event", set_next_well) -def image_next_well_on_sample(center, gain_pattern, subscriptions): +def image_next_well_on_sample(center, gain_pattern, subscriptions, well_layout): """Image next well in existing sample.""" async def send_cam_job(center, event): """Run on well event.""" - # TODO: Make stop field coordinates configurable. plate_name = "00" next_well_x, next_well_y = next_well_xy(center.sample, plate_name) if ( not match_event(event, event_type="camacq_start_event") - and not match_event(event, field_x=1, field_y=2, well_img_ok=True) + and not match_event( + event, + field_x=well_layout["x_fields"] - 1, + field_y=well_layout["y_fields"] - 1, + well_img_ok=True, + ) or next_well_x is None ): return await center.actions.command.send(command=del_com()) - # TODO: Make field coordinates configurable. + # TODO: Make field coordinates for gain job configurable. command = cam_com(gain_pattern, next_well_x, next_well_y, 0, 1, 0, 0) await center.actions.command.send(command=command) command = cam_com(gain_pattern, next_well_x, next_well_y, 1, 1, 0, 0) @@ -157,7 +170,7 @@ async def send_cam_job(center, event): return await center.actions.command.send(command=del_com()) - # TODO: Make field coordinates configurable. + # TODO: Make field coordinates for gain job configurable. command = cam_com(gain_pattern, event.well.x, event.well.y, 0, 1, 0, 0) await center.actions.command.send(command=command) command = cam_com(gain_pattern, event.well.x, event.well.y, 1, 1, 0, 0) @@ -181,7 +194,8 @@ def analyze_gain(center, save_path): async def calc_gain(center, event): """Calculate correct gain.""" - # TODO: Make event field coordinates, job id and save_path configurable. + # TODO: Make last gain image field coordinates, job id + # and save_path configurable. field_x = 1 field_y = 1 job_id = 3 @@ -247,7 +261,7 @@ async def set_gain(center, event): center.bus.register("gain_calc_event", set_gain) -def add_exp_job(center, channels, exp_pattern, subscriptions): +def add_exp_job(center, channels, exp_pattern, subscriptions, well_layout): """Add experiment job.""" async def add_cam_job(center, event): @@ -258,10 +272,9 @@ async def add_cam_job(center, event): ) != len(channels): return - # TODO: Make well layout configurable. commands = [] - for field_x in range(2): - for field_y in range(3): + for field_x in range(well_layout["x_fields"]): + for field_y in range(well_layout["y_fields"]): cmd = cam_com( exp_pattern, event.well_x, event.well_y, field_x, field_y, 0, 0 ) @@ -331,16 +344,20 @@ async def rename_image(center, event): return center.bus.register("image_event", rename_image) -def stop_exp(center, x_wells, y_wells): +def stop_exp(center, x_wells, y_wells, well_layout): """Trigger to stop experiment.""" async def stop_imaging(center, event): """Run to stop the experiment.""" - # TODO: Make well layout and stop field coordinates configurable. next_well_x, _ = next_well_xy(center.sample, "00", x_wells, y_wells) if ( - not match_event(event, field_x=1, field_y=2, well_img_ok=True) + not match_event( + event, + field_x=well_layout["x_fields"] - 1, + field_y=well_layout["y_fields"] - 1, + well_img_ok=True, + ) or next_well_x is not None ): return From 57b8ea5cedf56234fe50deb1d6d8f99798bcfa99 Mon Sep 17 00:00:00 2001 From: Martin Hjelmare Date: Sun, 17 Nov 2019 15:49:36 +0100 Subject: [PATCH 35/76] Clean up --- camacqplugins/production/__init__.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/camacqplugins/production/__init__.py b/camacqplugins/production/__init__.py index ffe441f..acc15d7 100644 --- a/camacqplugins/production/__init__.py +++ b/camacqplugins/production/__init__.py @@ -14,7 +14,6 @@ SAMPLE_STATE_FILE = "state_file" -# pylint: disable=no-value-for-parameter CONFIG_SCHEMA = vol.Schema( { vol.Required("gain_pattern_name"): vol.Coerce(str), @@ -32,6 +31,7 @@ vol.Required("x_fields"): vol.Coerce(int), vol.Required("y_fields"): vol.Coerce(int), }, + # pylint: disable=no-value-for-parameter "plot_save_path": vol.IsDir(), SAMPLE_STATE_FILE: vol.IsFile(), }, @@ -362,7 +362,6 @@ async def stop_imaging(center, event): ): return - # Sleep to let images be completely scanned before stopping. await center.actions.api.stop_imaging() center.bus.register("well_event", stop_imaging) From 460744ac412f9df652f5129d43334b76d11ffef5 Mon Sep 17 00:00:00 2001 From: Martin Hjelmare Date: Fri, 29 Nov 2019 17:44:48 +0100 Subject: [PATCH 36/76] Guard against duplicate image events --- camacqplugins/production/__init__.py | 56 +++++++++++++++++----------- 1 file changed, 34 insertions(+), 22 deletions(-) diff --git a/camacqplugins/production/__init__.py b/camacqplugins/production/__init__.py index acc15d7..b585a9f 100644 --- a/camacqplugins/production/__init__.py +++ b/camacqplugins/production/__init__.py @@ -1,4 +1,5 @@ """Provide a plugin for production standard flow.""" +import asyncio import logging import tempfile from pathlib import Path @@ -61,7 +62,7 @@ async def setup_module(center, config): add_next_well(center, x_wells, y_wells, well_layout) image_next_well_on_event(center, gain_pattern, subscriptions) - analyze_gain(center, plot_save_path) + analyze_gain(center, plot_save_path, asyncio.Lock()) set_exp_gain(center, channels) add_exp_job(center, channels, exp_pattern, subscriptions, well_layout) subscriptions.set_img_ok = set_img_ok(center) @@ -189,7 +190,7 @@ async def send_cam_job(center, event): center.bus.register("well_event", send_cam_job) -def analyze_gain(center, save_path): +def analyze_gain(center, save_path, gain_lock): """Analyze gain.""" async def calc_gain(center, event): @@ -209,26 +210,37 @@ async def calc_gain(center, event): ): return - await center.actions.command.stop_imaging() - - nonlocal save_path - if save_path is None: - save_path = Path(tempfile.gettempdir()) / event.plate_name - else: - save_path = Path(save_path) - if not save_path.exists(): - await center.add_executor_job(save_path.mkdir) - - # This should be a path to a base file name, not to an actual dir or file. - save_path = save_path / f"{event.well_x}--{event.well_y}" - - await center.actions.gain.calc_gain( - plate_name=event.plate_name, - well_x=event.well_x, - well_y=event.well_y, - make_plots=True, - save_path=save_path, - ) + # Guard against duplicate image events. + async with gain_lock: + well = center.sample.get_well( + plate_name=event.plate_name, well_x=event.well_x, well_y=event.well_y + ) + gain_set = any( + channel.gain is not None for channel in well.channels.values() + ) + if gain_set: + return + + await center.actions.command.stop_imaging() + + nonlocal save_path + if save_path is None: + save_path = Path(tempfile.gettempdir()) / event.plate_name + else: + save_path = Path(save_path) + if not save_path.exists(): + await center.add_executor_job(save_path.mkdir) + + # This should be a path to a base file name, not to an actual dir or file. + save_path = save_path / f"{event.well_x}--{event.well_y}" + + await center.actions.gain.calc_gain( + plate_name=event.plate_name, + well_x=event.well_x, + well_y=event.well_y, + make_plots=True, + save_path=save_path, + ) center.bus.register("image_event", calc_gain) From 44db2d36f0b2acc723f0be79a3c66dc39637f317 Mon Sep 17 00:00:00 2001 From: Martin Hjelmare Date: Fri, 29 Nov 2019 19:21:29 +0100 Subject: [PATCH 37/76] Add debug script --- scripts/debug.py | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 scripts/debug.py diff --git a/scripts/debug.py b/scripts/debug.py new file mode 100644 index 0000000..3a4ff8c --- /dev/null +++ b/scripts/debug.py @@ -0,0 +1,7 @@ +#!/usr/bin/env python3 +"""Start camacq to debug in vscode.""" +from camacq.__main__ import main + + +if __name__ == "__main__": + main(args=["--log-level", "debug"]) From 4d213d6cb118873898e84586c07dc7ec56b6a384 Mon Sep 17 00:00:00 2001 From: Martin Hjelmare Date: Fri, 29 Nov 2019 19:23:57 +0100 Subject: [PATCH 38/76] Fix stop experiment --- camacqplugins/production/__init__.py | 21 +++++++++------------ 1 file changed, 9 insertions(+), 12 deletions(-) diff --git a/camacqplugins/production/__init__.py b/camacqplugins/production/__init__.py index b585a9f..7fb5294 100644 --- a/camacqplugins/production/__init__.py +++ b/camacqplugins/production/__init__.py @@ -67,7 +67,6 @@ async def setup_module(center, config): add_exp_job(center, channels, exp_pattern, subscriptions, well_layout) subscriptions.set_img_ok = set_img_ok(center) subscriptions.rename_exp_image = rename_exp_image(center) - # FIXME: Make stop exp work for state file. stop_exp(center, x_wells, y_wells, well_layout) @@ -295,7 +294,7 @@ async def add_cam_job(center, event): await center.actions.command.send(command=del_com()) await center.actions.command.send_many(commands=commands) - if subscriptions.set_img_ok is not None: + if subscriptions.set_img_ok is None: subscriptions.set_img_ok = set_img_ok(center) if subscriptions.rename_exp_image is None: subscriptions.rename_exp_image = rename_exp_image(center) @@ -362,18 +361,16 @@ def stop_exp(center, x_wells, y_wells, well_layout): async def stop_imaging(center, event): """Run to stop the experiment.""" next_well_x, _ = next_well_xy(center.sample, "00", x_wells, y_wells) + match = match_event( + event, + field_x=well_layout["x_fields"] - 1, + field_y=well_layout["y_fields"] - 1, + well_img_ok=True, + ) - if ( - not match_event( - event, - field_x=well_layout["x_fields"] - 1, - field_y=well_layout["y_fields"] - 1, - well_img_ok=True, - ) - or next_well_x is not None - ): + if not match or next_well_x is not None: return - await center.actions.api.stop_imaging() + await center.actions.command.stop_imaging() center.bus.register("well_event", stop_imaging) From d5d3f522b9414202e5f7d27168216b0b8d84183f Mon Sep 17 00:00:00 2001 From: Martin Hjelmare Date: Fri, 29 Nov 2019 20:31:24 +0100 Subject: [PATCH 39/76] Add production config template --- config_templates/production.yml | 56 +++++++++++++++++++++++++++++++++ 1 file changed, 56 insertions(+) create mode 100644 config_templates/production.yml diff --git a/config_templates/production.yml b/config_templates/production.yml new file mode 100644 index 0000000..8d55429 --- /dev/null +++ b/config_templates/production.yml @@ -0,0 +1,56 @@ +production: + #state_file: "/path/to/sample_state.csv" + gain_pattern_name: p10xgain + exp_pattern_name: p10xexp + channels: + - channel: green + job_name: green10x + detector_num: 1 + default_gain: 800 + max_gain: 800 + - channel: blue + job_name: blue10x + detector_num: 1 + default_gain: 505 + max_gain: 610 + - channel: yellow + job_name: blue10x + detector_num: 2 + default_gain: 655 + max_gain: 760 + - channel: red + job_name: red10x + detector_num: 2 + default_gain: 630 + max_gain: 735 + well_layout: + x_fields: 2 + y_fields: 3 + #plot_save_path: "/path/to/gains/dir/00/" + + gain: + channels: + - channel: green + init_gain: [450, 495, 540, 585, 630, 675, 720, 765, 810, 855, 900] + - channel: blue + # 63x + #init_gain: [750, 730, 765, 800, 835, 870, 905] + # 10x + init_gain: [700, 735, 770, 805, 840, 875, 910] + - channel: yellow + # 63x + #init_gain: [550, 585, 620, 655, 690, 725, 760] + # 10x + init_gain: [700, 735, 770, 805, 840, 875, 910] + - channel: red + # 63x + #init_gain: [525, 560, 595, 630, 665, 700, 735] + # 10x + init_gain: [600, 635, 670, 705, 740, 775, 810] + #save_dir: "/path/to/gains/dir" + +rename_image: + +leica: + host: localhost + #imaging_dir: '/path/to/imaging_dir' From 51f1bb5be6e9ba4668de258518c9d9be661b1503 Mon Sep 17 00:00:00 2001 From: Martin Hjelmare Date: Sat, 30 Nov 2019 13:56:00 +0100 Subject: [PATCH 40/76] Set gain fields from well layout --- camacqplugins/production/__init__.py | 72 +++++++++++++++------------- 1 file changed, 38 insertions(+), 34 deletions(-) diff --git a/camacqplugins/production/__init__.py b/camacqplugins/production/__init__.py index 7fb5294..b965704 100644 --- a/camacqplugins/production/__init__.py +++ b/camacqplugins/production/__init__.py @@ -2,6 +2,7 @@ import asyncio import logging import tempfile +from math import ceil from pathlib import Path import voluptuous as vol @@ -46,6 +47,8 @@ async def setup_module(center, config): exp_pattern = conf["exp_pattern_name"] channels = conf["channels"] well_layout = conf["well_layout"] + x_fields = well_layout["x_fields"] + y_fields = well_layout["y_fields"] plot_save_path = conf.get("plot_save_path") subscriptions = dotdict() @@ -54,20 +57,24 @@ async def setup_module(center, config): x_wells = None y_wells = None await load_sample(center, state_file) - image_next_well_on_sample(center, gain_pattern, subscriptions, well_layout) + image_next_well_on_sample( + center, gain_pattern, subscriptions, x_fields, y_fields + ) else: x_wells = 12 y_wells = 8 start_exp(center) - add_next_well(center, x_wells, y_wells, well_layout) - image_next_well_on_event(center, gain_pattern, subscriptions) + add_next_well(center, x_wells, y_wells, x_fields, y_fields) + image_next_well_on_event( + center, gain_pattern, subscriptions, x_fields, y_fields + ) analyze_gain(center, plot_save_path, asyncio.Lock()) set_exp_gain(center, channels) - add_exp_job(center, channels, exp_pattern, subscriptions, well_layout) + add_exp_job(center, channels, exp_pattern, subscriptions, x_fields, y_fields) subscriptions.set_img_ok = set_img_ok(center) subscriptions.rename_exp_image = rename_exp_image(center) - stop_exp(center, x_wells, y_wells, well_layout) + stop_exp(center, x_wells, y_wells, x_fields, y_fields) async def load_sample(center, state_file): @@ -90,7 +97,7 @@ async def set_start_well(center, event): center.bus.register("camacq_start_event", set_start_well) -def add_next_well(center, x_wells, y_wells, well_layout): +def add_next_well(center, x_wells, y_wells, x_fields, y_fields): """Add next well.""" async def set_next_well(center, event): @@ -100,10 +107,7 @@ async def set_next_well(center, event): if ( not match_event( - event, - field_x=well_layout["x_fields"] - 1, - field_y=well_layout["y_fields"] - 1, - well_img_ok=True, + event, field_x=x_fields - 1, field_y=y_fields - 1, well_img_ok=True, ) or next_well_x is None ): @@ -120,7 +124,7 @@ async def set_next_well(center, event): center.bus.register("well_event", set_next_well) -def image_next_well_on_sample(center, gain_pattern, subscriptions, well_layout): +def image_next_well_on_sample(center, gain_pattern, subscriptions, x_fields, y_fields): """Image next well in existing sample.""" async def send_cam_job(center, event): @@ -131,21 +135,21 @@ async def send_cam_job(center, event): if ( not match_event(event, event_type="camacq_start_event") and not match_event( - event, - field_x=well_layout["x_fields"] - 1, - field_y=well_layout["y_fields"] - 1, - well_img_ok=True, + event, field_x=x_fields - 1, field_y=y_fields - 1, well_img_ok=True, ) or next_well_x is None ): return await center.actions.command.send(command=del_com()) - # TODO: Make field coordinates for gain job configurable. - command = cam_com(gain_pattern, next_well_x, next_well_y, 0, 1, 0, 0) - await center.actions.command.send(command=command) - command = cam_com(gain_pattern, next_well_x, next_well_y, 1, 1, 0, 0) - await center.actions.command.send(command=command) + + gain_x_field = ceil(x_fields / 2) - 1 + field_y = ceil(y_fields / 2) - 1 + for field_x in range(gain_x_field, gain_x_field + 2): + command = cam_com( + gain_pattern, next_well_x, next_well_y, field_x, field_y, 0, 0 + ) + await center.actions.command.send(command=command) if subscriptions.set_img_ok is not None: subscriptions.set_img_ok() @@ -161,7 +165,7 @@ async def send_cam_job(center, event): center.bus.register("well_event", send_cam_job) -def image_next_well_on_event(center, gain_pattern, subscriptions): +def image_next_well_on_event(center, gain_pattern, subscriptions, x_fields, y_fields): """Image next well.""" async def send_cam_job(center, event): @@ -170,11 +174,14 @@ async def send_cam_job(center, event): return await center.actions.command.send(command=del_com()) - # TODO: Make field coordinates for gain job configurable. - command = cam_com(gain_pattern, event.well.x, event.well.y, 0, 1, 0, 0) - await center.actions.command.send(command=command) - command = cam_com(gain_pattern, event.well.x, event.well.y, 1, 1, 0, 0) - await center.actions.command.send(command=command) + + gain_x_field = ceil(x_fields / 2) - 1 + field_y = ceil(y_fields / 2) - 1 + for field_x in range(gain_x_field, gain_x_field + 2): + command = cam_com( + gain_pattern, event.well.x, event.well.y, field_x, field_y, 0, 0 + ) + await center.actions.command.send(command=command) if subscriptions.set_img_ok is not None: subscriptions.set_img_ok() @@ -272,7 +279,7 @@ async def set_gain(center, event): center.bus.register("gain_calc_event", set_gain) -def add_exp_job(center, channels, exp_pattern, subscriptions, well_layout): +def add_exp_job(center, channels, exp_pattern, subscriptions, x_fields, y_fields): """Add experiment job.""" async def add_cam_job(center, event): @@ -284,8 +291,8 @@ async def add_cam_job(center, event): return commands = [] - for field_x in range(well_layout["x_fields"]): - for field_y in range(well_layout["y_fields"]): + for field_x in range(x_fields): + for field_y in range(y_fields): cmd = cam_com( exp_pattern, event.well_x, event.well_y, field_x, field_y, 0, 0 ) @@ -355,17 +362,14 @@ async def rename_image(center, event): return center.bus.register("image_event", rename_image) -def stop_exp(center, x_wells, y_wells, well_layout): +def stop_exp(center, x_wells, y_wells, x_fields, y_fields): """Trigger to stop experiment.""" async def stop_imaging(center, event): """Run to stop the experiment.""" next_well_x, _ = next_well_xy(center.sample, "00", x_wells, y_wells) match = match_event( - event, - field_x=well_layout["x_fields"] - 1, - field_y=well_layout["y_fields"] - 1, - well_img_ok=True, + event, field_x=x_fields - 1, field_y=y_fields - 1, well_img_ok=True, ) if not match or next_well_x is not None: From 8e28e82ba4b02b585eebbe31f5c60a811c10c8cb Mon Sep 17 00:00:00 2001 From: Martin Hjelmare Date: Sat, 30 Nov 2019 14:50:06 +0100 Subject: [PATCH 41/76] Set last gain coordinate from well layout --- camacqplugins/production/__init__.py | 100 +++++++++++++++------------ 1 file changed, 56 insertions(+), 44 deletions(-) diff --git a/camacqplugins/production/__init__.py b/camacqplugins/production/__init__.py index b965704..0f7fd4d 100644 --- a/camacqplugins/production/__init__.py +++ b/camacqplugins/production/__init__.py @@ -69,7 +69,7 @@ async def setup_module(center, config): center, gain_pattern, subscriptions, x_fields, y_fields ) - analyze_gain(center, plot_save_path, asyncio.Lock()) + analyze_gain(center, plot_save_path, asyncio.Lock(), x_fields, y_fields) set_exp_gain(center, channels) add_exp_job(center, channels, exp_pattern, subscriptions, x_fields, y_fields) subscriptions.set_img_ok = set_img_ok(center) @@ -141,25 +141,15 @@ async def send_cam_job(center, event): ): return - await center.actions.command.send(command=del_com()) - - gain_x_field = ceil(x_fields / 2) - 1 - field_y = ceil(y_fields / 2) - 1 - for field_x in range(gain_x_field, gain_x_field + 2): - command = cam_com( - gain_pattern, next_well_x, next_well_y, field_x, field_y, 0, 0 - ) - await center.actions.command.send(command=command) - - if subscriptions.set_img_ok is not None: - subscriptions.set_img_ok() - subscriptions.set_img_ok = None - if subscriptions.rename_exp_image is not None: - subscriptions.rename_exp_image() - subscriptions.rename_exp_image = None - - await center.actions.command.start_imaging() - await center.actions.command.send(command="/cmd:startcamscan") + await send_gain_jobs( + center, + gain_pattern, + next_well_x, + next_well_y, + x_fields, + y_fields, + subscriptions, + ) center.bus.register("camacq_start_event", send_cam_job) center.bus.register("well_event", send_cam_job) @@ -173,38 +163,26 @@ async def send_cam_job(center, event): if event.well.images: return - await center.actions.command.send(command=del_com()) - - gain_x_field = ceil(x_fields / 2) - 1 - field_y = ceil(y_fields / 2) - 1 - for field_x in range(gain_x_field, gain_x_field + 2): - command = cam_com( - gain_pattern, event.well.x, event.well.y, field_x, field_y, 0, 0 - ) - await center.actions.command.send(command=command) - - if subscriptions.set_img_ok is not None: - subscriptions.set_img_ok() - subscriptions.set_img_ok = None - if subscriptions.rename_exp_image is not None: - subscriptions.rename_exp_image() - subscriptions.rename_exp_image = None - - await center.actions.command.start_imaging() - await center.actions.command.send(command="/cmd:startcamscan") + await send_gain_jobs( + center, + gain_pattern, + event.well.x, + event.well.y, + x_fields, + y_fields, + subscriptions, + ) center.bus.register("well_event", send_cam_job) -def analyze_gain(center, save_path, gain_lock): +def analyze_gain(center, save_path, gain_lock, x_fields, y_fields): """Analyze gain.""" async def calc_gain(center, event): """Calculate correct gain.""" - # TODO: Make last gain image field coordinates, job id - # and save_path configurable. - field_x = 1 - field_y = 1 + # TODO: Make job id and channel id configurable. + field_x, field_y = get_last_gain_coords(x_fields, y_fields) job_id = 3 channel_id = 31 if not match_event( @@ -378,3 +356,37 @@ async def stop_imaging(center, event): await center.actions.command.stop_imaging() center.bus.register("well_event", stop_imaging) + + +def get_last_gain_coords(x_fields, y_fields): + """Return a tuple with last gain coordinates x and y. + + The gain coordinates will be the two most centered fields. + """ + last_x_field = ceil(x_fields / 2) + last_y_field = ceil(y_fields / 2) - 1 + return last_x_field, last_y_field + + +async def send_gain_jobs( + center, gain_job, well_x, well_y, x_fields, y_fields, subscriptions +): + """Send gain cam jobs for the center fields of a well.""" + field_x, field_y = get_last_gain_coords(x_fields, y_fields) + field_x = field_x - 1 # set the start x field coord + + await center.actions.command.send(command=del_com()) + + for field_x in range(field_x, field_x + 2): + command = cam_com(gain_job, well_x, well_y, field_x, field_y, 0, 0) + await center.actions.command.send(command=command) + + if subscriptions.set_img_ok is not None: + subscriptions.set_img_ok() + subscriptions.set_img_ok = None + if subscriptions.rename_exp_image is not None: + subscriptions.rename_exp_image() + subscriptions.rename_exp_image = None + + await center.actions.command.start_imaging() + await center.actions.command.send(command="/cmd:startcamscan") From d4070e86c68b2adedf5a70eb9f19b0b7e799b5f9 Mon Sep 17 00:00:00 2001 From: Martin Hjelmare Date: Sat, 30 Nov 2019 15:17:03 +0100 Subject: [PATCH 42/76] Make job ids configurable --- camacqplugins/production/__init__.py | 55 +++++++++++++++++++--------- config_templates/production.yml | 6 +++ 2 files changed, 43 insertions(+), 18 deletions(-) diff --git a/camacqplugins/production/__init__.py b/camacqplugins/production/__init__.py index 0f7fd4d..6386782 100644 --- a/camacqplugins/production/__init__.py +++ b/camacqplugins/production/__init__.py @@ -19,7 +19,12 @@ CONFIG_SCHEMA = vol.Schema( { vol.Required("gain_pattern_name"): vol.Coerce(str), + vol.Required("gain_job_id"): vol.Coerce(int), + vol.Required("gain_job_channels"): vol.Coerce(int), vol.Required("exp_pattern_name"): vol.Coerce(str), + vol.Required("exp_job_ids"): vol.All( + [vol.Coerce(int)], vol.Length(min=3, max=3) + ), vol.Required("channels"): [ { vol.Required("channel"): vol.Coerce(str), @@ -44,7 +49,10 @@ async def setup_module(center, config): """Set up production plugin.""" conf = config["production"] gain_pattern = conf["gain_pattern_name"] + gain_job_id = conf["gain_job_id"] + gain_job_channels = conf["gain_job_channels"] exp_pattern = conf["exp_pattern_name"] + exp_job_ids = conf["exp_job_ids"] channels = conf["channels"] well_layout = conf["well_layout"] x_fields = well_layout["x_fields"] @@ -69,11 +77,21 @@ async def setup_module(center, config): center, gain_pattern, subscriptions, x_fields, y_fields ) - analyze_gain(center, plot_save_path, asyncio.Lock(), x_fields, y_fields) + analyze_gain( + center, + plot_save_path, + asyncio.Lock(), + x_fields, + y_fields, + gain_job_id, + gain_job_channels, + ) set_exp_gain(center, channels) - add_exp_job(center, channels, exp_pattern, subscriptions, x_fields, y_fields) + add_exp_job( + center, channels, exp_pattern, subscriptions, x_fields, y_fields, exp_job_ids + ) subscriptions.set_img_ok = set_img_ok(center) - subscriptions.rename_exp_image = rename_exp_image(center) + subscriptions.rename_exp_image = rename_exp_image(center, exp_job_ids) stop_exp(center, x_wells, y_wells, x_fields, y_fields) @@ -176,20 +194,20 @@ async def send_cam_job(center, event): center.bus.register("well_event", send_cam_job) -def analyze_gain(center, save_path, gain_lock, x_fields, y_fields): +def analyze_gain( + center, save_path, gain_lock, x_fields, y_fields, gain_job_id, gain_job_channels +): """Analyze gain.""" async def calc_gain(center, event): """Calculate correct gain.""" - # TODO: Make job id and channel id configurable. field_x, field_y = get_last_gain_coords(x_fields, y_fields) - job_id = 3 - channel_id = 31 + channel_id = gain_job_channels - 1 if not match_event( event, field_x=field_x, field_y=field_y, - job_id=job_id, + job_id=gain_job_id, channel_id=channel_id, ): return @@ -257,7 +275,9 @@ async def set_gain(center, event): center.bus.register("gain_calc_event", set_gain) -def add_exp_job(center, channels, exp_pattern, subscriptions, x_fields, y_fields): +def add_exp_job( + center, channels, exp_pattern, subscriptions, x_fields, y_fields, exp_job_ids +): """Add experiment job.""" async def add_cam_job(center, event): @@ -282,7 +302,7 @@ async def add_cam_job(center, event): if subscriptions.set_img_ok is None: subscriptions.set_img_ok = set_img_ok(center) if subscriptions.rename_exp_image is None: - subscriptions.rename_exp_image = rename_exp_image(center) + subscriptions.rename_exp_image = rename_exp_image(center, exp_job_ids) await center.actions.command.start_imaging() await center.actions.command.send(command="/cmd:startcamscan") @@ -310,22 +330,21 @@ async def set_sample_img_ok(center, event): return center.bus.register("image_event", set_sample_img_ok) -def rename_exp_image(center): +def rename_exp_image(center, exp_job_ids): """Rename an experiment image.""" - # TODO: Make experiment pattern job_ids configurable. async def rename_image(center, event): """Rename an image.""" - if event.job_id not in (3, 4, 6): + if event.job_id not in exp_job_ids: return - if event.job_id == 3: - channel_id = event.channel_id - elif event.job_id == 4 and event.channel_id == 0: + if event.job_id == exp_job_ids[0]: + channel_id = 0 + elif event.job_id == exp_job_ids[1] and event.channel_id == 0: channel_id = 1 - elif event.job_id == 4 and event.channel_id == 1: + elif event.job_id == exp_job_ids[1] and event.channel_id == 1: channel_id = 2 - elif event.job_id == 6: + elif event.job_id == exp_job_ids[2]: channel_id = 3 new_name = ( diff --git a/config_templates/production.yml b/config_templates/production.yml index 8d55429..01dcf70 100644 --- a/config_templates/production.yml +++ b/config_templates/production.yml @@ -1,7 +1,13 @@ production: #state_file: "/path/to/sample_state.csv" gain_pattern_name: p10xgain + gain_job_id: 3 + gain_job_channels: 32 exp_pattern_name: p10xexp + exp_job_ids: + - 3 + - 4 + - 6 channels: - channel: green job_name: green10x From dc9803c8131e38846474a3a104189784dbbe150e Mon Sep 17 00:00:00 2001 From: Martin Hjelmare Date: Sat, 30 Nov 2019 15:27:30 +0100 Subject: [PATCH 43/76] Make rename image more robust --- camacqplugins/production/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/camacqplugins/production/__init__.py b/camacqplugins/production/__init__.py index 6386782..cacb23d 100644 --- a/camacqplugins/production/__init__.py +++ b/camacqplugins/production/__init__.py @@ -335,7 +335,7 @@ def rename_exp_image(center, exp_job_ids): async def rename_image(center, event): """Rename an image.""" - if event.job_id not in exp_job_ids: + if event.job_id not in exp_job_ids or event.channel_id not in (0, 1): return if event.job_id == exp_job_ids[0]: From 55335303f76cf39ce3632436de0aea8a0f429eb3 Mon Sep 17 00:00:00 2001 From: Martin Hjelmare Date: Sat, 30 Nov 2019 15:46:16 +0100 Subject: [PATCH 44/76] Update pylintrc --- pylintrc | 3 +++ 1 file changed, 3 insertions(+) diff --git a/pylintrc b/pylintrc index 199fca4..29a954e 100644 --- a/pylintrc +++ b/pylintrc @@ -1,6 +1,9 @@ [MASTER] reports=no +max-args=7 +max-locals=20 + # Black needs to disable wrong-hanging-indent. disable= From 30d2c35352dbbd92a76ee806bded80bcb515a55f Mon Sep 17 00:00:00 2001 From: Martin Hjelmare Date: Sat, 30 Nov 2019 16:30:36 +0100 Subject: [PATCH 45/76] Make plate name constant --- camacqplugins/production/__init__.py | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/camacqplugins/production/__init__.py b/camacqplugins/production/__init__.py index cacb23d..84efbde 100644 --- a/camacqplugins/production/__init__.py +++ b/camacqplugins/production/__init__.py @@ -14,6 +14,7 @@ _LOGGER = logging.getLogger(__name__) +PLATE_NAME = "00" SAMPLE_STATE_FILE = "state_file" CONFIG_SCHEMA = vol.Schema( @@ -110,7 +111,7 @@ def start_exp(center): async def set_start_well(center, event): """Run on start event.""" - await center.actions.sample.set_well(plate_name="00", well_x=0, well_y=0) + await center.actions.sample.set_well(plate_name=PLATE_NAME, well_x=0, well_y=0) center.bus.register("camacq_start_event", set_start_well) @@ -120,8 +121,7 @@ def add_next_well(center, x_wells, y_wells, x_fields, y_fields): async def set_next_well(center, event): """Run on well event.""" - plate_name = "00" - next_well_x, _ = next_well_xy(center.sample, plate_name, x_wells, y_wells) + next_well_x, _ = next_well_xy(center.sample, PLATE_NAME, x_wells, y_wells) if ( not match_event( @@ -133,10 +133,10 @@ async def set_next_well(center, event): await center.actions.command.stop_imaging() - well_x, well_y = next_well_xy(center.sample, plate_name, x_wells, y_wells) + well_x, well_y = next_well_xy(center.sample, PLATE_NAME, x_wells, y_wells) await center.actions.sample.set_well( - plate_name=plate_name, well_x=well_x, well_y=well_y + plate_name=PLATE_NAME, well_x=well_x, well_y=well_y ) center.bus.register("well_event", set_next_well) @@ -147,8 +147,7 @@ def image_next_well_on_sample(center, gain_pattern, subscriptions, x_fields, y_f async def send_cam_job(center, event): """Run on well event.""" - plate_name = "00" - next_well_x, next_well_y = next_well_xy(center.sample, plate_name) + next_well_x, next_well_y = next_well_xy(center.sample, PLATE_NAME) if ( not match_event(event, event_type="camacq_start_event") From 75c04e412175aa2bfcb07777d7b0747c819004b6 Mon Sep 17 00:00:00 2001 From: Martin Hjelmare Date: Sat, 30 Nov 2019 16:36:41 +0100 Subject: [PATCH 46/76] Fix rename name --- camacqplugins/production/__init__.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/camacqplugins/production/__init__.py b/camacqplugins/production/__init__.py index 84efbde..cf4e282 100644 --- a/camacqplugins/production/__init__.py +++ b/camacqplugins/production/__init__.py @@ -347,8 +347,9 @@ async def rename_image(center, event): channel_id = 3 new_name = ( - f"U{event.well_x:03}--V{event.well_y}--E{event.job_id}--X{event.field_x}" - f"--Y{event.field_y}--Z{event.z_slice}--C{channel_id}.ome.tif" + f"U{event.well_x:02}--V{event.well_y:02}--E{event.job_id:02}--" + f"X{event.field_x:02}--Y{event.field_y:02}--" + f"Z{event.z_slice:02}--C{channel_id:02}.ome.tif" ) await center.actions.rename_image.rename_image( From 869c2e98447fe4c9586f41fa5d1e323244a91694 Mon Sep 17 00:00:00 2001 From: Martin Hjelmare Date: Sat, 30 Nov 2019 16:37:10 +0100 Subject: [PATCH 47/76] Clean up --- camacqplugins/production/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/camacqplugins/production/__init__.py b/camacqplugins/production/__init__.py index cf4e282..01ff85b 100644 --- a/camacqplugins/production/__init__.py +++ b/camacqplugins/production/__init__.py @@ -364,7 +364,7 @@ def stop_exp(center, x_wells, y_wells, x_fields, y_fields): async def stop_imaging(center, event): """Run to stop the experiment.""" - next_well_x, _ = next_well_xy(center.sample, "00", x_wells, y_wells) + next_well_x, _ = next_well_xy(center.sample, PLATE_NAME, x_wells, y_wells) match = match_event( event, field_x=x_fields - 1, field_y=y_fields - 1, well_img_ok=True, ) From b72d12ae699c6984f1a2a9405d9391a39d9e7439 Mon Sep 17 00:00:00 2001 From: Martin Hjelmare Date: Sat, 30 Nov 2019 23:58:59 +0100 Subject: [PATCH 48/76] Refactor to use flow instance --- camacqplugins/production/__init__.py | 616 +++++++++++++-------------- pylintrc | 3 - 2 files changed, 307 insertions(+), 312 deletions(-) diff --git a/camacqplugins/production/__init__.py b/camacqplugins/production/__init__.py index 01ff85b..23319a0 100644 --- a/camacqplugins/production/__init__.py +++ b/camacqplugins/production/__init__.py @@ -10,7 +10,7 @@ from camacq.event import match_event from camacq.plugins.leica.command import cam_com, del_com, gain_com from camacq.plugins.sample.helper import next_well_xy -from camacq.util import dotdict, read_csv +from camacq.util import read_csv _LOGGER = logging.getLogger(__name__) @@ -49,332 +49,354 @@ async def setup_module(center, config): """Set up production plugin.""" conf = config["production"] - gain_pattern = conf["gain_pattern_name"] - gain_job_id = conf["gain_job_id"] - gain_job_channels = conf["gain_job_channels"] - exp_pattern = conf["exp_pattern_name"] - exp_job_ids = conf["exp_job_ids"] - channels = conf["channels"] - well_layout = conf["well_layout"] - x_fields = well_layout["x_fields"] - y_fields = well_layout["y_fields"] - plot_save_path = conf.get("plot_save_path") - subscriptions = dotdict() - + flow = WorkFlow(center, conf) state_file = conf.get(SAMPLE_STATE_FILE) - if state_file is not None: - x_wells = None - y_wells = None - await load_sample(center, state_file) - image_next_well_on_sample( - center, gain_pattern, subscriptions, x_fields, y_fields - ) - else: - x_wells = 12 - y_wells = 8 - start_exp(center) - add_next_well(center, x_wells, y_wells, x_fields, y_fields) - image_next_well_on_event( - center, gain_pattern, subscriptions, x_fields, y_fields - ) - - analyze_gain( - center, - plot_save_path, - asyncio.Lock(), - x_fields, - y_fields, - gain_job_id, - gain_job_channels, - ) - set_exp_gain(center, channels) - add_exp_job( - center, channels, exp_pattern, subscriptions, x_fields, y_fields, exp_job_ids - ) - subscriptions.set_img_ok = set_img_ok(center) - subscriptions.rename_exp_image = rename_exp_image(center, exp_job_ids) - stop_exp(center, x_wells, y_wells, x_fields, y_fields) - - -async def load_sample(center, state_file): - """Load sample state from file.""" - state_data = await center.add_executor_job(read_csv, state_file) - for data in state_data: - await center.actions.sample.set_plate(silent=True, **data) - await center.actions.sample.set_well(silent=True, **data) - await center.actions.sample.set_channel(silent=True, **data) - await center.actions.sample.set_field(silent=True, **data) - - -def start_exp(center): - """Trigger on start experiment.""" - - async def set_start_well(center, event): - """Run on start event.""" - await center.actions.sample.set_well(plate_name=PLATE_NAME, well_x=0, well_y=0) - - center.bus.register("camacq_start_event", set_start_well) - - -def add_next_well(center, x_wells, y_wells, x_fields, y_fields): - """Add next well.""" - - async def set_next_well(center, event): - """Run on well event.""" - next_well_x, _ = next_well_xy(center.sample, PLATE_NAME, x_wells, y_wells) - - if ( - not match_event( - event, field_x=x_fields - 1, field_y=y_fields - 1, well_img_ok=True, + await flow.setup(state_file) + + +class WorkFlow: + """Represent the production workflow.""" + + # pylint: disable=too-many-instance-attributes + + def __init__(self, center, conf): + """Set up instance.""" + self._center = center + self.gain_pattern = conf["gain_pattern_name"] + self.gain_job_id = conf["gain_job_id"] + self.gain_job_channels = conf["gain_job_channels"] + self.exp_pattern = conf["exp_pattern_name"] + self.exp_job_ids = conf["exp_job_ids"] + self.channels = conf["channels"] + well_layout = conf["well_layout"] + self.x_fields = well_layout["x_fields"] + self.y_fields = well_layout["y_fields"] + self.plot_save_path = conf.get("plot_save_path") + self._remove_set_img_ok = None + self._remove_rename_image = None + + async def setup(self, state_file): + """Set up the flow.""" + if state_file is not None: + x_wells = None + y_wells = None + await self.load_sample(state_file) + self.image_next_well_on_sample() + else: + x_wells = 12 + y_wells = 8 + self.start_exp() + self.add_next_well(x_wells, y_wells) + self.image_next_well_on_event() + + self.analyze_gain(asyncio.Lock()) + self.set_exp_gain() + self.add_exp_job() + self._remove_set_img_ok = self.set_img_ok() + self._remove_rename_image = self.rename_exp_image() + self.stop_exp(x_wells, y_wells) + + async def load_sample(self, state_file): + """Load sample state from file.""" + state_data = await self._center.add_executor_job(read_csv, state_file) + for data in state_data: + await self._center.actions.sample.set_plate(silent=True, **data) + await self._center.actions.sample.set_well(silent=True, **data) + await self._center.actions.sample.set_channel(silent=True, **data) + await self._center.actions.sample.set_field(silent=True, **data) + + def start_exp(self): + """Trigger on start experiment.""" + + async def set_start_well(center, event): + """Run on start event.""" + await center.actions.sample.set_well( + plate_name=PLATE_NAME, well_x=0, well_y=0 ) - or next_well_x is None - ): - return - - await center.actions.command.stop_imaging() - well_x, well_y = next_well_xy(center.sample, PLATE_NAME, x_wells, y_wells) + return self._center.bus.register("camacq_start_event", set_start_well) - await center.actions.sample.set_well( - plate_name=PLATE_NAME, well_x=well_x, well_y=well_y - ) + def add_next_well(self, x_wells, y_wells): + """Add next well.""" - center.bus.register("well_event", set_next_well) + async def set_next_well(center, event): + """Run on well event.""" + next_well_x, _ = next_well_xy(center.sample, PLATE_NAME, x_wells, y_wells) + if ( + not match_event( + event, + field_x=self.x_fields - 1, + field_y=self.y_fields - 1, + well_img_ok=True, + ) + or next_well_x is None + ): + return -def image_next_well_on_sample(center, gain_pattern, subscriptions, x_fields, y_fields): - """Image next well in existing sample.""" + await center.actions.command.stop_imaging() - async def send_cam_job(center, event): - """Run on well event.""" - next_well_x, next_well_y = next_well_xy(center.sample, PLATE_NAME) + well_x, well_y = next_well_xy(center.sample, PLATE_NAME, x_wells, y_wells) - if ( - not match_event(event, event_type="camacq_start_event") - and not match_event( - event, field_x=x_fields - 1, field_y=y_fields - 1, well_img_ok=True, + await center.actions.sample.set_well( + plate_name=PLATE_NAME, well_x=well_x, well_y=well_y ) - or next_well_x is None - ): - return - - await send_gain_jobs( - center, - gain_pattern, - next_well_x, - next_well_y, - x_fields, - y_fields, - subscriptions, - ) - - center.bus.register("camacq_start_event", send_cam_job) - center.bus.register("well_event", send_cam_job) - - -def image_next_well_on_event(center, gain_pattern, subscriptions, x_fields, y_fields): - """Image next well.""" - - async def send_cam_job(center, event): - """Run on well event.""" - if event.well.images: - return - - await send_gain_jobs( - center, - gain_pattern, - event.well.x, - event.well.y, - x_fields, - y_fields, - subscriptions, - ) - - center.bus.register("well_event", send_cam_job) - - -def analyze_gain( - center, save_path, gain_lock, x_fields, y_fields, gain_job_id, gain_job_channels -): - """Analyze gain.""" - - async def calc_gain(center, event): - """Calculate correct gain.""" - field_x, field_y = get_last_gain_coords(x_fields, y_fields) - channel_id = gain_job_channels - 1 - if not match_event( - event, - field_x=field_x, - field_y=field_y, - job_id=gain_job_id, - channel_id=channel_id, - ): - return - - # Guard against duplicate image events. - async with gain_lock: - well = center.sample.get_well( - plate_name=event.plate_name, well_x=event.well_x, well_y=event.well_y + + return self._center.bus.register("well_event", set_next_well) + + def image_next_well_on_sample(self): + """Image next well in existing sample.""" + + async def send_cam_job(center, event): + """Run on well event.""" + next_well_x, next_well_y = next_well_xy(center.sample, PLATE_NAME) + + if ( + not match_event(event, event_type="camacq_start_event") + and not match_event( + event, + field_x=self.x_fields - 1, + field_y=self.y_fields - 1, + well_img_ok=True, + ) + or next_well_x is None + ): + return + + await self.send_gain_jobs( + next_well_x, next_well_y, ) - gain_set = any( - channel.gain is not None for channel in well.channels.values() + + removes = [] + removes.append(self._center.bus.register("camacq_start_event", send_cam_job)) + removes.append(self._center.bus.register("well_event", send_cam_job)) + + def remove_callback(): + """Remove all registered listeners of this method.""" + for remove in removes: + remove() + removes.clear() + + return remove_callback + + def image_next_well_on_event(self): + """Image next well.""" + + async def send_cam_job(center, event): + """Run on well event.""" + if event.well.images: + return + + await self.send_gain_jobs( + event.well.x, event.well.y, ) - if gain_set: + + return self._center.bus.register("well_event", send_cam_job) + + def analyze_gain(self, gain_lock): + """Analyze gain.""" + + async def calc_gain(center, event): + """Calculate correct gain.""" + field_x, field_y = get_last_gain_coords(self.x_fields, self.y_fields) + channel_id = self.gain_job_channels - 1 + if not match_event( + event, + field_x=field_x, + field_y=field_y, + job_id=self.gain_job_id, + channel_id=channel_id, + ): return - await center.actions.command.stop_imaging() + # Guard against duplicate image events. + async with gain_lock: + well = center.sample.get_well( + plate_name=event.plate_name, + well_x=event.well_x, + well_y=event.well_y, + ) + gain_set = any( + channel.gain is not None for channel in well.channels.values() + ) + if gain_set: + return + + await center.actions.command.stop_imaging() + + if self.plot_save_path is None: + save_path = Path(tempfile.gettempdir()) / event.plate_name + else: + save_path = Path(self.plot_save_path) + if not save_path.exists(): + await center.add_executor_job(save_path.mkdir) + + # This should be a path to a base file name, not to a dir or file. + save_path = save_path / f"{event.well_x}--{event.well_y}" + + await center.actions.gain.calc_gain( + plate_name=event.plate_name, + well_x=event.well_x, + well_y=event.well_y, + make_plots=True, + save_path=save_path, + ) - nonlocal save_path - if save_path is None: - save_path = Path(tempfile.gettempdir()) / event.plate_name - else: - save_path = Path(save_path) - if not save_path.exists(): - await center.add_executor_job(save_path.mkdir) + return self._center.bus.register("image_event", calc_gain) - # This should be a path to a base file name, not to an actual dir or file. - save_path = save_path / f"{event.well_x}--{event.well_y}" + def set_exp_gain(self): + """Set experiment gain.""" - await center.actions.gain.calc_gain( + async def set_gain(center, event): + """Set pmt gain.""" + for channel in self.channels: + if event.channel_name != channel["channel"]: + continue + exp = channel["job_name"] + num = channel["detector_num"] + gain = min(event.gain or channel["default_gain"], channel["max_gain"]) + + command = gain_com(exp=exp, num=num, value=gain) + + # Set the gain at the microscope. + await center.actions.command.send(command=command) + # Set the gain in the sample state. + await center.actions.sample.set_channel( plate_name=event.plate_name, well_x=event.well_x, well_y=event.well_y, - make_plots=True, - save_path=save_path, + channel_name=event.channel_name, + gain=gain, ) - center.bus.register("image_event", calc_gain) - - -def set_exp_gain(center, channels): - """Set experiment gain.""" - - async def set_gain(center, event): - """Set pmt gain.""" - for channel in channels: - if event.channel_name != channel["channel"]: - continue - exp = channel["job_name"] - num = channel["detector_num"] - gain = min(event.gain or channel["default_gain"], channel["max_gain"]) - - command = gain_com(exp=exp, num=num, value=gain) - - # Set the gain at the microscope. - await center.actions.command.send(command=command) - # Set the gain in the sample state. - await center.actions.sample.set_channel( - plate_name=event.plate_name, - well_x=event.well_x, - well_y=event.well_y, - channel_name=event.channel_name, - gain=gain, - ) - - center.bus.register("gain_calc_event", set_gain) - - -def add_exp_job( - center, channels, exp_pattern, subscriptions, x_fields, y_fields, exp_job_ids -): - """Add experiment job.""" - - async def add_cam_job(center, event): - """Add an experiment job to the cam list.""" - last_channel = channels[-1] - if not match_event(event, channel_name=last_channel["channel"]) or len( - event.well.channels - ) != len(channels): - return - - commands = [] - for field_x in range(x_fields): - for field_y in range(y_fields): - cmd = cam_com( - exp_pattern, event.well_x, event.well_y, field_x, field_y, 0, 0 - ) - commands.append(cmd) - - await center.actions.command.send(command=del_com()) - await center.actions.command.send_many(commands=commands) - - if subscriptions.set_img_ok is None: - subscriptions.set_img_ok = set_img_ok(center) - if subscriptions.rename_exp_image is None: - subscriptions.rename_exp_image = rename_exp_image(center, exp_job_ids) + return self._center.bus.register("gain_calc_event", set_gain) - await center.actions.command.start_imaging() - await center.actions.command.send(command="/cmd:startcamscan") + def add_exp_job(self): + """Add experiment job.""" - center.bus.register("channel_event", add_cam_job) + async def add_cam_job(center, event): + """Add an experiment job to the cam list.""" + last_channel = self.channels[-1] + if not match_event(event, channel_name=last_channel["channel"]) or len( + event.well.channels + ) != len(self.channels): + return + commands = [] + for field_x in range(self.x_fields): + for field_y in range(self.y_fields): + cmd = cam_com( + self.exp_pattern, + event.well_x, + event.well_y, + field_x, + field_y, + 0, + 0, + ) + commands.append(cmd) + + await center.actions.command.send(command=del_com()) + await center.actions.command.send_many(commands=commands) + + if self._remove_set_img_ok is None: + self._remove_set_img_ok = self.set_img_ok() + if self._remove_rename_image is None: + self._remove_rename_image = self.rename_exp_image() + + await center.actions.command.start_imaging() + await center.actions.command.send(command="/cmd:startcamscan") + + return self._center.bus.register("channel_event", add_cam_job) + + def set_img_ok(self): + """Set field as imaged ok.""" + + async def set_sample_img_ok(center, event): + """Set sample field img ok.""" + if not match_event(event, job_id=6): + return -def set_img_ok(center): - """Set field as imaged ok.""" + await center.actions.sample.set_field( + plate_name=event.plate_name, + well_x=event.well_x, + well_y=event.well_y, + field_x=event.field_x, + field_y=event.field_y, + img_ok=True, + ) - async def set_sample_img_ok(center, event): - """Set sample field img ok.""" - if not match_event(event, job_id=6): - return + return self._center.bus.register("image_event", set_sample_img_ok) - await center.actions.sample.set_field( - plate_name=event.plate_name, - well_x=event.well_x, - well_y=event.well_y, - field_x=event.field_x, - field_y=event.field_y, - img_ok=True, - ) + def rename_exp_image(self): + """Rename an experiment image.""" - return center.bus.register("image_event", set_sample_img_ok) + async def rename_image(center, event): + """Rename an image.""" + if event.job_id not in self.exp_job_ids or event.channel_id not in (0, 1): + return + if event.job_id == self.exp_job_ids[0]: + channel_id = 0 + elif event.job_id == self.exp_job_ids[1] and event.channel_id == 0: + channel_id = 1 + elif event.job_id == self.exp_job_ids[1] and event.channel_id == 1: + channel_id = 2 + elif event.job_id == self.exp_job_ids[2]: + channel_id = 3 + + new_name = ( + f"U{event.well_x:02}--V{event.well_y:02}--E{event.job_id:02}--" + f"X{event.field_x:02}--Y{event.field_y:02}--" + f"Z{event.z_slice:02}--C{channel_id:02}.ome.tif" + ) -def rename_exp_image(center, exp_job_ids): - """Rename an experiment image.""" + await center.actions.rename_image.rename_image( + old_path=event.path, new_name=new_name + ) - async def rename_image(center, event): - """Rename an image.""" - if event.job_id not in exp_job_ids or event.channel_id not in (0, 1): - return + return self._center.bus.register("image_event", rename_image) - if event.job_id == exp_job_ids[0]: - channel_id = 0 - elif event.job_id == exp_job_ids[1] and event.channel_id == 0: - channel_id = 1 - elif event.job_id == exp_job_ids[1] and event.channel_id == 1: - channel_id = 2 - elif event.job_id == exp_job_ids[2]: - channel_id = 3 + def stop_exp(self, x_wells, y_wells): + """Trigger to stop experiment.""" - new_name = ( - f"U{event.well_x:02}--V{event.well_y:02}--E{event.job_id:02}--" - f"X{event.field_x:02}--Y{event.field_y:02}--" - f"Z{event.z_slice:02}--C{channel_id:02}.ome.tif" - ) + async def stop_imaging(center, event): + """Run to stop the experiment.""" + next_well_x, _ = next_well_xy(center.sample, PLATE_NAME, x_wells, y_wells) + match = match_event( + event, + field_x=self.x_fields - 1, + field_y=self.y_fields - 1, + well_img_ok=True, + ) - await center.actions.rename_image.rename_image( - old_path=event.path, new_name=new_name - ) + if not match or next_well_x is not None: + return - return center.bus.register("image_event", rename_image) + await center.actions.command.stop_imaging() + return self._center.bus.register("well_event", stop_imaging) -def stop_exp(center, x_wells, y_wells, x_fields, y_fields): - """Trigger to stop experiment.""" + async def send_gain_jobs(self, well_x, well_y): + """Send gain cam jobs for the center fields of a well.""" + field_x, field_y = get_last_gain_coords(self.x_fields, self.y_fields) + field_x = field_x - 1 # set the start x field coord - async def stop_imaging(center, event): - """Run to stop the experiment.""" - next_well_x, _ = next_well_xy(center.sample, PLATE_NAME, x_wells, y_wells) - match = match_event( - event, field_x=x_fields - 1, field_y=y_fields - 1, well_img_ok=True, - ) + await self._center.actions.command.send(command=del_com()) - if not match or next_well_x is not None: - return + for field_x in range(field_x, field_x + 2): + command = cam_com(self.gain_pattern, well_x, well_y, field_x, field_y, 0, 0) + await self._center.actions.command.send(command=command) - await center.actions.command.stop_imaging() + if self._remove_set_img_ok is not None: + self._remove_set_img_ok() + self._remove_set_img_ok = None + if self._remove_rename_image is not None: + self._remove_rename_image() + self._remove_rename_image = None - center.bus.register("well_event", stop_imaging) + await self._center.actions.command.start_imaging() + await self._center.actions.command.send(command="/cmd:startcamscan") def get_last_gain_coords(x_fields, y_fields): @@ -385,27 +407,3 @@ def get_last_gain_coords(x_fields, y_fields): last_x_field = ceil(x_fields / 2) last_y_field = ceil(y_fields / 2) - 1 return last_x_field, last_y_field - - -async def send_gain_jobs( - center, gain_job, well_x, well_y, x_fields, y_fields, subscriptions -): - """Send gain cam jobs for the center fields of a well.""" - field_x, field_y = get_last_gain_coords(x_fields, y_fields) - field_x = field_x - 1 # set the start x field coord - - await center.actions.command.send(command=del_com()) - - for field_x in range(field_x, field_x + 2): - command = cam_com(gain_job, well_x, well_y, field_x, field_y, 0, 0) - await center.actions.command.send(command=command) - - if subscriptions.set_img_ok is not None: - subscriptions.set_img_ok() - subscriptions.set_img_ok = None - if subscriptions.rename_exp_image is not None: - subscriptions.rename_exp_image() - subscriptions.rename_exp_image = None - - await center.actions.command.start_imaging() - await center.actions.command.send(command="/cmd:startcamscan") diff --git a/pylintrc b/pylintrc index 29a954e..199fca4 100644 --- a/pylintrc +++ b/pylintrc @@ -1,9 +1,6 @@ [MASTER] reports=no -max-args=7 -max-locals=20 - # Black needs to disable wrong-hanging-indent. disable= From 4450b523ebaa3e24a69d2b7f6c7438dcd5d35bd7 Mon Sep 17 00:00:00 2001 From: Martin Hjelmare Date: Sun, 1 Dec 2019 02:11:02 +0100 Subject: [PATCH 49/76] Use config for last job id --- camacqplugins/production/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/camacqplugins/production/__init__.py b/camacqplugins/production/__init__.py index 23319a0..01b6be9 100644 --- a/camacqplugins/production/__init__.py +++ b/camacqplugins/production/__init__.py @@ -314,7 +314,7 @@ def set_img_ok(self): async def set_sample_img_ok(center, event): """Set sample field img ok.""" - if not match_event(event, job_id=6): + if not match_event(event, job_id=self.exp_job_ids[-1]): return await center.actions.sample.set_field( From 780b519fd5a78cba207a8b02a5ca5666ace09cf1 Mon Sep 17 00:00:00 2001 From: Martin Hjelmare Date: Sun, 1 Dec 2019 22:26:13 +0100 Subject: [PATCH 50/76] Add first production test --- requirements_test.txt | 6 ++ tests/conftest.py | 12 ++++ tests/production/test_production.py | 108 ++++++++++++++++++++++++++++ tox.ini | 9 ++- 4 files changed, 132 insertions(+), 3 deletions(-) create mode 100644 requirements_test.txt create mode 100644 tests/conftest.py create mode 100644 tests/production/test_production.py diff --git a/requirements_test.txt b/requirements_test.txt new file mode 100644 index 0000000..3bc9366 --- /dev/null +++ b/requirements_test.txt @@ -0,0 +1,6 @@ +asynctest==0.13.0 +pytest==5.3.1 +pytest-asyncio==0.10.0 +pytest-cov==2.8.1 +pytest-mock==1.12.1 +pytest-timeout==1.3.3 diff --git a/tests/conftest.py b/tests/conftest.py new file mode 100644 index 0000000..3ae48e2 --- /dev/null +++ b/tests/conftest.py @@ -0,0 +1,12 @@ +"""Provide package level pytest fixtures.""" +import pytest + +from camacq.control import Center + + +@pytest.fixture +def center(event_loop): + """Give access to center via fixture.""" + _center = Center(loop=event_loop) + _center._track_tasks = True # pylint: disable=protected-access + yield _center diff --git a/tests/production/test_production.py b/tests/production/test_production.py new file mode 100644 index 0000000..6c9294e --- /dev/null +++ b/tests/production/test_production.py @@ -0,0 +1,108 @@ +"""Test the production plugin.""" +from asynctest import patch +import pytest +from ruamel.yaml import YAML + +from camacq import plugins +from camacq.plugins.api import ImageEvent + +# All test coroutines will be treated as marked. +pytestmark = pytest.mark.asyncio # pylint: disable=invalid-name + +CONFIG = """ +production: + gain_pattern_name: p10xgain + gain_job_id: 3 + gain_job_channels: 32 + exp_pattern_name: p10xexp + exp_job_ids: + - 3 + - 4 + - 6 + channels: + - channel: green + job_name: green10x + detector_num: 1 + default_gain: 800 + max_gain: 800 + - channel: blue + job_name: blue10x + detector_num: 1 + default_gain: 800 + max_gain: 800 + - channel: yellow + job_name: blue10x + detector_num: 2 + default_gain: 695 + max_gain: 800 + - channel: red + job_name: red10x + detector_num: 2 + default_gain: 700 + max_gain: 800 + well_layout: + x_fields: 2 + y_fields: 3 +gain: + channels: + - channel: green + init_gain: [450, 495, 540, 585, 630, 675, 720, 765, 810, 855, 900] + - channel: blue + # 63x + #init_gain: [750, 730, 765, 800, 835, 870, 905] + # 10x + init_gain: [700, 735, 770, 805, 840, 875, 910] + - channel: yellow + # 63x + #init_gain: [550, 585, 620, 655, 690, 725, 760] + # 10x + init_gain: [700, 735, 770, 805, 840, 875, 910] + - channel: red + # 63x + #init_gain: [525, 560, 595, 630, 665, 700, 735] + # 10x + init_gain: [600, 635, 670, 705, 740, 775, 810] +""" + + +@pytest.fixture(name="calc_gain") +def calc_gain_fixture(): + """Mock calc_gain plugin function.""" + with patch("camacq.plugins.gain.calc_gain") as mock_gain, patch( + "camacq.plugins.gain.make_proj" + ): + yield mock_gain + + +class WorkflowImageEvent(ImageEvent): + """Represent a test image event.""" + + event_type = "workflow_image_event" + + @property + def job_id(self): + """:int: Return job id of the image.""" + return self.data.get("job_id") + + +async def test_duplicate_image_events(center, calc_gain): + """Test duplicate image events.""" + config = YAML(typ="safe").load(CONFIG) + await plugins.setup_module(center, config) + + event = WorkflowImageEvent( + { + "path": "test_path", + "plate_name": "00", + "well_x": 0, + "well_y": 0, + "field_x": 1, + "field_y": 1, + "job_id": 3, + "channel_id": 31, + } + ) + await center.bus.notify(event) + await center.wait_for() + + assert calc_gain.call_count == 1 diff --git a/tox.ini b/tox.ini index 0212152..052b4a5 100644 --- a/tox.ini +++ b/tox.ini @@ -1,6 +1,9 @@ [tox] -envlist=py36, py37 +envlist = py36, py37 [testenv] -commands=pytest -deps=pytest +commands = + pytest --timeout=30 --cov=camacqplugins --cov-report= {posargs} +deps = + -rrequirements.txt + -rrequirements_test.txt From 0009f5e02af544c08597f9818731597aeb321822 Mon Sep 17 00:00:00 2001 From: Martin Hjelmare Date: Sun, 1 Dec 2019 22:47:43 +0100 Subject: [PATCH 51/76] Extend test --- tests/production/test_production.py | 45 ++++++++++++++++++++++------- 1 file changed, 34 insertions(+), 11 deletions(-) diff --git a/tests/production/test_production.py b/tests/production/test_production.py index 6c9294e..dd1b678 100644 --- a/tests/production/test_production.py +++ b/tests/production/test_production.py @@ -1,4 +1,8 @@ """Test the production plugin.""" +import tempfile +from pathlib import Path +from unittest.mock import call + from asynctest import patch import pytest from ruamel.yaml import YAML @@ -68,12 +72,17 @@ @pytest.fixture(name="calc_gain") def calc_gain_fixture(): """Mock calc_gain plugin function.""" - with patch("camacq.plugins.gain.calc_gain") as mock_gain, patch( - "camacq.plugins.gain.make_proj" - ): + with patch("camacq.plugins.gain.calc_gain") as mock_gain: yield mock_gain +@pytest.fixture(name="make_proj") +def make_proj_fixture(): + """Mock make_proj image function.""" + with patch("camacq.plugins.gain.make_proj") as mock_proj: + yield mock_proj + + class WorkflowImageEvent(ImageEvent): """Represent a test image event.""" @@ -85,24 +94,38 @@ def job_id(self): return self.data.get("job_id") -async def test_duplicate_image_events(center, calc_gain): +async def test_duplicate_image_events(center, calc_gain, make_proj): """Test duplicate image events.""" config = YAML(typ="safe").load(CONFIG) await plugins.setup_module(center, config) + plate_name = "00" + well_x = 0 + well_y = 0 + field_x = 1 + field_y = 1 + job_id = 3 + channel_id = 31 + save_path = Path(tempfile.gettempdir()) / plate_name + save_path = save_path / f"{well_x}--{well_y}" + test_projs = ["test"] + make_proj.return_value = test_projs event = WorkflowImageEvent( { "path": "test_path", - "plate_name": "00", - "well_x": 0, - "well_y": 0, - "field_x": 1, - "field_y": 1, - "job_id": 3, - "channel_id": 31, + "plate_name": plate_name, + "well_x": well_x, + "well_y": well_y, + "field_x": field_x, + "field_y": field_y, + "job_id": job_id, + "channel_id": channel_id, } ) await center.bus.notify(event) await center.wait_for() assert calc_gain.call_count == 1 + assert calc_gain.call_args == call( + center, config, plate_name, well_x, well_y, test_projs, True, str(save_path), + ) From ec6b4e74e19a733f9eb5f89ff8701d64bbaefe50 Mon Sep 17 00:00:00 2001 From: Martin Hjelmare Date: Sun, 1 Dec 2019 23:43:53 +0100 Subject: [PATCH 52/76] Test duplicate image events --- tests/production/test_production.py | 81 +++++++++++++++-------------- 1 file changed, 42 insertions(+), 39 deletions(-) diff --git a/tests/production/test_production.py b/tests/production/test_production.py index dd1b678..47d3794 100644 --- a/tests/production/test_production.py +++ b/tests/production/test_production.py @@ -3,12 +3,14 @@ from pathlib import Path from unittest.mock import call -from asynctest import patch import pytest +import voluptuous as vol +from asynctest import CoroutineMock from ruamel.yaml import YAML from camacq import plugins from camacq.plugins.api import ImageEvent +from camacq.plugins.gain import GainCalcEvent # All test coroutines will be treated as marked. pytestmark = pytest.mark.asyncio # pylint: disable=invalid-name @@ -47,42 +49,9 @@ well_layout: x_fields: 2 y_fields: 3 -gain: - channels: - - channel: green - init_gain: [450, 495, 540, 585, 630, 675, 720, 765, 810, 855, 900] - - channel: blue - # 63x - #init_gain: [750, 730, 765, 800, 835, 870, 905] - # 10x - init_gain: [700, 735, 770, 805, 840, 875, 910] - - channel: yellow - # 63x - #init_gain: [550, 585, 620, 655, 690, 725, 760] - # 10x - init_gain: [700, 735, 770, 805, 840, 875, 910] - - channel: red - # 63x - #init_gain: [525, 560, 595, 630, 665, 700, 735] - # 10x - init_gain: [600, 635, 670, 705, 740, 775, 810] """ -@pytest.fixture(name="calc_gain") -def calc_gain_fixture(): - """Mock calc_gain plugin function.""" - with patch("camacq.plugins.gain.calc_gain") as mock_gain: - yield mock_gain - - -@pytest.fixture(name="make_proj") -def make_proj_fixture(): - """Mock make_proj image function.""" - with patch("camacq.plugins.gain.make_proj") as mock_proj: - yield mock_proj - - class WorkflowImageEvent(ImageEvent): """Represent a test image event.""" @@ -94,7 +63,7 @@ def job_id(self): return self.data.get("job_id") -async def test_duplicate_image_events(center, calc_gain, make_proj): +async def test_duplicate_image_events(center): """Test duplicate image events.""" config = YAML(typ="safe").load(CONFIG) await plugins.setup_module(center, config) @@ -107,8 +76,36 @@ async def test_duplicate_image_events(center, calc_gain, make_proj): channel_id = 31 save_path = Path(tempfile.gettempdir()) / plate_name save_path = save_path / f"{well_x}--{well_y}" - test_projs = ["test"] - make_proj.return_value = test_projs + calc_gain = CoroutineMock() + + async def fire_gain_event(**kwargs): + """Fire gain event.""" + well_x = kwargs.get("well_x") + well_y = kwargs.get("well_y") + plate_name = kwargs.get("plate_name") + gains = { + "green": 800, + "blue": 700, + "yellow": 600, + "red": 500, + } + for channel_name, gain in gains.items(): + event = GainCalcEvent( + { + "plate_name": plate_name, + "well_x": well_x, + "well_y": well_y, + "channel_name": channel_name, + "gain": gain, + } + ) + await center.bus.notify(event) + + calc_gain.side_effect = fire_gain_event + + center.actions.register( + "gain", "calc_gain", calc_gain, vol.Schema({}, extra=vol.ALLOW_EXTRA) + ) event = WorkflowImageEvent( { @@ -122,10 +119,16 @@ async def test_duplicate_image_events(center, calc_gain, make_proj): "channel_id": channel_id, } ) - await center.bus.notify(event) + center.create_task(center.bus.notify(event)) + center.create_task(center.bus.notify(event)) await center.wait_for() assert calc_gain.call_count == 1 assert calc_gain.call_args == call( - center, config, plate_name, well_x, well_y, test_projs, True, str(save_path), + action_id="calc_gain", + plate_name=plate_name, + well_x=well_x, + well_y=well_y, + make_plots=True, + save_path=save_path, ) From 0207cae47e8d25adeddc49d297b666e4af70a8ca Mon Sep 17 00:00:00 2001 From: Martin Hjelmare Date: Sun, 1 Dec 2019 23:55:33 +0100 Subject: [PATCH 53/76] Use next and generator expression --- camacqplugins/production/__init__.py | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/camacqplugins/production/__init__.py b/camacqplugins/production/__init__.py index 01b6be9..839805b 100644 --- a/camacqplugins/production/__init__.py +++ b/camacqplugins/production/__init__.py @@ -249,12 +249,16 @@ def set_exp_gain(self): async def set_gain(center, event): """Set pmt gain.""" - for channel in self.channels: - if event.channel_name != channel["channel"]: - continue - exp = channel["job_name"] - num = channel["detector_num"] - gain = min(event.gain or channel["default_gain"], channel["max_gain"]) + channel = next( + ( + channel + for channel in self.channels + if event.channel_name == channel["channel"] + ) + ) + exp = channel["job_name"] + num = channel["detector_num"] + gain = min(event.gain or channel["default_gain"], channel["max_gain"]) command = gain_com(exp=exp, num=num, value=gain) From 907d4d091733da77d02e9af4dc1477d70f18bba8 Mon Sep 17 00:00:00 2001 From: Martin Hjelmare Date: Mon, 2 Dec 2019 00:11:26 +0100 Subject: [PATCH 54/76] Clean up test --- tests/production/test_production.py | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/tests/production/test_production.py b/tests/production/test_production.py index 47d3794..f59dd13 100644 --- a/tests/production/test_production.py +++ b/tests/production/test_production.py @@ -70,25 +70,22 @@ async def test_duplicate_image_events(center): plate_name = "00" well_x = 0 well_y = 0 - field_x = 1 - field_y = 1 - job_id = 3 - channel_id = 31 save_path = Path(tempfile.gettempdir()) / plate_name save_path = save_path / f"{well_x}--{well_y}" calc_gain = CoroutineMock() + gains = { + "green": 800, + "blue": 700, + "yellow": 600, + "red": 500, + } async def fire_gain_event(**kwargs): """Fire gain event.""" well_x = kwargs.get("well_x") well_y = kwargs.get("well_y") plate_name = kwargs.get("plate_name") - gains = { - "green": 800, - "blue": 700, - "yellow": 600, - "red": 500, - } + for channel_name, gain in gains.items(): event = GainCalcEvent( { @@ -113,10 +110,10 @@ async def fire_gain_event(**kwargs): "plate_name": plate_name, "well_x": well_x, "well_y": well_y, - "field_x": field_x, - "field_y": field_y, - "job_id": job_id, - "channel_id": channel_id, + "field_x": 1, + "field_y": 1, + "job_id": 3, + "channel_id": 31, } ) center.create_task(center.bus.notify(event)) @@ -132,3 +129,6 @@ async def fire_gain_event(**kwargs): make_plots=True, save_path=save_path, ) + for channel_name, gain in gains.items(): + channel = center.sample.get_channel(plate_name, well_x, well_y, channel_name) + assert channel.gain == gain From 5d7546cd3f84d285c8f8049644966f2b97000232 Mon Sep 17 00:00:00 2001 From: Martin Hjelmare Date: Mon, 2 Dec 2019 01:13:17 +0100 Subject: [PATCH 55/76] Add load sample test --- camacqplugins/production/__init__.py | 8 ++++++-- tests/production/test_production.py | 22 ++++++++++++++++++++++ 2 files changed, 28 insertions(+), 2 deletions(-) diff --git a/camacqplugins/production/__init__.py b/camacqplugins/production/__init__.py index 839805b..16b3b61 100644 --- a/camacqplugins/production/__init__.py +++ b/camacqplugins/production/__init__.py @@ -100,8 +100,12 @@ async def load_sample(self, state_file): """Load sample state from file.""" state_data = await self._center.add_executor_job(read_csv, state_file) for data in state_data: - await self._center.actions.sample.set_plate(silent=True, **data) - await self._center.actions.sample.set_well(silent=True, **data) + if data["plate_name"] not in self._center.sample.plates: + await self._center.actions.sample.set_plate(silent=True, **data) + if (data["well_x"], data["well_y"]) not in self._center.sample.plates[ + data["plate_name"] + ].wells: + await self._center.actions.sample.set_well(silent=True, **data) await self._center.actions.sample.set_channel(silent=True, **data) await self._center.actions.sample.set_field(silent=True, **data) diff --git a/tests/production/test_production.py b/tests/production/test_production.py index f59dd13..9764897 100644 --- a/tests/production/test_production.py +++ b/tests/production/test_production.py @@ -51,6 +51,14 @@ y_fields: 3 """ +SAMPLE_STATE = """ +plate_name,well_x,well_y,channel_name,gain +00,0,0 +00,0,1 +00,1,0 +00,1,1 +""".strip() + class WorkflowImageEvent(ImageEvent): """Represent a test image event.""" @@ -132,3 +140,17 @@ async def fire_gain_event(**kwargs): for channel_name, gain in gains.items(): channel = center.sample.get_channel(plate_name, well_x, well_y, channel_name) assert channel.gain == gain + + +async def test_load_sample(center, tmp_path): + """Test loading sample state from file.""" + state_file = tmp_path / "state_file.csv" + state_file.write_text(SAMPLE_STATE) + config = YAML(typ="safe").load(CONFIG) + config["production"]["state_file"] = str(state_file) + plate_name = "00" + await plugins.setup_module(center, config) + await center.wait_for() + + plate = center.sample.get_plate(plate_name) + assert len(plate.wells) == 4 From 2ec2dd87535feaebaa4b45f816e1ec0087847cd7 Mon Sep 17 00:00:00 2001 From: Martin Hjelmare Date: Mon, 2 Dec 2019 01:26:54 +0100 Subject: [PATCH 56/76] Clean up test --- tests/production/test_production.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/tests/production/test_production.py b/tests/production/test_production.py index 9764897..6c47d3b 100644 --- a/tests/production/test_production.py +++ b/tests/production/test_production.py @@ -1,6 +1,4 @@ """Test the production plugin.""" -import tempfile -from pathlib import Path from unittest.mock import call import pytest @@ -71,14 +69,16 @@ def job_id(self): return self.data.get("job_id") -async def test_duplicate_image_events(center): +async def test_duplicate_image_events(center, tmp_path): """Test duplicate image events.""" config = YAML(typ="safe").load(CONFIG) - await plugins.setup_module(center, config) plate_name = "00" well_x = 0 well_y = 0 - save_path = Path(tempfile.gettempdir()) / plate_name + save_path = tmp_path / plate_name + await center.add_executor_job(save_path.mkdir) + config["production"]["plot_save_path"] = save_path + await plugins.setup_module(center, config) save_path = save_path / f"{well_x}--{well_y}" calc_gain = CoroutineMock() gains = { From 587190ddf7868cfa424c40e0647e2c0eda0e0b02 Mon Sep 17 00:00:00 2001 From: Martin Hjelmare Date: Mon, 2 Dec 2019 01:41:22 +0100 Subject: [PATCH 57/76] Fix well check --- camacqplugins/production/__init__.py | 3 ++- tests/production/test_production.py | 7 +++++-- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/camacqplugins/production/__init__.py b/camacqplugins/production/__init__.py index 16b3b61..312eecd 100644 --- a/camacqplugins/production/__init__.py +++ b/camacqplugins/production/__init__.py @@ -102,7 +102,8 @@ async def load_sample(self, state_file): for data in state_data: if data["plate_name"] not in self._center.sample.plates: await self._center.actions.sample.set_plate(silent=True, **data) - if (data["well_x"], data["well_y"]) not in self._center.sample.plates[ + well_coord = int(data["well_x"]), int(data["well_y"]) + if well_coord not in self._center.sample.plates[ data["plate_name"] ].wells: await self._center.actions.sample.set_well(silent=True, **data) diff --git a/tests/production/test_production.py b/tests/production/test_production.py index 6c47d3b..5e148b9 100644 --- a/tests/production/test_production.py +++ b/tests/production/test_production.py @@ -50,8 +50,9 @@ """ SAMPLE_STATE = """ -plate_name,well_x,well_y,channel_name,gain -00,0,0 +plate_name,well_x,well_y,channel_name,gain,field_x,field_y +00,0,0,,,0,0 +00,0,0,,,0,1 00,0,1 00,1,0 00,1,1 @@ -154,3 +155,5 @@ async def test_load_sample(center, tmp_path): plate = center.sample.get_plate(plate_name) assert len(plate.wells) == 4 + well = center.sample.get_well(plate_name, 0, 0) + assert len(well.fields) == 2 From e56075bb09c6a7984658665e102cbdf7427cccad Mon Sep 17 00:00:00 2001 From: Martin Hjelmare Date: Fri, 6 Dec 2019 17:42:10 +0100 Subject: [PATCH 58/76] Add missing stop command on next sample well --- camacqplugins/production/__init__.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/camacqplugins/production/__init__.py b/camacqplugins/production/__init__.py index 312eecd..8e49ef5 100644 --- a/camacqplugins/production/__init__.py +++ b/camacqplugins/production/__init__.py @@ -103,9 +103,7 @@ async def load_sample(self, state_file): if data["plate_name"] not in self._center.sample.plates: await self._center.actions.sample.set_plate(silent=True, **data) well_coord = int(data["well_x"]), int(data["well_y"]) - if well_coord not in self._center.sample.plates[ - data["plate_name"] - ].wells: + if well_coord not in self._center.sample.plates[data["plate_name"]].wells: await self._center.actions.sample.set_well(silent=True, **data) await self._center.actions.sample.set_channel(silent=True, **data) await self._center.actions.sample.set_field(silent=True, **data) @@ -168,6 +166,7 @@ async def send_cam_job(center, event): ): return + await center.actions.command.stop_imaging() await self.send_gain_jobs( next_well_x, next_well_y, ) From 47187b7617d6c3b9b22430a300afff844974989a Mon Sep 17 00:00:00 2001 From: Martin Hjelmare Date: Fri, 6 Dec 2019 18:19:54 +0100 Subject: [PATCH 59/76] Do not stop imaging on first well --- camacqplugins/production/__init__.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/camacqplugins/production/__init__.py b/camacqplugins/production/__init__.py index 8e49ef5..8d86eec 100644 --- a/camacqplugins/production/__init__.py +++ b/camacqplugins/production/__init__.py @@ -166,7 +166,8 @@ async def send_cam_job(center, event): ): return - await center.actions.command.stop_imaging() + if center.sample.images: + await center.actions.command.stop_imaging() await self.send_gain_jobs( next_well_x, next_well_y, ) From 2dc5fdfea64dfc9befa38728e37185a50966445d Mon Sep 17 00:00:00 2001 From: Martin Hjelmare Date: Fri, 6 Dec 2019 18:21:33 +0100 Subject: [PATCH 60/76] Add log message on experiment stop --- camacqplugins/production/__init__.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/camacqplugins/production/__init__.py b/camacqplugins/production/__init__.py index 8d86eec..790b2df 100644 --- a/camacqplugins/production/__init__.py +++ b/camacqplugins/production/__init__.py @@ -384,6 +384,8 @@ async def stop_imaging(center, event): await center.actions.command.stop_imaging() + _LOGGER.info("Congratulations, experiment is finished!") + return self._center.bus.register("well_event", stop_imaging) async def send_gain_jobs(self, well_x, well_y): From 5b384864b0107371b790b7d5d02f91c970512d48 Mon Sep 17 00:00:00 2001 From: Martin Hjelmare Date: Fri, 6 Dec 2019 18:24:00 +0100 Subject: [PATCH 61/76] Remove not needed lock --- camacqplugins/production/__init__.py | 52 ++++++++++------------------ 1 file changed, 19 insertions(+), 33 deletions(-) diff --git a/camacqplugins/production/__init__.py b/camacqplugins/production/__init__.py index 790b2df..ed6011d 100644 --- a/camacqplugins/production/__init__.py +++ b/camacqplugins/production/__init__.py @@ -1,5 +1,4 @@ """Provide a plugin for production standard flow.""" -import asyncio import logging import tempfile from math import ceil @@ -89,7 +88,7 @@ async def setup(self, state_file): self.add_next_well(x_wells, y_wells) self.image_next_well_on_event() - self.analyze_gain(asyncio.Lock()) + self.analyze_gain() self.set_exp_gain() self.add_exp_job() self._remove_set_img_ok = self.set_img_ok() @@ -198,7 +197,7 @@ async def send_cam_job(center, event): return self._center.bus.register("well_event", send_cam_job) - def analyze_gain(self, gain_lock): + def analyze_gain(self): """Analyze gain.""" async def calc_gain(center, event): @@ -214,38 +213,25 @@ async def calc_gain(center, event): ): return - # Guard against duplicate image events. - async with gain_lock: - well = center.sample.get_well( - plate_name=event.plate_name, - well_x=event.well_x, - well_y=event.well_y, - ) - gain_set = any( - channel.gain is not None for channel in well.channels.values() - ) - if gain_set: - return + await center.actions.command.stop_imaging() - await center.actions.command.stop_imaging() + if self.plot_save_path is None: + save_path = Path(tempfile.gettempdir()) / event.plate_name + else: + save_path = Path(self.plot_save_path) + if not save_path.exists(): + await center.add_executor_job(save_path.mkdir) - if self.plot_save_path is None: - save_path = Path(tempfile.gettempdir()) / event.plate_name - else: - save_path = Path(self.plot_save_path) - if not save_path.exists(): - await center.add_executor_job(save_path.mkdir) - - # This should be a path to a base file name, not to a dir or file. - save_path = save_path / f"{event.well_x}--{event.well_y}" - - await center.actions.gain.calc_gain( - plate_name=event.plate_name, - well_x=event.well_x, - well_y=event.well_y, - make_plots=True, - save_path=save_path, - ) + # This should be a path to a base file name, not to a dir or file. + save_path = save_path / f"{event.well_x}--{event.well_y}" + + await center.actions.gain.calc_gain( + plate_name=event.plate_name, + well_x=event.well_x, + well_y=event.well_y, + make_plots=True, + save_path=save_path, + ) return self._center.bus.register("image_event", calc_gain) From 5df05a3cb1b01c9354899df6a5b080fd5fcb69b0 Mon Sep 17 00:00:00 2001 From: Martin Hjelmare Date: Fri, 6 Dec 2019 19:21:55 +0100 Subject: [PATCH 62/76] Fix rename of last field before next well --- camacqplugins/production/__init__.py | 111 +++++++++++++-------------- 1 file changed, 52 insertions(+), 59 deletions(-) diff --git a/camacqplugins/production/__init__.py b/camacqplugins/production/__init__.py index ed6011d..fb6e84f 100644 --- a/camacqplugins/production/__init__.py +++ b/camacqplugins/production/__init__.py @@ -71,8 +71,7 @@ def __init__(self, center, conf): self.x_fields = well_layout["x_fields"] self.y_fields = well_layout["y_fields"] self.plot_save_path = conf.get("plot_save_path") - self._remove_set_img_ok = None - self._remove_rename_image = None + self._remove_handle_exp_image = None async def setup(self, state_file): """Set up the flow.""" @@ -91,8 +90,7 @@ async def setup(self, state_file): self.analyze_gain() self.set_exp_gain() self.add_exp_job() - self._remove_set_img_ok = self.set_img_ok() - self._remove_rename_image = self.rename_exp_image() + self._remove_handle_exp_image = self.handle_exp_image() self.stop_exp(x_wells, y_wells) async def load_sample(self, state_file): @@ -294,63 +292,23 @@ async def add_cam_job(center, event): await center.actions.command.send(command=del_com()) await center.actions.command.send_many(commands=commands) - if self._remove_set_img_ok is None: - self._remove_set_img_ok = self.set_img_ok() - if self._remove_rename_image is None: - self._remove_rename_image = self.rename_exp_image() + if self._remove_handle_exp_image is None: + self._remove_handle_exp_image = self.handle_exp_image() await center.actions.command.start_imaging() await center.actions.command.send(command="/cmd:startcamscan") return self._center.bus.register("channel_event", add_cam_job) - def set_img_ok(self): - """Set field as imaged ok.""" + def handle_exp_image(self): + """Handle experiment image.""" - async def set_sample_img_ok(center, event): - """Set sample field img ok.""" - if not match_event(event, job_id=self.exp_job_ids[-1]): - return - - await center.actions.sample.set_field( - plate_name=event.plate_name, - well_x=event.well_x, - well_y=event.well_y, - field_x=event.field_x, - field_y=event.field_y, - img_ok=True, - ) + async def on_exp_image(center, event): + """Run on experiment image event.""" + await self.rename_image(center, event) + await self.set_sample_img_ok(center, event) - return self._center.bus.register("image_event", set_sample_img_ok) - - def rename_exp_image(self): - """Rename an experiment image.""" - - async def rename_image(center, event): - """Rename an image.""" - if event.job_id not in self.exp_job_ids or event.channel_id not in (0, 1): - return - - if event.job_id == self.exp_job_ids[0]: - channel_id = 0 - elif event.job_id == self.exp_job_ids[1] and event.channel_id == 0: - channel_id = 1 - elif event.job_id == self.exp_job_ids[1] and event.channel_id == 1: - channel_id = 2 - elif event.job_id == self.exp_job_ids[2]: - channel_id = 3 - - new_name = ( - f"U{event.well_x:02}--V{event.well_y:02}--E{event.job_id:02}--" - f"X{event.field_x:02}--Y{event.field_y:02}--" - f"Z{event.z_slice:02}--C{channel_id:02}.ome.tif" - ) - - await center.actions.rename_image.rename_image( - old_path=event.path, new_name=new_name - ) - - return self._center.bus.register("image_event", rename_image) + return self._center.bus.register("image_event", on_exp_image) def stop_exp(self, x_wells, y_wells): """Trigger to stop experiment.""" @@ -385,16 +343,51 @@ async def send_gain_jobs(self, well_x, well_y): command = cam_com(self.gain_pattern, well_x, well_y, field_x, field_y, 0, 0) await self._center.actions.command.send(command=command) - if self._remove_set_img_ok is not None: - self._remove_set_img_ok() - self._remove_set_img_ok = None - if self._remove_rename_image is not None: - self._remove_rename_image() - self._remove_rename_image = None + if self._remove_handle_exp_image is not None: + self._remove_handle_exp_image() + self._remove_handle_exp_image = None await self._center.actions.command.start_imaging() await self._center.actions.command.send(command="/cmd:startcamscan") + async def rename_image(self, center, event): + """Rename an image.""" + if event.job_id not in self.exp_job_ids or event.channel_id not in (0, 1): + return + + if event.job_id == self.exp_job_ids[0]: + channel_id = 0 + elif event.job_id == self.exp_job_ids[1] and event.channel_id == 0: + channel_id = 1 + elif event.job_id == self.exp_job_ids[1] and event.channel_id == 1: + channel_id = 2 + elif event.job_id == self.exp_job_ids[2]: + channel_id = 3 + + new_name = ( + f"U{event.well_x:02}--V{event.well_y:02}--E{event.job_id:02}--" + f"X{event.field_x:02}--Y{event.field_y:02}--" + f"Z{event.z_slice:02}--C{channel_id:02}.ome.tif" + ) + + await center.actions.rename_image.rename_image( + old_path=event.path, new_name=new_name + ) + + async def set_sample_img_ok(self, center, event): + """Set sample field img ok.""" + if not match_event(event, job_id=self.exp_job_ids[-1]): + return + + await center.actions.sample.set_field( + plate_name=event.plate_name, + well_x=event.well_x, + well_y=event.well_y, + field_x=event.field_x, + field_y=event.field_y, + img_ok=True, + ) + def get_last_gain_coords(x_fields, y_fields): """Return a tuple with last gain coordinates x and y. From 666f5102ba3a3494d6e8ed12f822f8f72abd083c Mon Sep 17 00:00:00 2001 From: Martin Hjelmare Date: Fri, 6 Dec 2019 20:54:26 +0100 Subject: [PATCH 63/76] Fix production config template indent --- config_templates/production.yml | 40 ++++++++++++++++----------------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/config_templates/production.yml b/config_templates/production.yml index 01dcf70..4ecdd38 100644 --- a/config_templates/production.yml +++ b/config_templates/production.yml @@ -34,26 +34,26 @@ production: y_fields: 3 #plot_save_path: "/path/to/gains/dir/00/" - gain: - channels: - - channel: green - init_gain: [450, 495, 540, 585, 630, 675, 720, 765, 810, 855, 900] - - channel: blue - # 63x - #init_gain: [750, 730, 765, 800, 835, 870, 905] - # 10x - init_gain: [700, 735, 770, 805, 840, 875, 910] - - channel: yellow - # 63x - #init_gain: [550, 585, 620, 655, 690, 725, 760] - # 10x - init_gain: [700, 735, 770, 805, 840, 875, 910] - - channel: red - # 63x - #init_gain: [525, 560, 595, 630, 665, 700, 735] - # 10x - init_gain: [600, 635, 670, 705, 740, 775, 810] - #save_dir: "/path/to/gains/dir" +gain: + channels: + - channel: green + init_gain: [450, 495, 540, 585, 630, 675, 720, 765, 810, 855, 900] + - channel: blue + # 63x + #init_gain: [750, 730, 765, 800, 835, 870, 905] + # 10x + init_gain: [700, 735, 770, 805, 840, 875, 910] + - channel: yellow + # 63x + #init_gain: [550, 585, 620, 655, 690, 725, 760] + # 10x + init_gain: [700, 735, 770, 805, 840, 875, 910] + - channel: red + # 63x + #init_gain: [525, 560, 595, 630, 665, 700, 735] + # 10x + init_gain: [600, 635, 670, 705, 740, 775, 810] + #save_dir: "/path/to/gains/dir" rename_image: From 52988fc97c89ac1932e1551b0edad06f65e5d61b Mon Sep 17 00:00:00 2001 From: Martin Hjelmare Date: Sat, 7 Dec 2019 13:35:49 +0100 Subject: [PATCH 64/76] Fix stop on wells left --- camacqplugins/production/__init__.py | 27 ++++++++++++++++++--------- 1 file changed, 18 insertions(+), 9 deletions(-) diff --git a/camacqplugins/production/__init__.py b/camacqplugins/production/__init__.py index fb6e84f..dddc160 100644 --- a/camacqplugins/production/__init__.py +++ b/camacqplugins/production/__init__.py @@ -72,6 +72,7 @@ def __init__(self, center, conf): self.y_fields = well_layout["y_fields"] self.plot_save_path = conf.get("plot_save_path") self._remove_handle_exp_image = None + self.wells_left = set() async def setup(self, state_file): """Set up the flow.""" @@ -83,6 +84,11 @@ async def setup(self, state_file): else: x_wells = 12 y_wells = 8 + self.wells_left = { + (well_x, well_y) + for well_x in range(x_wells) + for well_y in range(y_wells) + } self.start_exp() self.add_next_well(x_wells, y_wells) self.image_next_well_on_event() @@ -91,11 +97,12 @@ async def setup(self, state_file): self.set_exp_gain() self.add_exp_job() self._remove_handle_exp_image = self.handle_exp_image() - self.stop_exp(x_wells, y_wells) + self.stop_exp() async def load_sample(self, state_file): """Load sample state from file.""" state_data = await self._center.add_executor_job(read_csv, state_file) + self.wells_left = {(data["well_x"], data["well_y"]) for data in state_data} for data in state_data: if data["plate_name"] not in self._center.sample.plates: await self._center.actions.sample.set_plate(silent=True, **data) @@ -121,7 +128,9 @@ def add_next_well(self, x_wells, y_wells): async def set_next_well(center, event): """Run on well event.""" - next_well_x, _ = next_well_xy(center.sample, PLATE_NAME, x_wells, y_wells) + next_well_x, next_well_y = next_well_xy( + center.sample, PLATE_NAME, x_wells, y_wells + ) if ( not match_event( @@ -131,16 +140,15 @@ async def set_next_well(center, event): well_img_ok=True, ) or next_well_x is None + or (next_well_x, next_well_y) not in self.wells_left ): return await center.actions.command.stop_imaging() - - well_x, well_y = next_well_xy(center.sample, PLATE_NAME, x_wells, y_wells) - await center.actions.sample.set_well( - plate_name=PLATE_NAME, well_x=well_x, well_y=well_y + plate_name=PLATE_NAME, well_x=next_well_x, well_y=next_well_y ) + self.wells_left.pop((next_well_x, next_well_y)) return self._center.bus.register("well_event", set_next_well) @@ -160,6 +168,7 @@ async def send_cam_job(center, event): well_img_ok=True, ) or next_well_x is None + or (next_well_x, next_well_y) not in self.wells_left ): return @@ -168,6 +177,7 @@ async def send_cam_job(center, event): await self.send_gain_jobs( next_well_x, next_well_y, ) + self.wells_left.pop((next_well_x, next_well_y)) removes = [] removes.append(self._center.bus.register("camacq_start_event", send_cam_job)) @@ -310,12 +320,11 @@ async def on_exp_image(center, event): return self._center.bus.register("image_event", on_exp_image) - def stop_exp(self, x_wells, y_wells): + def stop_exp(self): """Trigger to stop experiment.""" async def stop_imaging(center, event): """Run to stop the experiment.""" - next_well_x, _ = next_well_xy(center.sample, PLATE_NAME, x_wells, y_wells) match = match_event( event, field_x=self.x_fields - 1, @@ -323,7 +332,7 @@ async def stop_imaging(center, event): well_img_ok=True, ) - if not match or next_well_x is not None: + if not match or self.wells_left: return await center.actions.command.stop_imaging() From 5a15029222dfd03d91778a4b7915299a78eb1060 Mon Sep 17 00:00:00 2001 From: Martin Hjelmare Date: Sat, 7 Dec 2019 13:50:46 +0100 Subject: [PATCH 65/76] Refactor to simplify into one case --- camacqplugins/production/__init__.py | 80 ++++------------------------ 1 file changed, 11 insertions(+), 69 deletions(-) diff --git a/camacqplugins/production/__init__.py b/camacqplugins/production/__init__.py index dddc160..bde1e17 100644 --- a/camacqplugins/production/__init__.py +++ b/camacqplugins/production/__init__.py @@ -6,6 +6,7 @@ import voluptuous as vol +from camacq.const import CAMACQ_START_EVENT from camacq.event import match_event from camacq.plugins.leica.command import cam_com, del_com, gain_com from camacq.plugins.sample.helper import next_well_xy @@ -77,31 +78,26 @@ def __init__(self, center, conf): async def setup(self, state_file): """Set up the flow.""" if state_file is not None: - x_wells = None - y_wells = None - await self.load_sample(state_file) - self.image_next_well_on_sample() + state_data = await self._center.add_executor_job(read_csv, state_file) else: x_wells = 12 y_wells = 8 - self.wells_left = { - (well_x, well_y) + state_data = [ + {"plate_name": PLATE_NAME, "well_x": well_x, "well_y": well_y} for well_x in range(x_wells) for well_y in range(y_wells) - } - self.start_exp() - self.add_next_well(x_wells, y_wells) - self.image_next_well_on_event() + ] + await self.load_sample(state_data) + self.image_next_well_on_sample() self.analyze_gain() self.set_exp_gain() self.add_exp_job() self._remove_handle_exp_image = self.handle_exp_image() self.stop_exp() - async def load_sample(self, state_file): - """Load sample state from file.""" - state_data = await self._center.add_executor_job(read_csv, state_file) + async def load_sample(self, state_data): + """Load sample state.""" self.wells_left = {(data["well_x"], data["well_y"]) for data in state_data} for data in state_data: if data["plate_name"] not in self._center.sample.plates: @@ -112,46 +108,6 @@ async def load_sample(self, state_file): await self._center.actions.sample.set_channel(silent=True, **data) await self._center.actions.sample.set_field(silent=True, **data) - def start_exp(self): - """Trigger on start experiment.""" - - async def set_start_well(center, event): - """Run on start event.""" - await center.actions.sample.set_well( - plate_name=PLATE_NAME, well_x=0, well_y=0 - ) - - return self._center.bus.register("camacq_start_event", set_start_well) - - def add_next_well(self, x_wells, y_wells): - """Add next well.""" - - async def set_next_well(center, event): - """Run on well event.""" - next_well_x, next_well_y = next_well_xy( - center.sample, PLATE_NAME, x_wells, y_wells - ) - - if ( - not match_event( - event, - field_x=self.x_fields - 1, - field_y=self.y_fields - 1, - well_img_ok=True, - ) - or next_well_x is None - or (next_well_x, next_well_y) not in self.wells_left - ): - return - - await center.actions.command.stop_imaging() - await center.actions.sample.set_well( - plate_name=PLATE_NAME, well_x=next_well_x, well_y=next_well_y - ) - self.wells_left.pop((next_well_x, next_well_y)) - - return self._center.bus.register("well_event", set_next_well) - def image_next_well_on_sample(self): """Image next well in existing sample.""" @@ -160,7 +116,7 @@ async def send_cam_job(center, event): next_well_x, next_well_y = next_well_xy(center.sample, PLATE_NAME) if ( - not match_event(event, event_type="camacq_start_event") + not match_event(event, event_type=CAMACQ_START_EVENT) and not match_event( event, field_x=self.x_fields - 1, @@ -180,7 +136,7 @@ async def send_cam_job(center, event): self.wells_left.pop((next_well_x, next_well_y)) removes = [] - removes.append(self._center.bus.register("camacq_start_event", send_cam_job)) + removes.append(self._center.bus.register(CAMACQ_START_EVENT, send_cam_job)) removes.append(self._center.bus.register("well_event", send_cam_job)) def remove_callback(): @@ -191,20 +147,6 @@ def remove_callback(): return remove_callback - def image_next_well_on_event(self): - """Image next well.""" - - async def send_cam_job(center, event): - """Run on well event.""" - if event.well.images: - return - - await self.send_gain_jobs( - event.well.x, event.well.y, - ) - - return self._center.bus.register("well_event", send_cam_job) - def analyze_gain(self): """Analyze gain.""" From c62d7fff509c417360333b449703d58db4d67f0f Mon Sep 17 00:00:00 2001 From: Martin Hjelmare Date: Sat, 7 Dec 2019 14:10:54 +0100 Subject: [PATCH 66/76] Clean up strings --- README.md | 2 +- camacqplugins/production/__init__.py | 116 +++++++++++++++++---------- config_templates/production.yml | 2 +- tests/production/test_production.py | 2 +- 4 files changed, 75 insertions(+), 47 deletions(-) diff --git a/README.md b/README.md index 2970a9f..746dca8 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,7 @@ file. ```yaml production: - state_file: '/sample_state.csv' + sample_state_file: '/sample_state.csv' ``` Each row in the csv file should represent a state of a sample container, diff --git a/camacqplugins/production/__init__.py b/camacqplugins/production/__init__.py index bde1e17..702368c 100644 --- a/camacqplugins/production/__init__.py +++ b/camacqplugins/production/__init__.py @@ -6,7 +6,7 @@ import voluptuous as vol -from camacq.const import CAMACQ_START_EVENT +from camacq.const import CAMACQ_START_EVENT, CHANNEL_EVENT, IMAGE_EVENT, WELL_EVENT from camacq.event import match_event from camacq.plugins.leica.command import cam_com, del_com, gain_com from camacq.plugins.sample.helper import next_well_xy @@ -14,34 +14,53 @@ _LOGGER = logging.getLogger(__name__) +CONF_GAIN_PATTERN_NAME = "gain_pattern_name" +CONF_GAIN_JOB_ID = "gain_job_id" +CONF_GAIN_JOB_CHANNELS = "gain_job_channels" +CONF_EXP_PATTERN_NAME = "exp_pattern_name" +CONF_EXP_JOB_IDS = "exp_job_ids" +CONF_CHANNELS = "channels" +CONF_CHANNEL = "channel" +CONF_JOB_NAME = "job_name" +CONF_DETECTOR_NUM = "detector_num" +CONF_DEFAULT_GAIN = "default_gain" +CONF_MAX_GAIN = "max_gain" +CONF_WELL_LAYOUT = "well_layout" +CONF_X_FIELDS = "x_fields" +CONF_Y_FIELDS = "y_fields" +CONF_PLOT_SAVE_PATH = "plot_save_path" +CONF_SAMPLE_STATE_FILE = "sample_state_file" + PLATE_NAME = "00" -SAMPLE_STATE_FILE = "state_file" +SAMPLE_PLATE_NAME = "plate_name" +SAMPLE_WELL_X = "well_x" +SAMPLE_WELL_Y = "well_y" CONFIG_SCHEMA = vol.Schema( { - vol.Required("gain_pattern_name"): vol.Coerce(str), - vol.Required("gain_job_id"): vol.Coerce(int), - vol.Required("gain_job_channels"): vol.Coerce(int), - vol.Required("exp_pattern_name"): vol.Coerce(str), - vol.Required("exp_job_ids"): vol.All( + vol.Required(CONF_GAIN_PATTERN_NAME): vol.Coerce(str), + vol.Required(CONF_GAIN_JOB_ID): vol.Coerce(int), + vol.Required(CONF_GAIN_JOB_CHANNELS): vol.Coerce(int), + vol.Required(CONF_EXP_PATTERN_NAME): vol.Coerce(str), + vol.Required(CONF_EXP_JOB_IDS): vol.All( [vol.Coerce(int)], vol.Length(min=3, max=3) ), - vol.Required("channels"): [ + vol.Required(CONF_CHANNELS): [ { - vol.Required("channel"): vol.Coerce(str), - vol.Required("job_name"): vol.Coerce(str), - vol.Required("detector_num"): vol.Coerce(int), - vol.Required("default_gain"): vol.Coerce(int), - vol.Required("max_gain"): vol.Coerce(int), + vol.Required(CONF_CHANNEL): vol.Coerce(str), + vol.Required(CONF_JOB_NAME): vol.Coerce(str), + vol.Required(CONF_DETECTOR_NUM): vol.Coerce(int), + vol.Required(CONF_DEFAULT_GAIN): vol.Coerce(int), + vol.Required(CONF_MAX_GAIN): vol.Coerce(int), } ], - vol.Required("well_layout"): { - vol.Required("x_fields"): vol.Coerce(int), - vol.Required("y_fields"): vol.Coerce(int), + vol.Required(CONF_WELL_LAYOUT): { + vol.Required(CONF_X_FIELDS): vol.Coerce(int), + vol.Required(CONF_Y_FIELDS): vol.Coerce(int), }, # pylint: disable=no-value-for-parameter - "plot_save_path": vol.IsDir(), - SAMPLE_STATE_FILE: vol.IsFile(), + CONF_PLOT_SAVE_PATH: vol.IsDir(), + CONF_SAMPLE_STATE_FILE: vol.IsFile(), }, ) @@ -50,7 +69,7 @@ async def setup_module(center, config): """Set up production plugin.""" conf = config["production"] flow = WorkFlow(center, conf) - state_file = conf.get(SAMPLE_STATE_FILE) + state_file = conf.get(CONF_SAMPLE_STATE_FILE) await flow.setup(state_file) @@ -62,16 +81,16 @@ class WorkFlow: def __init__(self, center, conf): """Set up instance.""" self._center = center - self.gain_pattern = conf["gain_pattern_name"] - self.gain_job_id = conf["gain_job_id"] - self.gain_job_channels = conf["gain_job_channels"] - self.exp_pattern = conf["exp_pattern_name"] - self.exp_job_ids = conf["exp_job_ids"] - self.channels = conf["channels"] - well_layout = conf["well_layout"] - self.x_fields = well_layout["x_fields"] - self.y_fields = well_layout["y_fields"] - self.plot_save_path = conf.get("plot_save_path") + self.gain_pattern = conf[CONF_GAIN_PATTERN_NAME] + self.gain_job_id = conf[CONF_GAIN_JOB_ID] + self.gain_job_channels = conf[CONF_GAIN_JOB_CHANNELS] + self.exp_pattern = conf[CONF_EXP_PATTERN_NAME] + self.exp_job_ids = conf[CONF_EXP_JOB_IDS] + self.channels = conf[CONF_CHANNELS] + well_layout = conf[CONF_WELL_LAYOUT] + self.x_fields = well_layout[CONF_X_FIELDS] + self.y_fields = well_layout[CONF_Y_FIELDS] + self.plot_save_path = conf.get(CONF_PLOT_SAVE_PATH) self._remove_handle_exp_image = None self.wells_left = set() @@ -83,7 +102,11 @@ async def setup(self, state_file): x_wells = 12 y_wells = 8 state_data = [ - {"plate_name": PLATE_NAME, "well_x": well_x, "well_y": well_y} + { + SAMPLE_PLATE_NAME: PLATE_NAME, + SAMPLE_WELL_X: well_x, + SAMPLE_WELL_Y: well_y, + } for well_x in range(x_wells) for well_y in range(y_wells) ] @@ -98,12 +121,17 @@ async def setup(self, state_file): async def load_sample(self, state_data): """Load sample state.""" - self.wells_left = {(data["well_x"], data["well_y"]) for data in state_data} + self.wells_left = { + (data[SAMPLE_WELL_X], data[SAMPLE_WELL_Y]) for data in state_data + } for data in state_data: - if data["plate_name"] not in self._center.sample.plates: + if data[SAMPLE_PLATE_NAME] not in self._center.sample.plates: await self._center.actions.sample.set_plate(silent=True, **data) - well_coord = int(data["well_x"]), int(data["well_y"]) - if well_coord not in self._center.sample.plates[data["plate_name"]].wells: + well_coord = int(data[SAMPLE_WELL_X]), int(data[SAMPLE_WELL_Y]) + if ( + well_coord + not in self._center.sample.plates[data[SAMPLE_PLATE_NAME]].wells + ): await self._center.actions.sample.set_well(silent=True, **data) await self._center.actions.sample.set_channel(silent=True, **data) await self._center.actions.sample.set_field(silent=True, **data) @@ -137,7 +165,7 @@ async def send_cam_job(center, event): removes = [] removes.append(self._center.bus.register(CAMACQ_START_EVENT, send_cam_job)) - removes.append(self._center.bus.register("well_event", send_cam_job)) + removes.append(self._center.bus.register(WELL_EVENT, send_cam_job)) def remove_callback(): """Remove all registered listeners of this method.""" @@ -183,7 +211,7 @@ async def calc_gain(center, event): save_path=save_path, ) - return self._center.bus.register("image_event", calc_gain) + return self._center.bus.register(IMAGE_EVENT, calc_gain) def set_exp_gain(self): """Set experiment gain.""" @@ -194,12 +222,12 @@ async def set_gain(center, event): ( channel for channel in self.channels - if event.channel_name == channel["channel"] + if event.channel_name == channel[CONF_CHANNEL] ) ) - exp = channel["job_name"] - num = channel["detector_num"] - gain = min(event.gain or channel["default_gain"], channel["max_gain"]) + exp = channel[CONF_JOB_NAME] + num = channel[CONF_DETECTOR_NUM] + gain = min(event.gain or channel[CONF_DEFAULT_GAIN], channel[CONF_MAX_GAIN]) command = gain_com(exp=exp, num=num, value=gain) @@ -222,7 +250,7 @@ def add_exp_job(self): async def add_cam_job(center, event): """Add an experiment job to the cam list.""" last_channel = self.channels[-1] - if not match_event(event, channel_name=last_channel["channel"]) or len( + if not match_event(event, channel_name=last_channel[CONF_CHANNEL]) or len( event.well.channels ) != len(self.channels): return @@ -250,7 +278,7 @@ async def add_cam_job(center, event): await center.actions.command.start_imaging() await center.actions.command.send(command="/cmd:startcamscan") - return self._center.bus.register("channel_event", add_cam_job) + return self._center.bus.register(CHANNEL_EVENT, add_cam_job) def handle_exp_image(self): """Handle experiment image.""" @@ -260,7 +288,7 @@ async def on_exp_image(center, event): await self.rename_image(center, event) await self.set_sample_img_ok(center, event) - return self._center.bus.register("image_event", on_exp_image) + return self._center.bus.register(IMAGE_EVENT, on_exp_image) def stop_exp(self): """Trigger to stop experiment.""" @@ -281,7 +309,7 @@ async def stop_imaging(center, event): _LOGGER.info("Congratulations, experiment is finished!") - return self._center.bus.register("well_event", stop_imaging) + return self._center.bus.register(WELL_EVENT, stop_imaging) async def send_gain_jobs(self, well_x, well_y): """Send gain cam jobs for the center fields of a well.""" diff --git a/config_templates/production.yml b/config_templates/production.yml index 4ecdd38..3a28568 100644 --- a/config_templates/production.yml +++ b/config_templates/production.yml @@ -1,5 +1,5 @@ production: - #state_file: "/path/to/sample_state.csv" + #sample_state_file: "/path/to/sample_state.csv" gain_pattern_name: p10xgain gain_job_id: 3 gain_job_channels: 32 diff --git a/tests/production/test_production.py b/tests/production/test_production.py index 5e148b9..cad6475 100644 --- a/tests/production/test_production.py +++ b/tests/production/test_production.py @@ -148,7 +148,7 @@ async def test_load_sample(center, tmp_path): state_file = tmp_path / "state_file.csv" state_file.write_text(SAMPLE_STATE) config = YAML(typ="safe").load(CONFIG) - config["production"]["state_file"] = str(state_file) + config["production"]["sample_state_file"] = str(state_file) plate_name = "00" await plugins.setup_module(center, config) await center.wait_for() From 39cf3f5a9d9c39b67e5917048b6b8f9a01c34c69 Mon Sep 17 00:00:00 2001 From: Martin Hjelmare Date: Sat, 7 Dec 2019 14:16:41 +0100 Subject: [PATCH 67/76] Clean up --- camacqplugins/production/__init__.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/camacqplugins/production/__init__.py b/camacqplugins/production/__init__.py index 702368c..0ca701f 100644 --- a/camacqplugins/production/__init__.py +++ b/camacqplugins/production/__init__.py @@ -121,13 +121,11 @@ async def setup(self, state_file): async def load_sample(self, state_data): """Load sample state.""" - self.wells_left = { - (data[SAMPLE_WELL_X], data[SAMPLE_WELL_Y]) for data in state_data - } for data in state_data: if data[SAMPLE_PLATE_NAME] not in self._center.sample.plates: await self._center.actions.sample.set_plate(silent=True, **data) well_coord = int(data[SAMPLE_WELL_X]), int(data[SAMPLE_WELL_Y]) + self.wells_left.add(well_coord) if ( well_coord not in self._center.sample.plates[data[SAMPLE_PLATE_NAME]].wells From 6a2c3be667353bb585cbb0cff31c44b4d6317c34 Mon Sep 17 00:00:00 2001 From: Martin Hjelmare Date: Sat, 7 Dec 2019 14:54:16 +0100 Subject: [PATCH 68/76] Load csv file during validation --- camacqplugins/production/__init__.py | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/camacqplugins/production/__init__.py b/camacqplugins/production/__init__.py index 0ca701f..d8c83ca 100644 --- a/camacqplugins/production/__init__.py +++ b/camacqplugins/production/__init__.py @@ -36,6 +36,13 @@ SAMPLE_WELL_X = "well_x" SAMPLE_WELL_Y = "well_y" + +@vol.truth +def is_csv(value): + """Return true if value ends with .csv.""" + return value.endswith(".csv") + + CONFIG_SCHEMA = vol.Schema( { vol.Required(CONF_GAIN_PATTERN_NAME): vol.Coerce(str), @@ -60,7 +67,7 @@ }, # pylint: disable=no-value-for-parameter CONF_PLOT_SAVE_PATH: vol.IsDir(), - CONF_SAMPLE_STATE_FILE: vol.IsFile(), + CONF_SAMPLE_STATE_FILE: vol.All(vol.IsFile(), is_csv, read_csv), }, ) @@ -69,8 +76,8 @@ async def setup_module(center, config): """Set up production plugin.""" conf = config["production"] flow = WorkFlow(center, conf) - state_file = conf.get(CONF_SAMPLE_STATE_FILE) - await flow.setup(state_file) + state_data = conf.get(CONF_SAMPLE_STATE_FILE) + await flow.setup(state_data) class WorkFlow: @@ -94,11 +101,9 @@ def __init__(self, center, conf): self._remove_handle_exp_image = None self.wells_left = set() - async def setup(self, state_file): + async def setup(self, state_data): """Set up the flow.""" - if state_file is not None: - state_data = await self._center.add_executor_job(read_csv, state_file) - else: + if state_data is None: x_wells = 12 y_wells = 8 state_data = [ From e489c387cbdc6b974e8f4d5b2217f63755bed77b Mon Sep 17 00:00:00 2001 From: Martin Hjelmare Date: Sat, 7 Dec 2019 14:54:49 +0100 Subject: [PATCH 69/76] Fix set remove --- camacqplugins/production/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/camacqplugins/production/__init__.py b/camacqplugins/production/__init__.py index d8c83ca..52f047d 100644 --- a/camacqplugins/production/__init__.py +++ b/camacqplugins/production/__init__.py @@ -164,7 +164,7 @@ async def send_cam_job(center, event): await self.send_gain_jobs( next_well_x, next_well_y, ) - self.wells_left.pop((next_well_x, next_well_y)) + self.wells_left.remove((next_well_x, next_well_y)) removes = [] removes.append(self._center.bus.register(CAMACQ_START_EVENT, send_cam_job)) From 561ad98e612484d92298a4335c08bc2038800695 Mon Sep 17 00:00:00 2001 From: Martin Hjelmare Date: Sat, 7 Dec 2019 15:04:48 +0100 Subject: [PATCH 70/76] Clean up --- camacqplugins/production/__init__.py | 25 ++++++++++++------------- 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/camacqplugins/production/__init__.py b/camacqplugins/production/__init__.py index 52f047d..6abf0ca 100644 --- a/camacqplugins/production/__init__.py +++ b/camacqplugins/production/__init__.py @@ -77,6 +77,18 @@ async def setup_module(center, config): conf = config["production"] flow = WorkFlow(center, conf) state_data = conf.get(CONF_SAMPLE_STATE_FILE) + if state_data is None: + x_wells = 12 + y_wells = 8 + state_data = [ + { + SAMPLE_PLATE_NAME: PLATE_NAME, + SAMPLE_WELL_X: well_x, + SAMPLE_WELL_Y: well_y, + } + for well_x in range(x_wells) + for well_y in range(y_wells) + ] await flow.setup(state_data) @@ -103,19 +115,6 @@ def __init__(self, center, conf): async def setup(self, state_data): """Set up the flow.""" - if state_data is None: - x_wells = 12 - y_wells = 8 - state_data = [ - { - SAMPLE_PLATE_NAME: PLATE_NAME, - SAMPLE_WELL_X: well_x, - SAMPLE_WELL_Y: well_y, - } - for well_x in range(x_wells) - for well_y in range(y_wells) - ] - await self.load_sample(state_data) self.image_next_well_on_sample() self.analyze_gain() From 3162c8616bd386ad0ae5624c95b75ca1b96ac3bb Mon Sep 17 00:00:00 2001 From: Martin Hjelmare Date: Sat, 7 Dec 2019 16:02:12 +0100 Subject: [PATCH 71/76] Add sample state file validation --- camacqplugins/production/__init__.py | 38 ++++++++++++++++++++++++++-- 1 file changed, 36 insertions(+), 2 deletions(-) diff --git a/camacqplugins/production/__init__.py b/camacqplugins/production/__init__.py index 6abf0ca..d151b75 100644 --- a/camacqplugins/production/__init__.py +++ b/camacqplugins/production/__init__.py @@ -9,6 +9,7 @@ from camacq.const import CAMACQ_START_EVENT, CHANNEL_EVENT, IMAGE_EVENT, WELL_EVENT from camacq.event import match_event from camacq.plugins.leica.command import cam_com, del_com, gain_com +from camacq.plugins.sample import ACTION_TO_METHOD, ACTION_SET_PLATE from camacq.plugins.sample.helper import next_well_xy from camacq.util import read_csv @@ -43,6 +44,37 @@ def is_csv(value): return value.endswith(".csv") +def is_sample_state(value): + """Validate state data. + + At least one sample action must validate per sample data item. + """ + for idx, data in enumerate(value): + valid = False + error = None + for action, settings in ACTION_TO_METHOD.items(): + if action == ACTION_SET_PLATE: + continue + schema = settings["schema"] + try: + data.update(schema(data)) + except vol.Invalid as exc: + error = exc + continue + else: + valid = True + + if not valid: + _LOGGER.error( + "The sample state file contains invalid data at row %s: %s", + idx + 2, + error, + ) + raise error + + return value + + CONFIG_SCHEMA = vol.Schema( { vol.Required(CONF_GAIN_PATTERN_NAME): vol.Coerce(str), @@ -67,7 +99,9 @@ def is_csv(value): }, # pylint: disable=no-value-for-parameter CONF_PLOT_SAVE_PATH: vol.IsDir(), - CONF_SAMPLE_STATE_FILE: vol.All(vol.IsFile(), is_csv, read_csv), + CONF_SAMPLE_STATE_FILE: vol.All( + vol.IsFile(), is_csv, read_csv, is_sample_state + ), }, ) @@ -128,7 +162,7 @@ async def load_sample(self, state_data): for data in state_data: if data[SAMPLE_PLATE_NAME] not in self._center.sample.plates: await self._center.actions.sample.set_plate(silent=True, **data) - well_coord = int(data[SAMPLE_WELL_X]), int(data[SAMPLE_WELL_Y]) + well_coord = data[SAMPLE_WELL_X], data[SAMPLE_WELL_Y] self.wells_left.add(well_coord) if ( well_coord From ed963259c6b18d5beef6d6ad80284acdc39a8898 Mon Sep 17 00:00:00 2001 From: Martin Hjelmare Date: Sat, 7 Dec 2019 16:04:43 +0100 Subject: [PATCH 72/76] Fix test --- tests/production/test_production.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/tests/production/test_production.py b/tests/production/test_production.py index cad6475..99e670d 100644 --- a/tests/production/test_production.py +++ b/tests/production/test_production.py @@ -70,8 +70,8 @@ def job_id(self): return self.data.get("job_id") -async def test_duplicate_image_events(center, tmp_path): - """Test duplicate image events.""" +async def test_image_events(center, tmp_path): + """Test image events.""" config = YAML(typ="safe").load(CONFIG) plate_name = "00" well_x = 0 @@ -126,7 +126,6 @@ async def fire_gain_event(**kwargs): } ) center.create_task(center.bus.notify(event)) - center.create_task(center.bus.notify(event)) await center.wait_for() assert calc_gain.call_count == 1 From 2a46109ac00105f2416828b55d8274fa550ab7e6 Mon Sep 17 00:00:00 2001 From: Martin Hjelmare Date: Sat, 7 Dec 2019 16:54:23 +0100 Subject: [PATCH 73/76] Update readmes --- README.md | 57 +++++++++++++++--------------- camacqplugins/production/README.md | 55 ++++++++++++++++++++++++++++ 2 files changed, 83 insertions(+), 29 deletions(-) create mode 100644 camacqplugins/production/README.md diff --git a/README.md b/README.md index 746dca8..48124df 100644 --- a/README.md +++ b/README.md @@ -1,45 +1,44 @@ # camacq-plugins -Plugins for camacq +Plugins for camacq: -## Usage +- [production](camacqplugins/production) -To allow the user to set up the sample state before starting an -experiment, camacq can load the sample state from a file. In the production -configuration section there is an option to specify a path to a csv -file. +## Installation -```yaml -production: - sample_state_file: '/sample_state.csv' -``` +- Clone and install the package. -Each row in the csv file should represent a state of a sample container, -ie plate, well, field or channel. The csv file should also have a -header. See below. + ```sh + # Clone the repo. + git clone https://github.com/CellProfiling/camacq-plugins.git + # Enter directory. + cd camacq-plugins + # Install package. + pip install . + # Test that program is callable and show help. + camacq -h + ``` -``` -plate_name,well_x,well_y,channel_name,gain -00,1,1,blue,600 -``` +### Requirements -This example will set create a plate '00', a well (1, 1), a blue channel -and set the gain of the blue channel to 600. +- Python version 3.6+. +- camacq >= 0.4.0 -``` -plate_name,well_x,well_y,field_x,field_y -00,1,1,1,1 -``` - -This example will create a plate '00' a well (1, 1) and a field (1, 1) -in the sample state. +## Usage -## Installation +Add configuration for the plugin you want to run. +See the [config_templates](config_templates/) directory for example configuration. -### Requirements +Then start `camacq`. -## Compatibility +```sh +camacq +``` ## Licence +- Apache-2.0. + ## Authors + +- Martin Hjelmare diff --git a/camacqplugins/production/README.md b/camacqplugins/production/README.md new file mode 100644 index 0000000..72d24e0 --- /dev/null +++ b/camacqplugins/production/README.md @@ -0,0 +1,55 @@ +# Production + +## Usage + +Add configuration for the `production`, `gain`, `leica` and `rename` plugin, in the `camacq` configuration file. +See the [config_templates](config_templates/) directory for example configuration. + +```yaml +production: + ... + +gain: + ... + +rename_image: + +leica: + ... +``` + +Then start `camacq`. + +```sh +camacq +``` + +To allow the user to set up the sample state before starting an +experiment, camacq can load the sample state from a file. In the production +configuration section there is an option to specify a path to a csv +file. + +```yaml +production: + sample_state_file: '/sample_state.csv' +``` + +Each row in the csv file should represent at least one state of a sample container, +ie well, field or channel. A plate name must also be included. The csv file should have a +header. See below. + +```csv +plate_name,well_x,well_y,channel_name,gain +00,1,1,blue,600 +``` + +This example will set a plate '00', a well (1, 1), a blue channel +and set the gain of the blue channel to 600. + +```csv +plate_name,well_x,well_y,field_x,field_y +00,1,1,1,1 +``` + +This example will create a plate '00' a well (1, 1) and a field (1, 1) +in the sample state. From 42ad2dda6c9ca463ff160fd301ad516209ebf4d4 Mon Sep 17 00:00:00 2001 From: Martin Hjelmare Date: Sat, 7 Dec 2019 17:00:05 +0100 Subject: [PATCH 74/76] Fix readme relative link --- README.md | 2 +- camacqplugins/production/README.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 48124df..de2c026 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ Plugins for camacq: -- [production](camacqplugins/production) +- [production](camacqplugins/production/) ## Installation diff --git a/camacqplugins/production/README.md b/camacqplugins/production/README.md index 72d24e0..3ce9c4c 100644 --- a/camacqplugins/production/README.md +++ b/camacqplugins/production/README.md @@ -3,7 +3,7 @@ ## Usage Add configuration for the `production`, `gain`, `leica` and `rename` plugin, in the `camacq` configuration file. -See the [config_templates](config_templates/) directory for example configuration. +See the [config_templates](../config_templates/) directory for example configuration. ```yaml production: From 7212ed0e5d5930f4ec055322185f80fd72a69d45 Mon Sep 17 00:00:00 2001 From: Martin Hjelmare Date: Sat, 7 Dec 2019 17:03:07 +0100 Subject: [PATCH 75/76] Add Python 3.8 tox env --- tox.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index 052b4a5..d3f181a 100644 --- a/tox.ini +++ b/tox.ini @@ -1,5 +1,5 @@ [tox] -envlist = py36, py37 +envlist = py36, py37, py38 [testenv] commands = From 3af927b83cc6f2db0ebfd175f63ee77e0ab8a050 Mon Sep 17 00:00:00 2001 From: Martin Hjelmare Date: Sun, 8 Dec 2019 01:45:17 +0100 Subject: [PATCH 76/76] Skip missing tox interpreters --- tox.ini | 1 + 1 file changed, 1 insertion(+) diff --git a/tox.ini b/tox.ini index d3f181a..bf47625 100644 --- a/tox.ini +++ b/tox.ini @@ -1,5 +1,6 @@ [tox] envlist = py36, py37, py38 +skip_missing_interpreters = True [testenv] commands =