def __init__(self, name, x, y, z, milling_angle, streams=None): """ :param name: (string) the feature name :param x: (float) the X axis of the feature position :param y: (float) the Y axis of the feature position :param z: (float) the Z axis of the feature position :param milling_angle: (float) angle used for milling (angle between the sample and the ion-beam, similar to the one in the chamber tab, not the actual Rx) :param streams: (List of StaticStream) list of acquired streams on this feature """ self.name = model.StringVA(name) # The 3D position of an interesting point in the site (Typically, the milling should happen around that # volume, never touching it.) self.pos = model.TupleContinuous( (x, y, z), range=((-1, -1, -1), (1, 1, 1)), cls=(int, float), ) # TODO: Check if negative milling angle is allowed if milling_angle <= 0: milling_angle = DEFAULT_MILLING_ANGLE logging.warning( f"Given milling angle {milling_angle} is negative, setting it to default {DEFAULT_MILLING_ANGLE}" ) self.milling_angle = model.FloatVA(milling_angle) self.status = model.StringVA(FEATURE_ACTIVE) # TODO: Handle acquired files self.streams = streams if streams is not None else model.ListVA()
def __init__(self, microscope, main_app): super(CLAcqPlugin, self).__init__(microscope, main_app) # Can only be used with a microscope if not microscope: return else: # Check which stream the microscope supports main_data = self.main_app.main_data if not (main_data.ccd and main_data.ebeam): return self.exposureTime = main_data.ccd.exposureTime self.binning = main_data.ccd.binning # Trick to pass the component (ccd to binning_1d_from_2d()) self.vaconf["binning"]["choices"] = ( lambda cp, va, cf: gui.conf.util.binning_1d_from_2d( main_data.ccd, va, cf)) self.xres = model.IntContinuous(10, (1, 1000), unit="px") self.yres = model.IntContinuous(10, (1, 1000), unit="px") self.stepsize = model.FloatVA(1e-6, unit="m") # Just to show # Maximum margin is half the CCD FoV ccd_rect = get_ccd_fov(main_data.ccd) max_margin = max(ccd_rect[2] - ccd_rect[0], ccd_rect[3] - ccd_rect[1]) / 2 self.roi_margin = model.FloatContinuous(0, (0, max_margin), unit="m") self.filename = model.StringVA("a.tiff") self.xres.subscribe(self._update_stepsize) self.yres.subscribe(self._update_stepsize) self.addMenu("Acquisition/CL acquisition...", self.start)
def __init__(self, fake_img): """ Use .fake_img to change the image sent by the ccd Args: fake_img: 2D DataArray """ super(FakeCCD, self).__init__("testccd", "ccd") self.exposureTime = model.FloatContinuous(0.1, (1e-6, 1000), unit="s") res = fake_img.shape[1], fake_img.shape[0] # X, Y depth = 2 ** (fake_img.dtype.itemsize * 8) self.shape = (res[0], res[1], depth) self.binning = model.TupleContinuous((1, 1), [(1, 1), (8, 8)], cls=(int, long, float), unit="") self.resolution = model.ResolutionVA(res, [(1, 1), res]) self.readoutRate = model.FloatVA(1e9, unit="Hz", readonly=True) pxs_sens = fake_img.metadata.get(model.MD_SENSOR_PIXEL_SIZE, (10e-6, 10e-6)) self.pixelSize = model.VigilantAttribute(pxs_sens, unit="m", readonly=True) self.data = CCDDataFlow(self) self._acquisition_thread = None self._acquisition_lock = threading.Lock() self._acquisition_init_lock = threading.Lock() self._acquisition_must_stop = threading.Event() self.fake_img = fake_img self._metadata = fake_img.metadata
def __init__(self, name): model.Emitter.__init__(self, name, "fakeebeam", parent=None) self._shape = (2048, 2048) self.resolution = model.ResolutionVA((256, 256), [(1, 1), self._shape]) self.pixelSize = model.VigilantAttribute((1e-9, 1e-9), unit="m", readonly=True) self.magnification = model.FloatVA(1000.)
def test_readonly(self): prop = model.FloatVA(2.0, readonly=True) try: prop.value = 6.0 self.fail("Modifying a readonly property should not be allowed.") except model.NotSettableError: pass # as it should be self.assertTrue(prop.value == 2)
def test_setter(self): """ check the delegation """ prop = model.FloatVA(2.0, setter=self.delegate_set_float) # maybe it should be 3.0? But it's better not to call the delegate setter # anyway, the owner can always also call the setter by itself self.assertEqual(prop.value, 2.0) prop.value = 10.0 self.assertEqual(prop.value, 11.0)
def __init__(self, name): self.name = model.StringVA(name) # a thumbnail version of what is displayed self.thumbnail = VigilantAttribute(None) # contains a wx.Image # Last time the image of the view was changed. It's actually mostly # a trick to allow other parts of the GUI to know when the (theoretical) # composited image has changed. self.lastUpdate = model.FloatVA(time.time(), unit="s")
def __init__(self, name): model.Emitter.__init__(self, name, "fakeebeam", parent=None) self._shape = (2048, 2048) self.dwellTime = model.FloatContinuous(1e-6, (1e-6, 1), unit="s") self.resolution = model.ResolutionVA((256, 256), [(1, 1), self._shape]) self.pixelSize = model.VigilantAttribute((1e-9, 1e-9), unit="m", readonly=True) self.magnification = model.FloatVA(1000.) self.scale = model.TupleContinuous((1, 1), [(1, 1), self._shape], cls=(int, long, float), unit="") self.translation = model.TupleContinuous((0, 0), ((0, 0), (0, 0)), cls=(int, long, float), unit="px")
def test_notify_init(self): prop = model.FloatVA(2.0) self.called = 0 # now count prop.subscribe(self.callback_test_notify, init=True) # +1 prop.value = 3.0 # +1 prop.value = 0 # +1 prop.value = 0.0 # nothing because same value try: prop.value = "coucou" self.fail("Assigning string to a float should not be allowed.") except TypeError: pass # as it should be prop.unsubscribe(self.callback_test_notify) prop.value = 12 # no more counting self.assertTrue(prop.value == 12) self.assertTrue(self.called == 3)
def test_getter_setter(self): """ check the delegation to getter and setter """ propt = model.FloatVA(time.time(), getter=self.delegate_get_float, setter=self.delegate_set_float, unit="s") self.called = 0 # now count propt.subscribe(self.callback_test_notify) t1 = propt.value # time now propt.value = 10.0 # will "set" 11 != time, => +1 change t2 = propt.value # new time self.assertGreater(t2, t1) self.assertEqual(self.called, 1) propt.unsubscribe(self.callback_test_notify)
def test_weakref(self): """ checks that even if an object has a method subscribed to a property, it will be garbage-collected when not used anymore and its subscription dropped. """ prop = model.FloatVA(2.0) o = LittleObject() wo = weakref.ref(o) assert (wo() is not None) prop.subscribe(o.callback) prop.value = 6.0 # +1 assert (o.called == 1) del o assert (wo() is None) prop.value = 1 assert (prop.value == 1)
def __init__(self, microscope, main_app): super(CLAcqPlugin, self).__init__(microscope, main_app) # Can only be used with a microscope if not microscope: return else: # Check which stream the microscope supports main_data = self.main_app.main_data if not (main_data.ccd and main_data.ebeam): return self.exposureTime = main_data.ccd.exposureTime self.binning = main_data.ccd.binning self.xres = model.IntContinuous(10, (1, 1000), unit="px") self.yres = model.IntContinuous(10, (1, 1000), unit="px") self.stepsize = model.FloatVA(1e-6, unit="m") # Just to show self.filename = model.StringVA("a.tiff") self.xres.subscribe(self._update_stepsize) self.yres.subscribe(self._update_stepsize) self.addMenu("Acquisition/CL acquisition...", self.start)
def __init__(self, name, role, port, sources, _serial=None, **kwargs): """ port (string): name of the serial port to connect to. Can be a pattern, in which case, all the ports fitting the pattern will be tried, and the first one which looks like an LLE will be used. sources (dict string -> 5-tuple of float): the light sources (by colour). The string is one of the seven names for the sources: "red", "cyan", "green", "UV", "yellow", "blue", "teal". They correspond to fix number in the LLE (cf documentation). The tuple contains the wavelength in m for the 99% low, 25% low, centre/max, 25% high, 99% high. They do no have to be extremely precise. The most important is the centre, and that they are all increasing values. If the device doesn't have the source it can be skipped. _serial (serial): for internal use only, directly use a opened serial connection """ # start with this opening the port: if it fails, we are done if _serial is not None: self._try_recover = False self._serial = _serial self._port = "" else: self._serial, self._port = self._findDevice(port) logging.info("Found LLE device on port %s", self._port) self._try_recover = True # to acquire before sending anything on the serial port self._ser_access = threading.Lock() # Init the LLE self._initDevice() if _serial is not None: # used for port testing => only simple init return # parse source and do some sanity check if not sources or not isinstance(sources, dict): logging.error( "sources argument must be a dict of source name -> wavelength 5 points" ) raise ValueError("Incorrect sources argument") self._source_id = [] # source number for each spectra self._gy = [] # indexes of green and yellow source self._rcubt = [] # indexes of other sources spectra = [] # list of the 5 wavelength points for cn, wls in sources.items(): cn = cn.lower() if cn not in COLOUR_TO_SOURCE: raise ValueError( "Sources argument contains unknown colour '%s'" % cn) if len(wls) != 5: raise ValueError( "Sources colour '%s' doesn't have exactly 5 wavelength points" % cn) prev_wl = 0 for wl in wls: if 0 > wl or wl > 100e-6: raise ValueError( "Sources colour '%s' has unexpected wavelength = %f nm" % (cn, wl * 1e9)) if prev_wl > wl: raise ValueError( "Sources colour '%s' has unsorted wavelengths" % cn) self._source_id.append(COLOUR_TO_SOURCE[cn]) if cn in ["green", "yellow"]: self._gy.append(len(spectra)) else: self._rcubt.append(len(spectra)) spectra.append(tuple(wls)) model.Emitter.__init__(self, name, role, **kwargs) self._shape = () self._max_power = 0.4 # W (According to doc: ~400mW) self.power = model.FloatContinuous(0., (0., self._max_power), unit="W") # emissions is list of 0 <= floats <= 1. self._intensities = [0.] * len(spectra) # start off self.emissions = model.ListVA(list(self._intensities), unit="", setter=self._setEmissions) self.spectra = model.ListVA(spectra, unit="m", readonly=True) self._prev_intensities = [None] * len( spectra) # => will update for sure self._updateIntensities() # turn off every source self.power.subscribe(self._updatePower) # set HW and SW version self._swVersion = "%s (serial driver: %s)" % ( odemis.__version__, driver.getSerialDriver(self._port)) self._hwVersion = "Lumencor Light Engine" # hardware doesn't report any version # Update temperature every 10s current_temp = self.GetTemperature() self.temperature = model.FloatVA(current_temp, unit=u"°C", readonly=True) self._temp_timer = util.RepeatingTimer(10, self._updateTemperature, "LLE temperature update") self._temp_timer.start()
def __init__(self, name, role, mag, mag_choices=None, na=0.95, ri=1, pole_pos=None, x_max=None, hole_diam=None, focus_dist=None, parabola_f=None, rotation=None, **kwargs): """ name (string): should be the name of the product (for metadata) mag (float > 0): magnification ratio mag_choices (None, list of floats > 0): list of allowed magnification ratio. If None, the magnification will be allowed for any value between 1e-3 to 1e6. na (float > 0): numerical aperture ri (0.01 < float < 100): refractive index pole_pos (2 floats > 0): position of the pole on the CCD (in px, without binning, with the top-left pixel as origin). Used for angular resolved imaging on SPARC (only). cf MD_AR_POLE x_max (float): the distance between the parabola origin and the cutoff position (in meters). Used for angular resolved imaging on SPARC (only). cf MD_AR_XMAX hole_diam (float): diameter of the hole in the mirror (in meters). Used for angular resolved imaging on SPARC (only). cf MD_AR_HOLE_DIAMETER focus_dist (float): the vertical mirror cutoff, iow the min distance between the mirror and the sample (in meters). Used for angular resolved imaging on SPARC (only). cf MD_AR_FOCUS_DISTANCE parabola_f (float): parabola_parameter=1/4f. Used for angular resolved imaging on SPARC (only). cf MD_AR_PARABOLA_F rotation (0<float<2*pi): rotation between the Y axis of the SEM referential and the optical path axis. Used on the SPARC to report the rotation between the AR image and the SEM image. """ assert (mag > 0) model.HwComponent.__init__(self, name, role, **kwargs) self._swVersion = "N/A (Odemis %s)" % odemis.__version__ self._hwVersion = name # allow the user to modify the value, if the lens is manually changed if mag_choices is None: self.magnification = model.FloatContinuous(mag, range=(1e-3, 1e6), unit="") else: mag_choices = frozenset(mag_choices) if mag not in mag_choices: raise ValueError("mag (%s) is not within the mag_choices %s" % (mag, mag_choices)) self.magnification = model.FloatEnumerated(mag, choices=mag_choices, unit="") self.numericalAperture = model.FloatContinuous(na, range=(1e-6, 1e3), unit="") self.refractiveIndex = model.FloatContinuous(ri, range=(0.01, 10), unit="") if pole_pos is not None: if (not isinstance(pole_pos, collections.Iterable) or len(pole_pos) != 2 or any(not 0 < v < 1e6 for v in pole_pos)): raise ValueError("pole_pos must be 2 positive values, got %s" % pole_pos) self.polePosition = model.ResolutionVA(tuple(pole_pos), rng=((0, 0), (1e6, 1e6)), unit="px") if x_max is not None: self.xMax = model.FloatVA(x_max, unit="m") if hole_diam is not None: self.holeDiameter = model.FloatVA(hole_diam, unit="m") if focus_dist is not None: self.focusDistance = model.FloatVA(focus_dist, unit="m") if parabola_f is not None: self.parabolaF = model.FloatVA(parabola_f, unit="m") if rotation is not None: self.rotation = model.FloatContinuous(rotation, (0, 2 * math.pi), unit="rad")
def __init__(self, name, role, mag, mag_choices=None, na=0.95, ri=1, pole_pos=None, x_max=None, hole_diam=None, focus_dist=None, parabola_f=None, rotation=None, configurations=None, **kwargs): """ name (string): should be the name of the product (for metadata) mag (float > 0): magnification ratio mag_choices (None, list of floats > 0): list of allowed magnification ratio. If None, the magnification will be allowed for any value between 1e-3 to 1e6. na (float > 0): numerical aperture ri (0.01 < float < 100): refractive index pole_pos (2 floats > 0): position of the pole on the CCD (in px, without binning, with the top-left pixel as origin). Used for angular resolved imaging on SPARC (only). cf MD_AR_POLE x_max (float): the distance between the parabola origin and the cutoff position (in meters). Used for angular resolved imaging on SPARC (only). cf MD_AR_XMAX hole_diam (float): diameter of the hole in the mirror (in meters). Used for angular resolved imaging on SPARC (only). cf MD_AR_HOLE_DIAMETER focus_dist (float): the vertical mirror cutoff, iow the min distance between the mirror and the sample (in meters). Used for angular resolved imaging on SPARC (only). cf MD_AR_FOCUS_DISTANCE parabola_f (float): parabola_parameter=1/4f. Used for angular resolved imaging on SPARC (only). cf MD_AR_PARABOLA_F rotation (0<float<2*pi): rotation between the Y axis of the SEM referential and the optical path axis. Used on the SPARC to report the rotation between the AR image and the SEM image. configurations (dict str -> (dict str -> value)): {configuration name -> {attribute name -> value}} All the configurations supported and their settings. A "configuration" is a set of attributes with predefined values. When this argument is specified, a .configuration attribute will be available, with each configuration name, and changing it will automatically set all the associated attributes to their predefined value. """ assert (mag > 0) model.HwComponent.__init__(self, name, role, **kwargs) self._swVersion = "N/A (Odemis %s)" % odemis.__version__ self._hwVersion = name # allow the user to modify the value, if the lens is manually changed if mag_choices is None: self.magnification = model.FloatContinuous(mag, range=(1e-3, 1e6), unit="") else: mag_choices = frozenset(mag_choices) if mag not in mag_choices: raise ValueError("mag (%s) is not within the mag_choices %s" % (mag, mag_choices)) self.magnification = model.FloatEnumerated(mag, choices=mag_choices, unit="") self.numericalAperture = model.FloatContinuous(na, range=(1e-6, 1e3), unit="") self.refractiveIndex = model.FloatContinuous(ri, range=(0.01, 10), unit="") if pole_pos is not None: if (not isinstance(pole_pos, collections.Iterable) or len(pole_pos) != 2 or any(not 0 < v < 1e6 for v in pole_pos)): raise ValueError("pole_pos must be 2 positive values, got %s" % pole_pos) self.polePosition = model.ResolutionVA(tuple(pole_pos), rng=((0, 0), (1e6, 1e6)), unit="px") if x_max is not None: self.xMax = model.FloatVA(x_max, unit="m") if hole_diam is not None: self.holeDiameter = model.FloatVA(hole_diam, unit="m") if focus_dist is not None: self.focusDistance = model.FloatVA(focus_dist, unit="m") if parabola_f is not None: self.parabolaF = model.FloatVA(parabola_f, unit="m") if rotation is not None: self.rotation = model.FloatContinuous(rotation, (0, 2 * math.pi), unit="rad") if configurations is not None: self._configurations = configurations # Find the configuration which is closest to the current settings def _compare_config(cn): settings = configurations[cn] score = 0 for arg, value in settings.items(): try: vaname = CONFIG_2_VA[arg] va = getattr(self, vaname) except (KeyError, AttributeError): raise ValueError( "Attribute name predefined in the configuration required" ) if value == va.value: score += 1 return score current_conf = max(configurations, key=_compare_config) self.configuration = model.StringEnumerated( current_conf, choices=set(configurations.keys()), setter=self._setConfiguration)
def __init__(self, name, role, image, dependencies=None, daemon=None, blur_factor=1e4, max_res=None, **kwargs): """ dependencies (dict string->Component): If "focus" is passed, and it's an actuator with a z axis, the image will be blurred based on the position, to simulate a focus axis. image (str or None): path to a file to use as fake image (relative to the directory of this class) max_res (tuple of (int, int) or None): maximum resolution to clip simulated image, if None whole image shape will be used. The simulated image will be a part of the original image based on the MD_POS metadata. """ # TODO: support transpose? If not, warn that it's not accepted # fake image setup image = str(image) # ensure relative path is from this file if not os.path.isabs(image): image = os.path.join(os.path.dirname(__file__), image) converter = dataio.find_fittest_converter(image, mode=os.O_RDONLY) self._img = converter.read_data(image)[0] # can be RGB or greyscale model.DigitalCamera.__init__(self, name, role, dependencies=dependencies, daemon=daemon, **kwargs) if self._img.ndim > 3: # remove dims of length 1 self._img = numpy.squeeze(self._img) imshp = self._img.shape if len(imshp) == 3 and imshp[0] in {3, 4}: # CYX, change it to YXC, to simulate a RGB detector self._img = numpy.rollaxis(self._img, 2) # XCY self._img = numpy.rollaxis(self._img, 2) # YXC imshp = self._img.shape def clip_max_res(img_res): if len(max_res) != 2: raise ValueError("Shape of max_res should be = 2.") return tuple(min(x, y) for x, y in zip( img_res, max_res)) # in case max_res > image shape # For RGB, the colour is last dim, but we still indicate it as higher # dimension to ensure shape always starts with X, Y if len(imshp) == 3 and imshp[-1] in {3, 4}: # resolution doesn't affect RGB dim res = imshp[-2::-1] self._img_res = res # Original image shape in case it's clipped if max_res: res = clip_max_res(res) self._shape = res + imshp[-1::] # X, Y, C # indicate it's RGB pixel-per-pixel ordered self._img.metadata[model.MD_DIMS] = "YXC" else: self._img_res = imshp[:: -1] # Original image shape in case it's clipped res = imshp[::-1] if max_res is None else tuple(max_res) if max_res: res = clip_max_res(res) self._shape = res # X, Y,... # TODO: handle non integer dtypes depth = 2**(self._img.dtype.itemsize * 8) self._shape += (depth, ) self._resolution = res self.resolution = model.ResolutionVA(self._resolution, ((1, 1), self._resolution), setter=self._setResolution) self._binning = (1, 1) self.binning = model.ResolutionVA(self._binning, ((1, 1), (16, 16)), setter=self._setBinning) hlf_shape = (self._shape[0] // 2 - 1, self._shape[1] // 2 - 1) tran_rng = [(-hlf_shape[0], -hlf_shape[1]), (hlf_shape[0], hlf_shape[1])] self._translation = (0, 0) self.translation = model.ResolutionVA(self._translation, tran_rng, unit="px", cls=(int, long), setter=self._setTranslation) self._orig_exp = self._img.metadata.get(model.MD_EXP_TIME, 0.1) # s self.exposureTime = model.FloatContinuous(self._orig_exp, (1e-3, 10), unit="s") # Some code care about the readout rate to know how long an acquisition will take self.readoutRate = model.FloatVA(1e9, unit="Hz", readonly=True) pxs = self._img.metadata.get(model.MD_PIXEL_SIZE, (10e-6, 10e-6)) mag = self._img.metadata.get(model.MD_LENS_MAG, 1) spxs = tuple(s * mag for s in pxs) self.pixelSize = model.VigilantAttribute(spxs, unit="m", readonly=True) self._metadata = { model.MD_HW_NAME: "FakeCam", model.MD_SENSOR_PIXEL_SIZE: spxs, model.MD_DET_TYPE: model.MD_DT_INTEGRATING } # Set the amount of blurring during defocusing. self._blur_factor = float(blur_factor) try: focuser = dependencies["focus"] if (not isinstance(focuser, model.ComponentBase) or not hasattr(focuser, "axes") or not isinstance(focuser.axes, dict) or "z" not in focuser.axes): raise ValueError( "focus %s must be a Actuator with a 'z' axis" % (focuser, )) self._focus = focuser # The "good" focus is at the current position self._good_focus = self._focus.position.value["z"] self._metadata[model.MD_FAV_POS_ACTIVE] = {"z": self._good_focus} logging.debug("Simulating focus, with good focus at %g m", self._good_focus) except (TypeError, KeyError): logging.info("Will not simulate focus") self._focus = None # Simple implementation of the flow: we keep generating images and if # there are subscribers, they'll receive it. self.data = SimpleDataFlow(self) self._generator = None # Convenience event for the user to connect and fire self.softwareTrigger = model.Event() # Include a thread which creates or fixes an hardware error in the simcam on the basis of the presence of the # file ERROR_STATE_FILE in model.BASE_DIRECTORY self._is_running = True self._error_creation_thread = threading.Thread( target=self._state_error_run, name="Creating and state error") self._error_creation_thread.daemon = True self._error_creation_thread.start()
def __init__(self, name, role, image, children=None, daemon=None, **kwargs): ''' children (dict string->kwargs): parameters setting for the children. The only possible child is "focus". They will be provided back in the .children VA image (str or None): path to a file to use as fake image (relative to the directory of this class) ''' # TODO: support transpose? If not, warn that it's not accepted # fake image setup image = unicode(image) # change to this directory to ensure relative path is from this file os.chdir(os.path.dirname(unicode(__file__))) exporter = dataio.find_fittest_exporter(image) self._img = exporter.read_data(image)[0] # can be RGB or greyscale # we will fill the set of children with Components later in ._children model.DigitalCamera.__init__(self, name, role, daemon=daemon, **kwargs) if self._img.ndim > 3: # remove dims of length 1 self._img = numpy.squeeze(self._img) imshp = self._img.shape if len(imshp) == 3 and imshp[0] in {3, 4}: # CYX, change it to YXC, to simulate a RGB detector self._img = numpy.rollaxis(self._img, 2) # XCY self._img = numpy.rollaxis(self._img, 2) # YXC imshp = self._img.shape # For RGB, the colour is last dim, but we still indicate it as higher # dimension to ensure shape always starts with X, Y if len(imshp) == 3 and imshp[-1] in {3, 4}: # resolution doesn't affect RGB dim res = imshp[-2::-1] self._shape = res + imshp[-1::] # X, Y, C # indicate it's RGB pixel-per-pixel ordered self._img.metadata[model.MD_DIMS] = "YXC" else: res = imshp[::-1] self._shape = res # X, Y,... # TODO: handle non integer dtypes depth = 2**(self._img.dtype.itemsize * 8) self._shape += (depth, ) # TODO: don't provide range? or don't make it readonly? self.resolution = model.ResolutionVA(res, [res, res]) # , readonly=True) # TODO: support (simulated) binning self.binning = model.ResolutionVA((1, 1), [(1, 1), (1, 1)]) exp = self._img.metadata.get(model.MD_EXP_TIME, 0.1) # s self.exposureTime = model.FloatContinuous(exp, [1e-3, 1e3], unit="s") # Some code care about the readout rate to know how long an acquisition will take self.readoutRate = model.FloatVA(1e9, unit="Hz", readonly=True) pxs = self._img.metadata.get(model.MD_PIXEL_SIZE, (10e-6, 10e-6)) mag = self._img.metadata.get(model.MD_LENS_MAG, 1) spxs = tuple(s * mag for s in pxs) self.pixelSize = model.VigilantAttribute(spxs, unit="m", readonly=True) self._metadata = { model.MD_HW_NAME: "FakeCam", model.MD_SENSOR_PIXEL_SIZE: spxs } try: kwargs = children["focus"] except (KeyError, TypeError): logging.info("Will not simulate focus") self._focus = None else: self._focus = CamFocus(parent=self, daemon=daemon, **kwargs) self.children.value = self.children.value | {self._focus} # Simple implementation of the flow: we keep generating images and if # there are subscribers, they'll receive it. self.data = model.DataFlow(self) self._generator = util.RepeatingTimer(exp, self._generate, "SimCam image generator") self._generator.start()
def __init__(self, name, role, image, children=None, daemon=None, **kwargs): ''' children (dict string->kwargs): parameters setting for the children. The only possible child is "focus". They will be provided back in the .children VA image (str or None): path to a file to use as fake image (relative to the directory of this class) ''' # TODO: support transpose? If not, warn that it's not accepted # fake image setup image = unicode(image) # ensure relative path is from this file if not os.path.isabs(image): image = os.path.join(os.path.dirname(__file__), image) converter = dataio.find_fittest_converter(image, mode=os.O_RDONLY) self._img = converter.read_data(image)[0] # can be RGB or greyscale # we will fill the set of children with Components later in ._children model.DigitalCamera.__init__(self, name, role, daemon=daemon, **kwargs) if self._img.ndim > 3: # remove dims of length 1 self._img = numpy.squeeze(self._img) imshp = self._img.shape if len(imshp) == 3 and imshp[0] in {3, 4}: # CYX, change it to YXC, to simulate a RGB detector self._img = numpy.rollaxis(self._img, 2) # XCY self._img = numpy.rollaxis(self._img, 2) # YXC imshp = self._img.shape # For RGB, the colour is last dim, but we still indicate it as higher # dimension to ensure shape always starts with X, Y if len(imshp) == 3 and imshp[-1] in {3, 4}: # resolution doesn't affect RGB dim res = imshp[-2::-1] self._shape = res + imshp[-1::] # X, Y, C # indicate it's RGB pixel-per-pixel ordered self._img.metadata[model.MD_DIMS] = "YXC" else: res = imshp[::-1] self._shape = res # X, Y,... # TODO: handle non integer dtypes depth = 2 ** (self._img.dtype.itemsize * 8) self._shape += (depth,) self._resolution = res self.resolution = model.ResolutionVA(self._resolution, ((1, 1), self._resolution), setter=self._setResolution) self._binning = (1, 1) self.binning = model.ResolutionVA(self._binning, ((1, 1), (16, 16)), setter=self._setBinning) hlf_shape = (self._shape[0] // 2 - 1, self._shape[1] // 2 - 1) tran_rng = [(-hlf_shape[0], -hlf_shape[1]), (hlf_shape[0], hlf_shape[1])] self._translation = (0, 0) self.translation = model.ResolutionVA(self._translation, tran_rng, cls=(int, long), unit="px", setter=self._setTranslation) exp = self._img.metadata.get(model.MD_EXP_TIME, 0.1) # s self.exposureTime = model.FloatContinuous(exp, (1e-3, 1e3), unit="s") # Some code care about the readout rate to know how long an acquisition will take self.readoutRate = model.FloatVA(1e9, unit="Hz", readonly=True) pxs = self._img.metadata.get(model.MD_PIXEL_SIZE, (10e-6, 10e-6)) mag = self._img.metadata.get(model.MD_LENS_MAG, 1) spxs = tuple(s * mag for s in pxs) self.pixelSize = model.VigilantAttribute(spxs, unit="m", readonly=True) self._metadata = {model.MD_HW_NAME: "FakeCam", model.MD_SENSOR_PIXEL_SIZE: spxs, model.MD_DET_TYPE: model.MD_DT_INTEGRATING} try: kwargs = children["focus"] except (KeyError, TypeError): logging.info("Will not simulate focus") self._focus = None else: self._focus = CamFocus(parent=self, daemon=daemon, **kwargs) self.children.value = self.children.value | {self._focus} # Simple implementation of the flow: we keep generating images and if # there are subscribers, they'll receive it. self.data = SimpleDataFlow(self) self._generator = None # Convenience event for the user to connect and fire self.softwareTrigger = model.Event()
def __init__(self, name, role, channel, node_idx, **kwargs): """ channel (str): channel name of can bus node_idx (int): node index of focus tracker """ model.HwComponent.__init__(self, name, role, **kwargs) # Connect to the CANbus and the CANopen network. self.network = canopen.Network() bustype = 'socketcan' if channel != 'fake' else 'virtual' try: self.network.connect(bustype=bustype, channel=channel) except IOError as exp: if exp.errno == 19: raise HwError("Focus Tracker is not connected.") else: raise self.network.check() object_dict = pkg_resources.resource_filename("odemis.driver", "FocusTracker.eds") if channel == 'fake': self.node = FakeRemoteNode(node_idx, object_dict) else: self.node = canopen.RemoteNode(node_idx, object_dict) self.network.add_node(self.node) self._swVersion = "python-canopen v%s , python-can v%s" % ( canopen.__version__, can.__version__) rev_num = self.node.sdo["Identity Object"]["Revision number"].raw minor = rev_num & 0xffff major = (rev_num >> 16) & 0xffff self._hwVersion = "{}.{}".format(major, minor) # Create SDO communication objects to communicate self._position_sdo = self.node.sdo["AI Input PV"][1] self._target_pos_sdo = self.node.sdo["CO Set Point W"][1] # Read PID gains from the device (and set the current metadata) self._proportional_gain_sdo = self.node.sdo[ 'CO Proportional Band Xp1'][1] self._integral_gain_sdo = self.node.sdo['CO Integral Action Time Tn1'][ 1] self._derivative_gain_sdo = self.node.sdo[ 'CO Derivative Action Time Tv1'][1] self._tracking_sdo = self.node.sdo['Controller On/ Off'][1] # set VAs only if there is hardware, channel = '' if no hardware self._metadata[model.MD_GAIN_P] = self._proportional_gain_sdo.raw self._metadata[model.MD_GAIN_I] = self._integral_gain_sdo.raw self._metadata[model.MD_GAIN_I] = self._derivative_gain_sdo.raw self.targetPosition = model.FloatContinuous( self._target_pos_sdo.raw, TARGET_POSITION_RANGE, unit="m", getter=self._get_target_pos, setter=self._set_target_pos) self.position = model.FloatVA(self._position_sdo.raw, unit="m", readonly=True, getter=self._get_position) self.tracking = model.BooleanVA(self._tracking_sdo.raw, getter=self._get_tracking, setter=self._set_tracking)
def __init__(self, name, role, port, axes, ustepsize, refproc=None, temp=False, **kwargs): """ port (str): port name (use /dev/fake for a simulator) axes (list of str): names of the axes, from the 1st to the 3rd. ustepsize (list of float): size of a microstep in m (the smaller, the bigger will be a move for a given distance in m) refproc (str or None): referencing (aka homing) procedure type. Use None to indicate it's not possible (no reference/limit switch) or the name of the procedure. For now only "2xFinalForward" is accepted. temp (bool): if True, will read the temperature from the analogue input (10 mV <-> 1 °C) inverted (set of str): names of the axes which are inverted (IOW, either empty or the name of the axis) """ if len(axes) != 3: raise ValueError("Axes must be a list of 3 axis names (got %s)" % (axes,)) self._axes_names = axes # axes names in order if len(axes) != len(ustepsize): raise ValueError("Expecting %d ustepsize (got %s)" % (len(axes), ustepsize)) if refproc not in {REFPROC_2XFF, REFPROC_FAKE, None}: raise ValueError("Reference procedure %s unknown" % (refproc, )) self._refproc = refproc for sz in ustepsize: if sz > 10e-3: # sz is typically ~1µm, so > 1 cm is very fishy raise ValueError("ustepsize should be in meter, but got %g" % (sz,)) self._ustepsize = ustepsize try: self._serial = self._openSerialPort(port) except serial.SerialException: raise HwError("Failed to find device %s on port %s. Ensure it is " "connected to the computer." % (name, port)) self._port = port self._ser_access = threading.Lock() self._target = 1 # Always one, when directly connected via USB self._resynchonise() modl, vmaj, vmin = self.GetVersion() if modl != 3110: logging.warning("Controller TMCM-%d is not supported, will try anyway", modl) if name is None and role is None: # For scan only return if port != "/dev/fake": # TODO: support programs in simulator # Detect if it is "USB bus powered" by using the fact that programs # don't run when USB bus powered addr = 80 # big enough to not overlap with REFPROC_2XFF programs prog = [(9, 50, 2, 1), # Set global param 50 to 1 (28,), # STOP ] self.UploadProgram(prog, addr) if not self._isFullyPowered(): # Only a warning, at the power can be connected afterwards logging.warning("Device %s has no power, the motor will not move", name) # will take care of executing axis move asynchronously self._executor = CancellableThreadPoolExecutor(max_workers=1) # one task at a time axes_def = {} for n, sz in zip(self._axes_names, self._ustepsize): # Mov abs supports ±2³¹ but the actual position is only within ±2²³ rng = [(-2 ** 23) * sz, (2 ** 23 - 1) * sz] # Probably not that much, but there is no info unless the axis has # limit switches and we run a referencing axes_def[n] = model.Axis(range=rng, unit="m") model.Actuator.__init__(self, name, role, axes=axes_def, **kwargs) for i, a in enumerate(self._axes_names): self._init_axis(i) driver_name = driver.getSerialDriver(self._port) self._swVersion = "%s (serial driver: %s)" % (odemis.__version__, driver_name) self._hwVersion = "TMCM-%d (firmware %d.%02d)" % (modl, vmaj, vmin) self.position = model.VigilantAttribute({}, unit="m", readonly=True) self._updatePosition() # TODO: add support for changing speed. cf p.68: axis param 4 + p.81 + TMC 429 p.6 self.speed = model.VigilantAttribute({}, unit="m/s", readonly=True) self._updateSpeed() if refproc is not None: # str -> boolean. Indicates whether an axis has already been referenced axes_ref = dict([(a, False) for a in axes]) self.referenced = model.VigilantAttribute(axes_ref, readonly=True) if temp: # One sensor is at the top, one at the bottom of the sample holder. # The most interesting is the temperature difference, so just # report both. self.temperature = model.FloatVA(0, unit=u"°C", readonly=True) self.temperature1 = model.FloatVA(0, unit=u"°C", readonly=True) self._temp_timer = util.RepeatingTimer(10, self._updateTemperatureVA, "TMCM temperature update") self._updateTemperatureVA() # make sure the temperature is correct self._temp_timer.start()
def __init__(self, name, role, image, dependencies=None, daemon=None, blur_factor=1e4, resolution=None, **kwargs): ''' dependencies (dict string->Component): If "focus" is passed, and it's an actuator with a z axis, the image will be blurred based on the position, to simulate a focus axis. image (str or None): path to a file to use as fake image (relative to the directory of this class) ''' # TODO: support transpose? If not, warn that it's not accepted # fake image setup image = unicode(image) # ensure relative path is from this file if not os.path.isabs(image): image = os.path.join(os.path.dirname(__file__), image) converter = dataio.find_fittest_converter(image, mode=os.O_RDONLY) self._imgs = converter.read_data(image) # can be RGB or greyscale model.DigitalCamera.__init__(self, name, role, dependencies=dependencies, daemon=daemon, **kwargs) for i, img in enumerate(self._imgs): if img.ndim > 3: # remove dims of length 1 self._imgs[i] = numpy.squeeze(img) imshp = img.shape if len(imshp) == 3 and imshp[i] in {3, 4}: # CYX, change it to YXC, to simulate a RGB detector self._imgs[i] = util.img.ensureYXC(img) for img in self._imgs[1:]: if self._imgs[0].shape != self._imgs[i].shape: raise ValueError("all images must have the same resolution") imshp = self._imgs[0].shape # For RGB, the colour is last dim, but we still indicate it as higher # dimension to ensure shape always starts with X, Y if len(imshp) == 3 and imshp[-1] in {3, 4}: # resolution doesn't affect RGB dim if resolution: if resolution >= imshp[-2::-1]: res = tuple(resolution) else: res = imshp[-2::-1] self._shape = res + imshp[-1::] # X, Y, C else: if resolution: res = tuple(resolution) else: res = imshp[::-1] self._shape = res # X, Y,... # TODO: handle non integer dtypes depth = 2**(self._imgs[0].dtype.itemsize * 8) self._shape += (depth, ) self._resolution = res self.resolution = model.ResolutionVA(self._resolution, ((1, 1), self._resolution), setter=self._setResolution) self._binning = (1, 1) self.binning = model.ResolutionVA(self._binning, ((1, 1), (16, 16)), setter=self._setBinning) hlf_shape = (self._shape[0] // 2 - 1, self._shape[1] // 2 - 1) tran_rng = [(-hlf_shape[0], -hlf_shape[1]), (hlf_shape[0], hlf_shape[1])] self._translation = (0, 0) self.translation = model.ResolutionVA(self._translation, tran_rng, cls=(int, long), unit="px", setter=self._setTranslation) exp = self._imgs[0].metadata.get(model.MD_EXP_TIME, 0.1) # s self.exposureTime = model.FloatContinuous(exp, (1e-3, 1e3), unit="s") # Some code care about the readout rate to know how long an acquisition will take self.readoutRate = model.FloatVA(1e9, unit="Hz", readonly=True) pxs = self._imgs[0].metadata.get(model.MD_PIXEL_SIZE, (10e-6, 10e-6)) mag = self._imgs[0].metadata.get(model.MD_LENS_MAG, 1) spxs = tuple(s * mag for s in pxs) self.pixelSize = model.VigilantAttribute(spxs, unit="m", readonly=True) self._metadata = { model.MD_HW_NAME: "FakeCam", model.MD_SENSOR_PIXEL_SIZE: spxs, model.MD_DET_TYPE: model.MD_DT_INTEGRATING, model.MD_PIXEL_SIZE: pxs } # Set the amount of blurring during defocusing. self._blur_factor = float(blur_factor) try: focuser = dependencies["focus"] if (not isinstance(focuser, model.ComponentBase) or not hasattr(focuser, "axes") or not isinstance(focuser.axes, dict) or "z" not in focuser.axes): raise ValueError( "focus %s must be a Actuator with a 'z' axis" % (focuser, )) self._focus = focuser # The "good" focus is at the current position self._good_focus = self._focus.position.value["z"] self._metadata[model.MD_FAV_POS_ACTIVE] = {"z": self._good_focus} logging.debug("Simulating focus, with good focus at %g m", self._good_focus) except (TypeError, KeyError): logging.info("Will not simulate focus") self._focus = None try: stage = dependencies["stage"] if (not isinstance(stage, model.ComponentBase) or not hasattr(stage, "axes") or not isinstance(stage.axes, dict)): raise ValueError("stage %s must be a Actuator with a 'z' axis", stage) self._stage = stage if resolution == None: raise ValueError("resolution is %s", resolution) # the position of the center of the image self._orig_stage_pos = self._stage.position.value[ "x"], self._stage.position.value["y"] logging.debug("Simulating stage at %s m", self._orig_stage_pos) except (TypeError, KeyError): logging.info("Will not simulate stage") self._stage = None # Simple implementation of the flow: we keep generating images and if # there are subscribers, they'll receive it. self.data = SimpleDataFlow(self) self._generator = None # Convenience event for the user to connect and fire self.softwareTrigger = model.Event()