Skip to content

Commit

Permalink
add lateral chromatic correction in filter wheel
Browse files Browse the repository at this point in the history
  • Loading branch information
K4rishma committed Jul 4, 2024
1 parent e4acf02 commit a0e984b
Show file tree
Hide file tree
Showing 3 changed files with 250 additions and 0 deletions.
236 changes: 236 additions & 0 deletions install/linux/usr/share/odemis/sim/meteor-sim-chr.odm.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,236 @@
# For now, this is esssentially just a FM microscope, as the FIB part is handled
# separately by the SEM

METEOR-Sim: {
class: Microscope,
role: meteor,
children: [
"Stage",
"Meteor Stage",
"Optical Objective",
"Optical Focus",
"Light Source",
"Filter Wheel",
"Camera",
"EBeam",
],
}

"SEM": {
class: simsem.SimSEM,
role: null,
init: {
image: "simsem-fake-output.h5", # any large 16 bit image is fine
},
children: {
scanner: "EBeam",
detector0: "SE Detector",
focus: "EBeam Focus",
}
}

"EBeam": {
# Internal child of SimSEM, so no class
role: e-beam, # Not required for the meteor
init: {},
affects: ["SE Detector"],
}

"EBeam Focus": {
# role: ebeam-focus,
role: null,
init: {},
affects: ["EBeam"]
}

"SE Detector": {
# Internal child of SimSEM, so no class
# role: se-detector,
role: null,
init: {},
}

# Normally provided by the SEM
"Stage": {
class: tmcm.TMCLController,
role: stage-bare,
init: {
port: "/dev/fake6",
address: null,
axes: ["x", "y", "z", "rx", "rz"],
ustepsize: [1.e-7, 1.e-7, 1.e-7, 1.2e-5, 1.2e-5], # unit/µstep
rng: [[-0.1, 0.1], [-0.05, 0.05], [-0.05, 0.1], [-2, 2], [0, 6.28]],
unit: ["m", "m", "m", "rad", "rad"],
refproc: "Standard",
},
metadata: {
# Loading position:
FAV_POS_DEACTIVE: { 'rx': 0, 'rz': 1.9076449, 'x': -0.01529, 'y': 0.0506, 'z': 0.01975 },
# XYZ ranges for SEM & METEOR
SEM_IMAGING_RANGE: {"x": [-10.e-3, 10.e-3], "y": [-5.e-3, 10.e-3], "z": [-0.5e-3, 8.e-3]},
FM_IMAGING_RANGE: {"x": [0.040, 0.054], "y": [-5.e-3, 10.e-3], "z": [-0.5e-3, 8.e-3]},
# Grid centers in SEM range
# Adjusted so that at init (0,0,0), it's at the Grid 1.
SAMPLE_CENTERS: {"GRID 1": {'x': 0, 'y': 0, 'z': 0}, "GRID 2": {'x': 2.98e-3, 'y': 2.46e-3, 'z': 0}},
# Mirroring values between SEM - METEOR
POS_COR: [0.02447, -0.000017],
# Active tilting (rx) & rotation (rz) angles positions when switching between SEM & FM, in radians.
# Note: these values are calibrated at installation time.
FAV_FM_POS_ACTIVE: {"rx": 0.12213888553625313 , "rz": 5.06145}, # 7° - 270°
# Typically rz = 110°, but we make it 0 so that at init it looks like in SEM position
FAV_SEM_POS_ACTIVE: {"rx": 0, "rz": 0} # Note that milling angle (rx) can be changed per session
},
}

"Linked YZ": {
class: actuator.ConvertStage,
role: null,
dependencies: {
"under": "Stage"
},
init: {
axes: [ "y", "z" ], # name of the axes in the dependency, mapped to x,y (if identity transformation)
rotation: -0.8034946943806713, # rad , -45°
},
}

"Meteor Stage": {
class: actuator.MultiplexActuator,
role: stage,
dependencies: { "x": "Stage", "y": "Linked YZ", "z": "Linked YZ", },
init: {
axes_map: { "x": "x", "y": "x", "z": "y",},
},
affects: ["Camera", "EBeam"],
metadata: {
# Typically, x range is the same as FM_IMAGING_RANGE, and Y has to be converted
POS_ACTIVE_RANGE: {"x": [0.040, 0.054], "y": [-10.e-3, 20.e-3]}
},
}

"Light Source": {
class: omicronxx.HubxX,
role: light,
init: {
port: "/dev/fakehub", # Simulator
#port: "/dev/ttyFTDI*",
},
affects: ["Camera"],
}

"Optical Objective": {
class: static.OpticalLens,
role: lens,
init: {
mag: 84.0, # ratio, (actually of the complete light path)
na: 0.85, # ratio, numerical aperture
ri: 1.0, # ratio, refractive index
},
affects: ["Camera"]
}

# Normally a IDS uEye or Zyla
# Axes: X is horizontal on screen (going left->right), physical: far->close when looking at the door
# Y is vertical on screen (going bottom->top), physical: left->right when looking at the door
"Camera": {
class: simcam.Camera,
role: ccd,
dependencies: {focus: "Optical Focus"},
init: {
image: "andorcam2-fake-clara.tiff",
transp: [-1, 2], # To swap/invert axes
},
metadata: {
# To change what the "good" focus position is on the simulator
# It's needed for not using the initial value, which is at deactive position.
FAV_POS_ACTIVE: {'z': 1.7e-3}, # good focus position
ROTATION: -0.099484, # [rad] (=-5.7°)
},
}

