Skip to content

Commit

Permalink
Merge pull request #2782 from pieleric/feature-support-hardware-synch…
Browse files Browse the repository at this point in the history
…ronized-acquisition-on-sparc

[feature] Support hardware synchronized acquisition on SPARC
  • Loading branch information
pieleric authored Jul 4, 2024
2 parents 71c1615 + ed1a8ee commit 93400a3
Show file tree
Hide file tree
Showing 7 changed files with 746 additions and 210 deletions.
9 changes: 4 additions & 5 deletions install/linux/usr/share/odemis/sim/sparc2-nidaq-sim.odm.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,8 @@ SPARC2: {
delay: { # Time it takes before a component is accessible
"Calibration Light": 0, # To turn on/off the light, it's immediate
"CL PMT control unit": 0,
"Camera": 3,
"Spectrograph": 5,
"Camera": 2,
"Spectrograph": 2,
"Optical Actuators": 1
},
init: { # Which component to power on from the very beginning (to save time)
Expand Down Expand Up @@ -164,7 +164,6 @@ SPARC2: {
},
}

# In reality, this is a Zyla, but you need libandor3-dev to simulate an AndorCam3
# Depending exactly on the configuration, it might also be used for spectrometer
"Camera": {
class: andorcam2.AndorCam2,
Expand All @@ -179,7 +178,7 @@ SPARC2: {

"Spectrometer Vis-NIR": {
class: spectrometer.CompositedSpectrometer,
role: spectrometer-integrated,
role: spectrometer,
dependencies: {detector: "Camera", spectrograph: "Spectrograph"},
init: {
transp: [1, -2], # only applied to the spectrometer data (not raw CCD)
Expand Down Expand Up @@ -279,7 +278,7 @@ SPARC2: {
address: null,
axes: ["l1", "l2", "cl-sel", "fw", "slit"],
# These values are adapted to make the simulator roughly the same speed
ustepsize: [25.1e-9, 25.1e-9, 26.1e-9, 3.392e-5, 5.e-9], # m/µstep, excepted for the fw: rad/µstep
ustepsize: [25.1e-8, 25.1e-8, 26.1e-8, 3.392e-4, 5.e-8], # m/µstep, excepted for the fw: rad/µstep (adjusted for the simulator)
unit: ["m", "m", "m", "rad", "m"],
refproc: "Standard",
refswitch: {"l1": 0, "l2": 0, "cl-sel": 4, "fw": 4},
Expand Down
47 changes: 23 additions & 24 deletions plugins/cliccd.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,36 +39,35 @@ class SEMCLCCDStream(SEMCCDMDStream):

def _preprocessData(self, n, data, i):
"""
return (int): mean of the data
return: mean of the data (if CCD)
"""
# We only care about the CCD data
if n < len(self.streams) - 1:
return super(SEMCLCCDStream, self)._preprocessData(n, data, i)

# Computing the sum or the mean is theoretically equivalent, but the sum
# provides gigantic values while the mean gives values in the same order
# as in the original data. Note that we cannot use the original dtype
# because if it's an integer it will likely cause quantisation (as a
# large part of the image received is identical for all the e-beam
# positions scanned).
return data.mean()

def _assembleLiveData(self, n, raw_data, px_idx, rep, pol_idx):
if n == len(self.streams) - 1:
# Computing the sum or the mean is theoretically equivalent, but the sum
# provides gigantic values while the mean gives values in the same order
# as in the original data. Note that we cannot use the original dtype
# because if it's an integer it will likely cause quantisation (as a
# large part of the image received is identical for all the e-beam
# positions scanned).
data = data.mean()

return super()._preprocessData(n, data, i)

def _assembleLiveData(self, n, raw_data, px_idx, px_pos, rep, pol_idx=0):
if n != self._ccd_idx:
return super(SEMCLCCDStream, self)._assembleLiveData(n, raw_data, px_idx, rep, pol_idx)
return super()._assembleLiveData(n, raw_data, px_idx, px_pos, rep, pol_idx)

if pol_idx > len(self._live_data[n]) - 1:
# New polarization => new DataArray
md = raw_data.metadata.copy()
# Compute metadata based on SEM metadata
semmd = self._live_data[0][pol_idx].metadata
# handle sub-pixels (aka fuzzing)
md[MD_PIXEL_SIZE] = (semmd[MD_PIXEL_SIZE][0] * self._emitter.resolution.value[0],
semmd[MD_PIXEL_SIZE][1] * self._emitter.resolution.value[1])
md[MD_POS] = self._live_data[0][pol_idx].metadata[MD_POS]
md[MD_DIMS] = "YX"
md[MD_DESCRIPTION] = self._streams[n].name.value
# Make sure it doesn't contain metadata related to AR
# Compute metadata to match the SEM metadata
center, pxs = self._get_center_pxs(rep, (1, 1), self._pxs, px_pos)
md.update({MD_POS: center,
MD_PIXEL_SIZE: pxs,
MD_DIMS: "YX",
MD_DESCRIPTION: self._streams[n].name.value})

# Remove any metadata related to AR & Spectrometry
for k in (model.MD_AR_POLE, model.MD_AR_MIRROR_BOTTOM, model.MD_AR_MIRROR_TOP,
model.MD_AR_FOCUS_DISTANCE, model.MD_AR_HOLE_DIAMETER, model.MD_AR_PARABOLA_F,
model.MD_AR_XMAX, model.MD_ROTATION, model.MD_WL_LIST):
Expand All @@ -82,7 +81,7 @@ def _assembleLiveData(self, n, raw_data, px_idx, rep, pol_idx):

class CLiCCDPlugin(Plugin):
name = "CL intensity CCD"
__version__ = "1.2"
__version__ = "1.3"
__author__ = u"Éric Piel"
__license__ = "GPLv2"

Expand Down
14 changes: 7 additions & 7 deletions plugins/secom_cl.py
Original file line number Diff line number Diff line change
Expand Up @@ -538,7 +538,7 @@ def __init__(self, name, streams):
"""
:param streams: ([Stream]) The streams to acquire.
"""
super(SECOMCLSEMMDStream, self).__init__(name, streams)
super().__init__(name, streams)

self.filename = model.StringVA("a.tiff")
self.firstOptImg = None # save the first optical image for display in analysis tab
Expand All @@ -552,7 +552,7 @@ def _runAcquisition(self, future):
"""
self.ccd_roi = sem_roi_to_ccd(self._emitter, self._ccd, self.roi.value, self._sccd.roi_margin.value)

return super(SECOMCLSEMMDStream, self)._runAcquisition(future)
return super()._runAcquisition(future)

def _preprocessData(self, n, data, i):
"""
Expand All @@ -564,7 +564,7 @@ def _preprocessData(self, n, data, i):
:returns: (value) The value as needed by _assembleFinalData.
"""
if n != self._ccd_idx:
return super(SECOMCLSEMMDStream, self)._preprocessData(n, data, i)
return super()._preprocessData(n, data, i)

ccd_roi = self.ccd_roi
data = data[ccd_roi[1]: ccd_roi[3] + 1, ccd_roi[0]: ccd_roi[2] + 1] # crop
Expand Down Expand Up @@ -596,9 +596,9 @@ def _preprocessData(self, n, data, i):
# Return something, but not the data to avoid data being cached.
return model.DataArray(numpy.array([0]))

def _assembleLiveData(self, n, raw_data, px_idx, rep, pol_idx):
def _assembleLiveData(self, n, raw_data, px_idx, px_pos, rep, pol_idx=0):
if n != self._ccd_idx:
return super(SECOMCLSEMMDStream, self)._assembleLiveData(n, raw_data, px_idx, rep, pol_idx)
return super()._assembleLiveData(n, raw_data, px_idx, px_pos, rep, pol_idx)

# For other streams (CL) don't do a live update
return
Expand All @@ -610,7 +610,7 @@ def _assembleFinalData(self, n, data):
:param raw_das: (list) List of data acquired for given detector n.
"""
if n != self._ccd_idx:
super(SECOMCLSEMMDStream, self)._assembleFinalData(n, data)
super()._assembleFinalData(n, data)

# For other streams (CL) don't do anything
return
Expand Down Expand Up @@ -685,7 +685,7 @@ class CLAcqPlugin(Plugin):
spots on the sample along a grid. Can also be used as a plugin.
"""
name = "CL acquisition for SECOM"
__version__ = "2.0"
__version__ = "2.1"
__author__ = u"Éric Piel, Lennard Voortman, Sabrina Rossberger"
__license__ = "Public domain"

Expand Down
8 changes: 5 additions & 3 deletions plugins/spectrum_arbscor.py
Original file line number Diff line number Diff line change
Expand Up @@ -141,23 +141,25 @@ def _getSpotPositions(self):
return pos

def _assembleLiveData(self, n: int, raw_data: "DataArray",
px_idx: Tuple[int, int], rep: Tuple[int, int], pol_idx: int):
px_idx: Tuple[int, int], px_pos: Tuple[float, float],
rep: Tuple[int, int], pol_idx: int = 0):
"""
Wrapper for _assembleLiveData() to convert back the standard px_idx (eg, (0,0), (0,1), (0,2)...)
into the index that was actually scanned at that moment.
:param px_idx: y, x position
:param pxs_pos: position of center of data in m: x, y
:param rep: x, y number of points in the scan
For other parameters, see MultipleDetectorStream._assembleLiveData()
"""
px_idx_flat = px_idx[0] * rep[0] + px_idx[1]
act_px_idx = self._px_order[px_idx_flat][::-1]
logging.debug("Converted back idx %s to %s", px_idx, act_px_idx)
return super()._assembleLiveData(n, raw_data, act_px_idx, rep, pol_idx)
return super()._assembleLiveData(n, raw_data, act_px_idx, px_pos, rep, pol_idx)


class SpectrumArbitraryScanOrderPlugin(Plugin):
name = "Spectrum acquisition in arbitrary scan order"
__version__ = "1.0"
__version__ = "1.1"
__author__ = u"Éric Piel"
__license__ = "GPLv2"

Expand Down
34 changes: 14 additions & 20 deletions plugins/spectrum_raw.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# -*- coding: utf-8 -*-
'''
"""
Created on 1 Sep 2023
@author: Éric Piel
Expand All @@ -19,7 +19,7 @@
You should have received a copy of the GNU General Public License along with Odemis. If not,
see http://www.gnu.org/licenses/.
'''
"""

import logging
from collections import OrderedDict
Expand Down Expand Up @@ -196,9 +196,9 @@ class SEMSpectrumRawMDStream(SEMCCDMDStream):
def _prepare(self):
return self._sccd._prepare()

def _assembleLiveData(self, n, raw_data, px_idx, rep, pol_idx):
def _assembleLiveData(self, n, raw_data, px_idx, px_pos, rep, pol_idx=0):
if n != self._ccd_idx:
return super()._assembleLiveData(n, raw_data, px_idx, rep, pol_idx)
return super()._assembleLiveData(n, raw_data, px_idx, px_pos, rep, pol_idx)

# Raw data format is YC, where Y is the CCD Y... but there is already the Y of the e-beam.
# So we report it as a T dimension, which makes the data exporter happy and Odemis viewer happy.
Expand All @@ -209,27 +209,19 @@ def _assembleLiveData(self, n, raw_data, px_idx, rep, pol_idx):
if pol_idx > len(self._live_data[n]) - 1:
# New polarization => new DataArray
md = raw_data.metadata.copy()
# Compute metadata based on SEM metadata
semmd = self._live_data[0][pol_idx].metadata
# handle sub-pixels (aka fuzzing)
md[MD_PIXEL_SIZE] = (semmd[MD_PIXEL_SIZE][0] * self._emitter.resolution.value[0],
semmd[MD_PIXEL_SIZE][1] * self._emitter.resolution.value[1])
md[MD_POS] = semmd[MD_POS]

for k in (MD_ROTATION, MD_ROTATION_COR):
if k in semmd:
md[k] = semmd[k]
else:
md.pop(k, None)

md[MD_DIMS] = "CTZYX"
# Compute metadata to match the SEM metadata
center, pxs = self._get_center_pxs(rep, (1, 1), self._pxs, px_pos)
md.update({MD_POS: center,
MD_PIXEL_SIZE: pxs,
MD_DIMS: "CTZYX",
MD_DESCRIPTION: self._streams[n].name.value})

# Note: MD_WL_LIST is normally present.
if MD_WL_LIST in md and spec_res != len(md[MD_WL_LIST]):
# Not a big deal, can happen if wavelength = 0
logging.warning("MD_WL_LIST is length %s, while spectrum res is %s",
len(md[MD_WL_LIST]), spec_res)

md[MD_DESCRIPTION] = self._streams[n].name.value
# Make sure it doesn't contain metadata related to AR
for k in (model.MD_AR_POLE, model.MD_AR_MIRROR_BOTTOM, model.MD_AR_MIRROR_TOP,
model.MD_AR_FOCUS_DISTANCE, model.MD_AR_HOLE_DIAMETER, model.MD_AR_PARABOLA_F,
Expand Down Expand Up @@ -260,6 +252,7 @@ def _assembleFinalData(self, n, data):

self._raw.extend(data)


# To force the "Temporal Spectrum" view to show the live stream
TemporalSpectrumStream.register(SpectrumRawSettingsStream)

Expand Down Expand Up @@ -308,9 +301,10 @@ def _assembleFinalData(self, n, data):
}),
))


class SpectrumRawPlugin(Plugin):
name = "Spectrum Raw acquisition"
__version__ = "1.0"
__version__ = "1.1"
__author__ = u"Éric Piel"
__license__ = "GPLv2"

Expand Down
Loading

0 comments on commit 93400a3

Please sign in to comment.