From edc4cf33d683d0dfb070b3645acdc3c1945b2903 Mon Sep 17 00:00:00 2001 From: Kesavan Date: Fri, 16 Apr 2021 11:18:12 +0530 Subject: [PATCH 01/19] made object detection algorithm 3D --- deepthought/detection.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/deepthought/detection.py b/deepthought/detection.py index 12d9e14..62ffc14 100644 --- a/deepthought/detection.py +++ b/deepthought/detection.py @@ -1,5 +1,6 @@ import tifffile from cellpose import models +import numpy as np def segment_nuclei(image): @@ -27,5 +28,13 @@ def detect_object(image, kind="dapi"): elif kind == "cyto": seg_func = segment_cyto + if image.shape[0] > 1: + labels_ = [] + for img in image: + labels_.append(seg_func(img)) + labels_ = np.array(labels_) + + return (image, labels_) + label_ = seg_func(image) return (image, label_) From 345a116ab3ba21af44c644982d417053720ebb57 Mon Sep 17 00:00:00 2001 From: Kesavan Date: Fri, 16 Apr 2021 11:18:33 +0530 Subject: [PATCH 02/19] defined a special case of imshow --- deepthought/viz.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/deepthought/viz.py b/deepthought/viz.py index a48ba96..003b640 100644 --- a/deepthought/viz.py +++ b/deepthought/viz.py @@ -46,8 +46,12 @@ def transform_xy(x, y, stage_coords): return list(zip(x, y)) +def imshow(image, *args, **kwargs): + with napari.gui_qt(): + viewer = napari.view_image(image, name="image") + -def imshow(image, label_image, stage_coords): +def imshow_sp(image, label_image, stage_coords): with napari.gui_qt(): viewer = napari.view_image(image, name="DAPI") From 8420412be4df4833e1bdb07d6f9afc31f08b992e Mon Sep 17 00:00:00 2001 From: Kesavan Date: Fri, 16 Apr 2021 11:18:59 +0530 Subject: [PATCH 03/19] simulated microscope now sends a noisy stage timelapse --- deepthought/devices.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/deepthought/devices.py b/deepthought/devices.py index 1e3bd0b..ed4c80e 100644 --- a/deepthought/devices.py +++ b/deepthought/devices.py @@ -107,6 +107,14 @@ def random_crop(image, size=512): return cropped_image +def frame_crop(image, size=512, tol=100): + """generate a random crop of the image for the given size""" + error = np.random.randint(0, tol) + x, y = 150, 250 + cropped_image = image[x+error:x+size+error, y+error:y+size+error] + return cropped_image + + class SimMMC: """This is a simulated microscope that returns a 512x512 image.""" @@ -146,7 +154,7 @@ def waitForDevice(self, label): return def getImage(self): - return random_crop(self.data) + return frame_crop(self.data) class Focus: From ced76df24ada13a082d6c06de5c58a2c0b6f7556 Mon Sep 17 00:00:00 2001 From: Kesavan Date: Fri, 16 Apr 2021 11:19:48 +0530 Subject: [PATCH 04/19] cleaned up run by defining a Microscope, with higher level bluesky calls, and returns databroker headers --- deepthought/microscope.py | 31 ++++++++++++++++++++++++----- deepthought/run.py | 42 +++------------------------------------ 2 files changed, 29 insertions(+), 44 deletions(-) diff --git a/deepthought/microscope.py b/deepthought/microscope.py index 3894d06..b81353a 100644 --- a/deepthought/microscope.py +++ b/deepthought/microscope.py @@ -78,12 +78,33 @@ # other devices have to be added. # to figure out where +from bluesky import RunEngine +from bluesky.callbacks.best_effort import BestEffortCallback +from bluesky.plans import count, scan +from databroker import Broker + +bec = BestEffortCallback() +bec.disable_plots() + +db = Broker.named("temp") + + +RE = RunEngine({}) +RE.subscribe(bec) +RE.subscribe(db.insert) class Microscope: - def __init__(self): + def __init__(self, mmc): self.name = None - - def snap(self): - # run a blue sky count method with detector - pass + self.mmc = mmc + self._cam = [Camera(mmc)] + self.z = Focus(mmc) + self.xy = None + + def snap(self, num=1, delay=0): + # run a blue sky count method with cameras + # return uid + uid, = RE(count(self._cam, num=num, delay=None)) + header = db[uid] + return header diff --git a/deepthought/run.py b/deepthought/run.py index 2c3dafa..3efa888 100644 --- a/deepthought/run.py +++ b/deepthought/run.py @@ -1,44 +1,8 @@ -from bluesky import RunEngine -from bluesky.callbacks.best_effort import BestEffortCallback -from bluesky.plans import count, scan -from databroker import Broker -from magicgui import magicgui - from comms import client -from configs import config -from detection import detect_object -from devices import Camera, Focus, SimMMC -from viz import imshow - -bec = BestEffortCallback() -bec.disable_plots() - -db = Broker.named("temp") +from devices import SimMMC +from microscope import Microscope # mmc = client(addr="10.10.1.62", port=18861).mmc mmc = SimMMC() -cam = Camera(mmc) -motor = Focus(mmc) - -RE = RunEngine({}) -RE.subscribe(bec) -RE.subscribe(db.insert) - - -# decorate your function with the @magicgui decorator -@magicgui(call_button="snap", result_widget=True) -def snap(): - RE(count([cam], num=1)) - # to access the data, get the header object (of databroker) - # and access the data of camera - header = db[-1] - - data = header.data("camera") - img = next(data) - (_, label) = detect_object(img, kind="dapi") - stage_coords = mmc.getXYPosition() - - imshow(img, label, stage_coords) - -snap.show(run=True) +bs = Microscope(mmc) From 56ff8f66c0490b95b0dc25163ba83e13a138f341 Mon Sep 17 00:00:00 2001 From: Kesavan Date: Fri, 16 Apr 2021 12:14:50 +0530 Subject: [PATCH 05/19] access pandas dataframe --- deepthought/run.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/deepthought/run.py b/deepthought/run.py index 3efa888..b768213 100644 --- a/deepthought/run.py +++ b/deepthought/run.py @@ -6,3 +6,5 @@ mmc = SimMMC() bs = Microscope(mmc) +data_header = bs.snap(num=3) +df = data_header.table() From d631849ee610e8220ba0eb1b8da031f90c0849e7 Mon Sep 17 00:00:00 2001 From: Kesavan Date: Fri, 16 Apr 2021 14:42:12 +0530 Subject: [PATCH 06/19] isort-ed import statements --- deepthought/comms.py | 1 + deepthought/configs.py | 2 +- deepthought/detection.py | 2 +- deepthought/devices.py | 10 ++++------ deepthought/microscope.py | 10 ++++++---- 5 files changed, 13 insertions(+), 12 deletions(-) diff --git a/deepthought/comms.py b/deepthought/comms.py index 316c7a1..9e12ebc 100644 --- a/deepthought/comms.py +++ b/deepthought/comms.py @@ -2,6 +2,7 @@ import rpyc from rpyc.utils.server import ThreadedServer + def server(object_, port, *args, **kwargs): s = ThreadedServer(object_, hostname="", port=port, auto_register=None, protocol_config={"allow_all_attrs": True, diff --git a/deepthought/configs.py b/deepthought/configs.py index 9a989df..b8aff7d 100644 --- a/deepthought/configs.py +++ b/deepthought/configs.py @@ -1,5 +1,5 @@ -from collections import OrderedDict import os +from collections import OrderedDict config = OrderedDict() diff --git a/deepthought/detection.py b/deepthought/detection.py index 62ffc14..f54ca03 100644 --- a/deepthought/detection.py +++ b/deepthought/detection.py @@ -1,6 +1,6 @@ +import numpy as np import tifffile from cellpose import models -import numpy as np def segment_nuclei(image): diff --git a/deepthought/devices.py b/deepthought/devices.py index ed4c80e..c6f505b 100644 --- a/deepthought/devices.py +++ b/deepthought/devices.py @@ -7,17 +7,15 @@ 2. https://github.com/SEBv15/GSD192-tools """ -import time import threading -from typing import Dict, List, Any, TypeVar, Tuple - - -from collections import OrderedDict +import time +from collections import OrderedDict +from typing import Any, Dict, List, Tuple, TypeVar import numpy as np +from ophyd.status import Status from skimage import io -from ophyd.status import Status class BaseScope: def __init__(self, mmc): diff --git a/deepthought/microscope.py b/deepthought/microscope.py index b81353a..f1385d1 100644 --- a/deepthought/microscope.py +++ b/deepthought/microscope.py @@ -74,15 +74,17 @@ """ -from devices import Camera, Focus -# other devices have to be added. -# to figure out where - from bluesky import RunEngine from bluesky.callbacks.best_effort import BestEffortCallback from bluesky.plans import count, scan from databroker import Broker +from devices import Camera, Focus + +# other devices have to be added. +# to figure out where + + bec = BestEffortCallback() bec.disable_plots() From 436a2a7de043ebb5415deabe201b3e15410c23e2 Mon Sep 17 00:00:00 2001 From: Kesavan Date: Fri, 16 Apr 2021 14:44:07 +0530 Subject: [PATCH 07/19] replace for loop with list comprehension --- deepthought/detection.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/deepthought/detection.py b/deepthought/detection.py index f54ca03..fa1eb6e 100644 --- a/deepthought/detection.py +++ b/deepthought/detection.py @@ -29,10 +29,7 @@ def detect_object(image, kind="dapi"): seg_func = segment_cyto if image.shape[0] > 1: - labels_ = [] - for img in image: - labels_.append(seg_func(img)) - labels_ = np.array(labels_) + labels_ = np.array([seg_func(img) for img in image]) return (image, labels_) From 4e3b3850acebdf4c41dd83eafedadd31cde6f56d Mon Sep 17 00:00:00 2001 From: Kesavan Date: Mon, 19 Apr 2021 21:15:30 +0530 Subject: [PATCH 08/19] comments --- deepthought/comms.py | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/deepthought/comms.py b/deepthought/comms.py index 9e12ebc..8092e9a 100644 --- a/deepthought/comms.py +++ b/deepthought/comms.py @@ -4,6 +4,17 @@ def server(object_, port, *args, **kwargs): + """serving an object in a port. + + parameters + -- + object_ : object + any python object that needs to be network enabled. + port : int + defines the port where the server is listening for requests. + + + """ s = ThreadedServer(object_, hostname="", port=port, auto_register=None, protocol_config={"allow_all_attrs": True, "allow_pickle" : True, @@ -13,6 +24,15 @@ def server(object_, port, *args, **kwargs): return s def client(addr, port, *args, **kwargs): + """generic function to connect to a rpyc server. + + parameters + -- + addr : str + ip address/url of the server + port : int + port + """ obj = rpyc.connect(addr, port, config={ "allow_all_attrs": True, "allow_pickle" : True }) From c8bcd6a06d65204ebd5ee9ba36df320edbb45307 Mon Sep 17 00:00:00 2001 From: Kesavan Date: Mon, 19 Apr 2021 22:08:01 +0530 Subject: [PATCH 09/19] return label only --- deepthought/detection.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/deepthought/detection.py b/deepthought/detection.py index fa1eb6e..2fba46f 100644 --- a/deepthought/detection.py +++ b/deepthought/detection.py @@ -31,7 +31,7 @@ def detect_object(image, kind="dapi"): if image.shape[0] > 1: labels_ = np.array([seg_func(img) for img in image]) - return (image, labels_) + return labels_ label_ = seg_func(image) - return (image, label_) + return label_ From 4a6a8ed62906d72eaf7ab442178c902d919eba56 Mon Sep 17 00:00:00 2001 From: Kesavan Date: Mon, 19 Apr 2021 22:10:34 +0530 Subject: [PATCH 10/19] stage init --- deepthought/devices.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/deepthought/devices.py b/deepthought/devices.py index c6f505b..d1d316b 100644 --- a/deepthought/devices.py +++ b/deepthought/devices.py @@ -274,3 +274,16 @@ def describe_configuration(self) -> OrderedDict: def read_configuration(self) -> OrderedDict: return OrderedDict() + + +class Stage: + name = "stage" + parent = None + + + def __init__(self, mmc): + self.mmc = mmc + x = None + y = None + z = None + \ No newline at end of file From 116727ff3d48e31e56de55a800c6222645d5546f Mon Sep 17 00:00:00 2001 From: Kesavan Date: Mon, 19 Apr 2021 22:11:37 +0530 Subject: [PATCH 11/19] added comments --- deepthought/microscope.py | 86 ++++++--------------------------------- 1 file changed, 12 insertions(+), 74 deletions(-) diff --git a/deepthought/microscope.py b/deepthought/microscope.py index f1385d1..fe4b17d 100644 --- a/deepthought/microscope.py +++ b/deepthought/microscope.py @@ -1,79 +1,12 @@ """ -microscope abstraction layer --- -handles all the abstractions of human API with the microscope. - - -construction --- -a microscope is made up of modular device components that work together -to orchestrate an experimental task. - -these modules are - 1. XYStage - 1. xy position - 2. limits of stage - 2. NosePiece - 1. Objectives - 2. Z value - 3. EnteringLight - 1. LightSources - 1. LED - 1. Intensity - 2. Wavelength - 2. Hallide - 1. Intensity - 2. Shutters - 3. Optics - 1. Mirrors - 2. Condensor - 4. ExitingLight - 1. ViewPorts - 1. Detectors - 1. exposure - 2. gain - 3. binning - 2. EyePiece - - -usage primitives --- -The microscope is fundamentally used by a user to make visual/abstract observations of the - samples thru one or more ViewPorts. - -The user configures the devices appropriately according to their wishes of -how the light should enter or exit the sample. - -For example: - if I want to image DAPI image, EnteringLight is configured such that - 1. LED is on, set to 405, with an intensity determined by a feedback - system. - -This abstraction allows us to group devices according to their functionality, -and create a more of an end-image centric organization of the device -primitives to define common microscopy tasks, which is what this section aims to encode. - -user API --- - -What a user does with their microscope, is their own business, but there are patterns -in usage that can be utilized to form an abstraction that can be used to generalize use -cases so as to code it into a system. Such abstractions are ideal design parameters for -APIs. - -We intend to map the user API with the System, in order to figure out the microscope -abstraction - - - -Notes --- -1. One can in-principle keep exposure constant and vary intensity of light source -2. 50-50 can be a ViewPort of the Detector kind, where there is an - exposure_factor of 2. +To do: +1. configure number of cameras and camera parameters easily from user code. +2. xy, z into a position object. + Stage.x + """ - +import numpy as np from bluesky import RunEngine from bluesky.callbacks.best_effort import BestEffortCallback from bluesky.plans import count, scan @@ -107,6 +40,11 @@ def snap(self, num=1, delay=0): # run a blue sky count method with cameras # return uid uid, = RE(count(self._cam, num=num, delay=None)) + + # https://nsls-ii.github.io/databroker/generated/databroker.Header.table.html#databroker.Header.table header = db[uid] - return header + + img = np.stack(header.table()["camera"].array) + + return img From b43fce47a7079dec96e235bc5d14cf934c99906a Mon Sep 17 00:00:00 2001 From: Kesavan Date: Mon, 19 Apr 2021 22:12:31 +0530 Subject: [PATCH 12/19] more user code --- deepthought/run.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/deepthought/run.py b/deepthought/run.py index b768213..32bece6 100644 --- a/deepthought/run.py +++ b/deepthought/run.py @@ -1,10 +1,15 @@ from comms import client from devices import SimMMC from microscope import Microscope +from detection import detect_object +from viz import imshow +# https://valelab4.ucsf.edu/~MM/doc/MMCore/html/class_c_m_m_core.html # mmc = client(addr="10.10.1.62", port=18861).mmc mmc = SimMMC() +# bs - bright star microscope bs = Microscope(mmc) -data_header = bs.snap(num=3) -df = data_header.table() +images = bs.snap(num=4) +labels = detect_object(images) +imshow(images, labels) From 4127ccc1df7bcf94db30574301771c8bf200481a Mon Sep 17 00:00:00 2001 From: Kesavan Date: Mon, 19 Apr 2021 22:12:52 +0530 Subject: [PATCH 13/19] added label layer --- deepthought/viz.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/deepthought/viz.py b/deepthought/viz.py index 003b640..2067695 100644 --- a/deepthought/viz.py +++ b/deepthought/viz.py @@ -46,10 +46,14 @@ def transform_xy(x, y, stage_coords): return list(zip(x, y)) -def imshow(image, *args, **kwargs): +def imshow(image, label_image=None, *args, **kwargs): with napari.gui_qt(): viewer = napari.view_image(image, name="image") + if label_image is not None: + print("here") + viewer.add_labels(label_image, visible=False, name="segments") + def imshow_sp(image, label_image, stage_coords): with napari.gui_qt(): From 4b86d38a5410211830fae266359ac7caa421146c Mon Sep 17 00:00:00 2001 From: Kesavan Date: Mon, 3 May 2021 15:34:54 +0530 Subject: [PATCH 14/19] added support for xystage in ophyd --- deepthought/devices.py | 22 ++++++++++++++++------ 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/deepthought/devices.py b/deepthought/devices.py index d1d316b..6c39437 100644 --- a/deepthought/devices.py +++ b/deepthought/devices.py @@ -14,6 +14,7 @@ import numpy as np from ophyd.status import Status +from ophyd import Signal from skimage import io @@ -218,6 +219,7 @@ def describe_configuration(self) -> OrderedDict: class Camera: name = "camera" parent = None + exposure_time = None def __init__(self, mmc): self.mmc = mmc @@ -276,14 +278,22 @@ def read_configuration(self) -> OrderedDict: return OrderedDict() -class Stage: - name = "stage" +class XYStage: + name = "xy" parent = None - def __init__(self, mmc): self.mmc = mmc - x = None - y = None - z = None + self.mmc_device_name = self.mmc.getXYStageDevice() + self.get() + + def get(self, **kwargs): + '''The readback value''' + self._readback = self.mmc.getXYPosition() + return self._readback + + def set(self, value): + self.mmc.setXYPosition(*value) + self.mmc.waitForDevice(self.mmc_device_name) + self.get() \ No newline at end of file From 5040a5ef7b8457d0d99b706ff7ea88aecf051001 Mon Sep 17 00:00:00 2001 From: Kesavan Date: Tue, 4 May 2021 00:12:20 +0530 Subject: [PATCH 15/19] Stage as a pseudopositioner --- deepthought/devices.py | 87 +++++++++++++++++++++++++++++++++++++----- 1 file changed, 78 insertions(+), 9 deletions(-) diff --git a/deepthought/devices.py b/deepthought/devices.py index 6c39437..5c411b0 100644 --- a/deepthought/devices.py +++ b/deepthought/devices.py @@ -15,6 +15,11 @@ import numpy as np from ophyd.status import Status from ophyd import Signal +from ophyd import (Component as Cpt) +from ophyd import (PseudoPositioner, PseudoSingle) +from ophyd.pseudopos import (pseudo_position_argument, + real_position_argument) + from skimage import io @@ -285,15 +290,79 @@ class XYStage: def __init__(self, mmc): self.mmc = mmc self.mmc_device_name = self.mmc.getXYStageDevice() - self.get() - def get(self, **kwargs): - '''The readback value''' - self._readback = self.mmc.getXYPosition() - return self._readback + + def trigger(self): + status = Status(obj=self, timeout=10) + + def wait(): + try: + self.mmc.waitForDevice(self.mmc_device_name) + except Exception as exc: + status.set_exception(exc) + else: + status.set_finished() + + threading.Thread(target=wait).start() + return status + + def read(self): + data = OrderedDict() + data['xy'] = {'value': self.mmc.getXYPosition(), 'timestamp': time.time()} + return data + + def describe(self): + data = OrderedDict() + data['xy'] = {'source': "MMCore", + 'dtype': "number", + 'shape' : []} + return data + def set(self, value): - self.mmc.setXYPosition(*value) - self.mmc.waitForDevice(self.mmc_device_name) - self.get() - \ No newline at end of file + status = Status(obj=self, timeout=5) + def wait(): + try: + self.mmc.setXYPosition(*value) + self.mmc.waitForDevice(self.mmc_device_name) + except Exception as exc: + status.set_exception(exc) + else: + status.set_finished() + + threading.Thread(target=wait).start() + + return status + + def read_configuration(self) -> OrderedDict: + return OrderedDict() + + def describe_configuration(self) -> OrderedDict: + return OrderedDict() + + +class Stage(PseudoPositioner): + # The pseudo positioner axes: + px = Cpt(PseudoSingle) + py = Cpt(PseudoSingle) + + # The real (or physical) positioners: + rxy = Cpt(XYStage, self.mmc) # FIXME: + + def __init__(self, prefix='', mmc=None, *, **kwargs): + if mmc is None: + raise ValueError("Must supply the 'mmc' object.") + self.mmc = mmc + + # now, tell the PseudoPositioner to construct itself + super().__init__(prefix=prefix, **kwargs) + + @pseudo_position_argument + def forward(self, pseudo_pos): + '''Run a forward (pseudo -> real) calculation''' + return self.RealPosition(rxy=(pseudo_pos.px, pseudo_pos.py)) + @real_position_argument + def inverse(self, real_pos): + '''Run an inverse (real -> pseudo) calculation''' + return self.PseudoPosition(px=real_pos.rxy[0], + py=real_pos.rxy[1]) From 774a07802fcfd1b7158eecc8f8798b37ecfa32aa Mon Sep 17 00:00:00 2001 From: Kesavan Subburam Date: Sat, 8 May 2021 01:08:05 +0530 Subject: [PATCH 16/19] #D02-3 livetiffexport of images for manual access --- deepthought/configs.py | 21 +++++++++++++++------ deepthought/microscope.py | 25 ++++++++++++++++++------- 2 files changed, 33 insertions(+), 13 deletions(-) diff --git a/deepthought/configs.py b/deepthought/configs.py index b8aff7d..181a47c 100644 --- a/deepthought/configs.py +++ b/deepthought/configs.py @@ -1,11 +1,13 @@ -import os from collections import OrderedDict +import os config = OrderedDict() + def abspath(path): return os.path.abspath(path) + def read_env_value(name, default): if name in os.environ: value = os.environ[name] @@ -13,14 +15,21 @@ def read_env_value(name, default): value = default return value + MM_DIR = { - "name" : "MM_DIR", - "default" : "C:\Program Files\Micro-Manager-2.0gamma" + "name": "MM_DIR", + "default": "C:\Program Files\Micro-Manager-2.0gamma" } MM_CONFIG = { - "name" : "MM_CONFIG", - "default" : "./mmconfigs/Bright_Star.cfg" + "name": "MM_CONFIG", + "default": "./mmconfigs/Bright_Star.cfg" +} +MM_SERVER = { + "name": "MM_SERVER", + "default": "localhost", } - config["mm_dir"] = abspath(read_env_value(**MM_DIR)) config["mm_config"] = abspath(read_env_value(**MM_CONFIG)) +config["mm_server"] = {"addr": read_env_value(**MM_SERVER), "port": 18861} +store_disk = True +# feature toggle diff --git a/deepthought/microscope.py b/deepthought/microscope.py index fe4b17d..1010979 100644 --- a/deepthought/microscope.py +++ b/deepthought/microscope.py @@ -11,11 +11,11 @@ from bluesky.callbacks.best_effort import BestEffortCallback from bluesky.plans import count, scan from databroker import Broker - +from configs import store_disk from devices import Camera, Focus # other devices have to be added. -# to figure out where +# to figure out where bec = BestEffortCallback() @@ -28,6 +28,17 @@ RE.subscribe(bec) RE.subscribe(db.insert) +if store_disk: + from bluesky.callbacks.broker import LiveTiffExporter + + template = "output_dir/{start[scan_id]}/{event[seq_num]}.tiff" + live = LiveTiffExporter("camera", + template=template, + db=db, + overwrite=True) + RE.subscribe(live) + + class Microscope: def __init__(self, mmc): self.name = None @@ -39,12 +50,12 @@ def __init__(self, mmc): def snap(self, num=1, delay=0): # run a blue sky count method with cameras # return uid - uid, = RE(count(self._cam, num=num, delay=None)) - + uid, = RE(count(self._cam, num=num, delay=delay)) + print(uid) + # https://nsls-ii.github.io/databroker/generated/databroker.Header.table.html#databroker.Header.table - header = db[uid] + header = db[uid] img = np.stack(header.table()["camera"].array) - - return img + return img From f4447c8fee3c03beb25d99b83f76f624031d0b8c Mon Sep 17 00:00:00 2001 From: Kesavan Subburam Date: Tue, 11 May 2021 01:35:38 +0530 Subject: [PATCH 17/19] working xy scan code --- deepthought/devices.py | 187 ++++++++++++++++++++++++++------------ deepthought/microscope.py | 30 ++++-- deepthought/run.py | 20 ++-- 3 files changed, 162 insertions(+), 75 deletions(-) diff --git a/deepthought/devices.py b/deepthought/devices.py index 5c411b0..f885888 100644 --- a/deepthought/devices.py +++ b/deepthought/devices.py @@ -12,15 +12,28 @@ from collections import OrderedDict from typing import Any, Dict, List, Tuple, TypeVar +from ophyd import Component +from ophyd import DynamicDeviceComponent as DDCpt +from ophyd import PseudoPositioner +from ophyd import PseudoSingle +from ophyd import SoftPositioner +from ophyd.pseudopos import pseudo_position_argument +from ophyd.pseudopos import real_position_argument + import numpy as np from ophyd.status import Status +from ophyd.status import MoveStatus +from ophyd.mixins import SignalPositionerMixin from ophyd import Signal -from ophyd import (Component as Cpt) -from ophyd import (PseudoPositioner, PseudoSingle) -from ophyd.pseudopos import (pseudo_position_argument, - real_position_argument) from skimage import io +import warnings +from comms import client + + +def get_mmc(): + mmc = client(addr="10.10.1.35", port=18861).mmc + return mmc class BaseScope: @@ -122,6 +135,7 @@ def frame_crop(image, size=512, tol=100): class SimMMC: """This is a simulated microscope that returns a 512x512 image.""" + def __init__(self): self.pos = 0 self.xy = [0, 0] @@ -148,11 +162,10 @@ def getPosition(self): def setXYPosition(self, value): self.xy = value return - + def getXYPosition(self): return self.xy - def waitForDevice(self, label): time.sleep(1) return @@ -172,7 +185,7 @@ def __init__(self, mmc): def trigger(self): status = Status(obj=self, timeout=10) - + def wait(): try: self.mmc.waitForDevice(self.mmc_device_name) @@ -184,22 +197,22 @@ def wait(): threading.Thread(target=wait).start() return status - + def read(self): data = OrderedDict() data['z'] = {'value': self.mmc.getPosition(), 'timestamp': time.time()} - return data + return data def describe(self): data = OrderedDict() - data['z'] = {'source': "MMCore", + data['z'] = {'source': "MMCore", 'dtype': "number", - 'shape' : []} - return data - + 'shape': []} + return data def set(self, value): status = Status(obj=self, timeout=5) + def wait(): try: self.mmc.setPosition(float(value)) @@ -231,7 +244,7 @@ def __init__(self, mmc): self.mmc_device_name = str(self.mmc.getCameraDevice()) self.image = None - + self.configure() self._subscribers = [] def _collection_callback(self): @@ -240,7 +253,7 @@ def _collection_callback(self): def trigger(self): status = Status(obj=self, timeout=10) - + def wait(): try: self.image_time = time.time() @@ -260,6 +273,18 @@ def wait(): return status + def configure(self): + cam_name = self.mmc.getCameraDevice() + + def configure_cam(prop, idx): + values = self.mmc.getAllowedPropertyValues(cam_name, prop) + self.mmc.setProperty(cam_name, prop, values[idx]) + return self.mmc.getProperty(cam_name, prop) + + print(configure_cam("Binning", -1)) + print(configure_cam("PixelReadoutRate", 0)) + print(configure_cam("Sensitivity/DynamicRange", 0)) + def read(self) -> OrderedDict: data = OrderedDict() data['camera'] = {'value': self.image, 'timestamp': self.image_time} @@ -267,10 +292,10 @@ def read(self) -> OrderedDict: def describe(self): data = OrderedDict() - data['camera'] = {'source': self.mmc_device_name, - 'dtype': 'array', - 'shape' : self.image.shape} - return data + data['camera'] = {'source': self.mmc_device_name, + 'dtype': 'array', + 'shape': self.image.shape} + return data def subscribe(self, func): if not func in self._subscribers: @@ -291,36 +316,36 @@ def __init__(self, mmc): self.mmc = mmc self.mmc_device_name = self.mmc.getXYStageDevice() - def trigger(self): - status = Status(obj=self, timeout=10) - - def wait(): - try: - self.mmc.waitForDevice(self.mmc_device_name) - except Exception as exc: - status.set_exception(exc) - else: - status.set_finished() - - threading.Thread(target=wait).start() - return status + status = Status(obj=self, timeout=10) + + def wait(): + try: + self.mmc.waitForDevice(self.mmc_device_name) + except Exception as exc: + status.set_exception(exc) + else: + status.set_finished() + + threading.Thread(target=wait).start() + return status def read(self): data = OrderedDict() - data['xy'] = {'value': self.mmc.getXYPosition(), 'timestamp': time.time()} + data['xy'] = {'value': self.mmc.getXYPosition(), + 'timestamp': time.time()} return data def describe(self): data = OrderedDict() - data['xy'] = {'source': "MMCore", - 'dtype': "number", - 'shape' : []} - return data - - + data['xy'] = {'source': "MMCore", + 'dtype': "number", + 'shape': []} + return data + def set(self, value): status = Status(obj=self, timeout=5) + def wait(): try: self.mmc.setXYPosition(*value) @@ -341,28 +366,74 @@ def describe_configuration(self) -> OrderedDict: return OrderedDict() -class Stage(PseudoPositioner): +class SoftMMCPositioner(SignalPositionerMixin, Signal): + + _move_thread = None + + def __init__(self, *args, mmc=None, **kwargs): + self.mmc = get_mmc() + self.mmc_device_name = self.mmc.getXYStageDevice() + + super().__init__(*args, set_func=self._write_xy, **kwargs) + + # get the position from the controller on startup + self._readback = np.array(self.mmc.getXYPosition()) + + def _write_xy(self, value, **kwargs): + if self._move_thread is not None: + # The MoveStatus object defends us; this is just an additional safeguard. + # Do not ever expect to see this warning. + warnings.warn("Already moving. Will not start new move.") + st = MoveStatus(self, target=value) + + def moveXY(): + self.mmc.setXYPosition(*value) + # ALWAYS wait for the device + self.mmc.waitForDevice(self.mmc_device_name) + + # update the _readback attribute (which triggers other ophyd actions) + # np.array on the netref object forces conversion to np.array + self._readback = np.array(self.mmc.getXYPosition()) + + # MUST set to None BEFORE declaring status True + self._move_thread = None + st.set_finished() + + self._move_thread = threading.Thread(target=moveXY) + self._move_thread.start() + return st + + +class TwoD_XY_StagePositioner(PseudoPositioner): + # The pseudo positioner axes: - px = Cpt(PseudoSingle) - py = Cpt(PseudoSingle) - + x = Component(PseudoSingle, target_initial_position=True) + y = Component(PseudoSingle, target_initial_position=True) + # The real (or physical) positioners: - rxy = Cpt(XYStage, self.mmc) # FIXME: - - def __init__(self, prefix='', mmc=None, *, **kwargs): - if mmc is None: - raise ValueError("Must supply the 'mmc' object.") - self.mmc = mmc - - # now, tell the PseudoPositioner to construct itself - super().__init__(prefix=prefix, **kwargs) + # NOTE: ``mmc`` object MUST be defined`` first. + pair = Component(SoftMMCPositioner, mmc=get_mmc()) @pseudo_position_argument def forward(self, pseudo_pos): - '''Run a forward (pseudo -> real) calculation''' - return self.RealPosition(rxy=(pseudo_pos.px, pseudo_pos.py)) - @real_position_argument + """Run a forward (pseudo -> real) calculation (return pair).""" + return self.RealPosition(pseudo_pos) + + # @real_position_argument def inverse(self, real_pos): - '''Run an inverse (real -> pseudo) calculation''' - return self.PseudoPosition(px=real_pos.rxy[0], - py=real_pos.rxy[1]) + """Run an inverse (real -> pseudo) calculation (return x & y).""" + if len(real_pos) == 1: + if real_pos.pair is None: + # as called from .move() + x, y = self.pair.mmc.getXYPosition() + else: + # initial call, get position from the hardware + x, y = tuple(real_pos.pair) + elif len(real_pos) == 2: + # as called directly + x, y = real_pos + else: + raise ValueError( + f"Incorrect argument: {self.name}.inverse({real_pos})" + ) + return self.PseudoPosition(x=x, y=y) diff --git a/deepthought/microscope.py b/deepthought/microscope.py index 1010979..4261ea3 100644 --- a/deepthought/microscope.py +++ b/deepthought/microscope.py @@ -4,15 +4,15 @@ 1. configure number of cameras and camera parameters easily from user code. 2. xy, z into a position object. Stage.x - + """ import numpy as np from bluesky import RunEngine from bluesky.callbacks.best_effort import BestEffortCallback -from bluesky.plans import count, scan +from bluesky.plans import count, scan, spiral_square from databroker import Broker from configs import store_disk -from devices import Camera, Focus +from devices import Camera, Focus, TwoD_XY_StagePositioner, get_mmc # other devices have to be added. # to figure out where @@ -40,18 +40,17 @@ class Microscope: - def __init__(self, mmc): + def __init__(self): self.name = None - self.mmc = mmc - self._cam = [Camera(mmc)] - self.z = Focus(mmc) - self.xy = None + self.mmc = get_mmc() + self._cam = [Camera(self.mmc)] + self.z = Focus(self.mmc) + self.stage = TwoD_XY_StagePositioner("", name="xy_stage") def snap(self, num=1, delay=0): # run a blue sky count method with cameras # return uid uid, = RE(count(self._cam, num=num, delay=delay)) - print(uid) # https://nsls-ii.github.io/databroker/generated/databroker.Header.table.html#databroker.Header.table header = db[uid] @@ -59,3 +58,16 @@ def snap(self, num=1, delay=0): img = np.stack(header.table()["camera"].array) return img + + def scan(self, center=None, range=None, num=None): + if center is None: + center = self.mmc.getXYPosition() + x_center, y_center = center + + plan = spiral_square(self._cam, self.stage.x, self.stage.y, x_center=x_center, y_center=y_center, + x_range=1000, y_range=1000, x_num=5, y_num=5) + + uid, = RE(plan) + header = db[uid] + img = np.stack(header.table()["camera"].array) + return img diff --git a/deepthought/run.py b/deepthought/run.py index 32bece6..94b3aba 100644 --- a/deepthought/run.py +++ b/deepthought/run.py @@ -1,15 +1,19 @@ -from comms import client -from devices import SimMMC +import sys +import traceback from microscope import Microscope from detection import detect_object from viz import imshow # https://valelab4.ucsf.edu/~MM/doc/MMCore/html/class_c_m_m_core.html -# mmc = client(addr="10.10.1.62", port=18861).mmc -mmc = SimMMC() + # bs - bright star microscope -bs = Microscope(mmc) -images = bs.snap(num=4) -labels = detect_object(images) -imshow(images, labels) +bs = Microscope() + +imgs = bs.scan() + +bs.mmc.setCameraDevice("right_port") + +# images = bs.snap(num=1) +# labels=detect_object(images) +# imshow(images) From e02f4ab1a3a3ac241e143a2dff2db3aabc1e2df5 Mon Sep 17 00:00:00 2001 From: Kesavan Subburam Date: Tue, 11 May 2021 13:51:22 +0530 Subject: [PATCH 18/19] channel config --- deepthought/devices.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/deepthought/devices.py b/deepthought/devices.py index f885888..4101e85 100644 --- a/deepthought/devices.py +++ b/deepthought/devices.py @@ -281,9 +281,14 @@ def configure_cam(prop, idx): self.mmc.setProperty(cam_name, prop, values[idx]) return self.mmc.getProperty(cam_name, prop) + def configure_channel(channel): + self.mmc.setConfig("channel", channel) + return channel + print(configure_cam("Binning", -1)) print(configure_cam("PixelReadoutRate", 0)) print(configure_cam("Sensitivity/DynamicRange", 0)) + print(configure_channel("DAPI")) def read(self) -> OrderedDict: data = OrderedDict() From 8d186b7d93bf53657edd42ecc68f22c66a30a1c1 Mon Sep 17 00:00:00 2001 From: Kesavan Subburam Date: Tue, 11 May 2021 14:03:42 +0530 Subject: [PATCH 19/19] cleaned up run --- deepthought/run.py | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/deepthought/run.py b/deepthought/run.py index 94b3aba..796d322 100644 --- a/deepthought/run.py +++ b/deepthought/run.py @@ -1,19 +1,14 @@ -import sys -import traceback from microscope import Microscope from detection import detect_object from viz import imshow -# https://valelab4.ucsf.edu/~MM/doc/MMCore/html/class_c_m_m_core.html - # bs - bright star microscope bs = Microscope() +bs.mmc.setCameraDevice("right_port") -imgs = bs.scan() +imgs = bs.snap() -bs.mmc.setCameraDevice("right_port") -# images = bs.snap(num=1) -# labels=detect_object(images) -# imshow(images) +labels = detect_object(imgs) +imshow(imgs, labels)