# Controller for the filter-wheel
# DIP must be configured with address 7 (= 1110000)
"Optical Actuators": {
class: tmcm.TMCLController,
role: null,
init: {
port: "/dev/fake6", # Simulator
address: null, # Simulator
axes: ["fw"],
ustepsize: [1.227184e-3], # [rad/µstep] fake value for simulator
rng: [[-14, 7]], # rad, more than 0->2 Pi, in order to allow one extra rotation in both direction, when quickly switching
unit: ["rad"],
refproc: "Standard",
refswitch: {"fw": 0}, #digital output used to switch on/off sensor
inverted: ["fw"], # for the filter wheel, the direction doesn't matter, as long as the positions are correct
},
}

"AntiBacklash for Filter Wheel": {
class: actuator.AntiBacklashActuator,
role: null,
init: {
backlash: {
# Force every move to always finish in the same direction
"fw": 50.e-3, # rad
},
},
dependencies: {"slave": "Optical Actuators"},
}

"Filter Wheel": {
class: actuator.FixedPositionsActuator,
role: filter,
dependencies: {"band": "AntiBacklash for Filter Wheel"},
init: {
axis_name: "fw",
# This filter-wheel is made so that the light goes through two "holes":
# the filter, and the opposite hole (left empty). So although it has 8
# holes, it only supports 4 filters (from 0° to 135°), and there is no
# "fast-path" between the last filter and the first one.
positions: {
# pos (rad) -> m,m
0.08: [414.e-9, 450.e-9], # FF01-432/36
0.865398: [500.e-9, 530.e-9], # FF01-515/30
1.650796: [579.5e-9, 610.5e-9], # FF01-595/31
2.4361944: [663.e-9, 733.e-9], # FF02-698/70
},
cycle: 6.283185, # position of ref switch (0) after a full turn
},
metadata: {
TRANSFORM_PER_CHANNEL: { 0.08: { "Pixel size cor": [ 1,1 ],"Centre position cor": [ 0,0 ] ,"Rotation cor": 1 ,"Shear cor": 1 }, 0.865398: { "Pixel size cor": [ 1,1 ],"Centre position cor": [ 0,0 ] ,"Rotation cor": 1 ,"Shear cor": 1 }, 1.650796: { "Pixel size cor": [ 1,1 ],"Centre position cor": [ 0,0 ] ,"Rotation cor": 1 ,"Shear cor": 1 }}
},
# TODO: a way to indicate the best filter to use during alignement and brightfield? via some metadata?
affects: ["Camera"],
}

# CLS3252dsc-1
"Optical Focus": {
class: smaract.MCS2,
role: focus,
init: {
locator: "fake",
ref_on_init: True,
# TODO: check speed/accel
speed: 0.003, # m/s
accel: 0.003, # m/s²
#hold_time: 5 # s, default = infinite
# TODO: check the ranges, and the channel
axes: {
'z': {
# -11.5mm is safely parked (FAV_POS_DEACTIVE)
# 1.7mm is typically in focus (FAV_POS_ACTIVE)
range: [-15.e-3, 5.e-3],
unit: 'm',
channel: 0,
},
},
},
metadata: {
# Loading position to retract lens
FAV_POS_DEACTIVE: {'z': -11.5e-3},
# Initial active position (close from the sample, but not too close, for safety)
FAV_POS_ACTIVE: {'z': 1.69e-3}
},
affects: ["Camera"],
}
2 changes: 2 additions & 0 deletions src/odemis/model/_metadata.py
Original file line number Diff line number Diff line change
Expand Up @@ -271,3 +271,5 @@
# Fastem: Parameters used for stitching and reconstruction of 3D volumes
MD_SLICE_IDX = "Index of slice in volume stack" # int
MD_FIELD_SIZE = "Average field of view of a megafield" # tuple (px, px)

MD_TRANSFORM_PER_CHANNEL = "Transformation parameter per RGB channel"
12 changes: 12 additions & 0 deletions src/odemis/odemisd/mdupdater.py
Original file line number Diff line number Diff line change
Expand Up @@ -352,6 +352,18 @@ def observeFilter(self, filter, comp_affected):
def updateOutWLRange(pos, fl=filter, comp_affected=comp_affected):
wl_out = fl.axes["band"].choices[pos["band"]]
comp_affected.updateMetadata({model.MD_OUT_WL: wl_out})
# apply lateral chromatic correction to align with the reference channel
apply_transform = fl.getMetadata().get(model.MD_TRANSFORM_PER_CHANNEL, None)
if apply_transform is not None:
if fl.position.value["band"] in apply_transform.keys():
comp_affected.updateMetadata(
{model.MD_PIXEL_SIZE_COR: apply_transform[fl.position.value["band"]][model.MD_PIXEL_SIZE_COR]})
comp_affected.updateMetadata(
{model.MD_POS_COR: apply_transform[fl.position.value["band"]][model.MD_POS_COR]})
comp_affected.updateMetadata(
{model.MD_ROTATION_COR: apply_transform[fl.position.value["band"]][model.MD_ROTATION_COR]})
comp_affected.updateMetadata(
{model.MD_SHEAR_COR: apply_transform[fl.position.value["band"]][model.MD_SHEAR_COR]})

filter.position.subscribe(updateOutWLRange, init=True)
self._onTerminate.append((filter.position.unsubscribe, (updateOutWLRange,)))
Expand Down

0 comments on commit a0e984b

Please sign in to comment.