Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 2 additions & 3 deletions malcolm/modules/ADOdin/blocks/odin_runnable_block.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,6 @@
description: Name of secondary dataset to link in nxs file
default: sum


- builtin.defines.docstring:
value: |
Device block corresponding to ADOdin + Odin file writer plugin .
Expand All @@ -65,7 +64,7 @@
mri: $(mri_prefix):DRV
prefix: $(pv_prefix):$(drv_suffix)

- ADCore.parts.DetectorDriverPart:
- ADOdin.parts.OdinDriverPart:
name: DRV
mri: $(mri_prefix):DRV
soft_trigger_modes:
Expand All @@ -91,4 +90,4 @@
name: DSET

- builtin.parts.IconPart:
svg: $(yamldir)/../icons/odin_logo.svg
svg: $(yamldir)/../icons/odin_logo.svg
23 changes: 18 additions & 5 deletions malcolm/modules/ADOdin/blocks/odin_writer_block.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -79,11 +79,24 @@
description: type of the pixel data
rbv: $(prefix):DataType

#- ca.parts.CALongPa#-rt:
# name: flushData# PerNFrames
# description: Nu# mber of frames to capture between HDF dataset flushes
# pv: $(prefix):N# umFramesFlush
# rbv_suffix: _RB# V
#- ca.parts.CALongPart:
# name: flushDataPerNFrames
# description: Number of frames to capture between HDF dataset flushes
# pv: $(prefix):NumFramesFlush
# rbv_suffix: _RBV

- ca.parts.CALongPart:
name: uidOffset
description: value to offset UID by
pv: $(prefix):PARAM:UID:Adjustment
rbv_suffix: _RBV

- ca.parts.CALongPart:
name: frameOffset
description: value to offset frame index by
pv: $(prefix):OFF:Adjustment
rbv_suffix: _RBV


# Filename
- ca.parts.CACharArrayPart:
Expand Down
1 change: 1 addition & 0 deletions malcolm/modules/ADOdin/parts/__init__.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
from .odinwriterpart import OdinWriterPart, AMri, APartName
from .odindriverpart import OdinDriverPart

# Expose a nice namespace
from malcolm.core import submodule_all
Expand Down
18 changes: 18 additions & 0 deletions malcolm/modules/ADOdin/parts/odindriverpart.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
from malcolm.core import Context
from malcolm.modules import builtin, scanning
from malcolm.modules.ADCore.parts import DetectorDriverPart

from annotypes import add_call_types, Any

class OdinDriverPart(DetectorDriverPart):
def setup_detector(self,
context, # type: Context
completed_steps, # type: scanning.hooks.ACompletedSteps
steps_to_do, # type: scanning.hooks.AStepsToDo
duration, # type: float
part_info, # type: scanning.hooks.APartInfo
**kwargs # type: Any
):
# type: (...) -> None
super(OdinDriverPart, self).setup_detector(context, 0, steps_to_do, duration, part_info)

44 changes: 31 additions & 13 deletions malcolm/modules/ADOdin/parts/odinwriterpart.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
from vdsgen import InterleaveVDSGenerator, ReshapeVDSGenerator
from scanpointgenerator import CompoundGenerator

from malcolm.core import APartName, Future, Info, PartRegistrar, BadValueError
from malcolm.core import APartName, Future, Info, PartRegistrar, BadValueError, errors
from malcolm.modules import builtin, scanning

if TYPE_CHECKING:
Expand Down Expand Up @@ -230,7 +230,7 @@ def add_nexus_nodes(generator, vds_file_path):


# We will set these attributes on the child block, so don't save them
@builtin.util.no_save("fileName", "filePath", "numCapture")
@builtin.util.no_save("fileName", "filePath", "numCapture", "uidOffset", "frameOffset")
class OdinWriterPart(builtin.parts.ChildPart):
"""Part for controlling an `hdf_writer_block` in a Device"""

Expand All @@ -239,6 +239,7 @@ class OdinWriterPart(builtin.parts.ChildPart):
array_future = None # type: Future
done_when_reaches = None # type: int
unique_id_offset = None # type: int
frame_offset = None # type: int
# The HDF5 layout file we write to say where the datasets go
layout_filename = None # type: str
exposure_time = None # type: float
Expand All @@ -249,9 +250,8 @@ def __init__(self,
initial_visibility=True, # type: AInitialVisibility
uid_name="uid", # type: AUidName
sum_name="sum", # type: ASumName
secondary_set="sum" # type: ASecondaryDataset
secondary_set="sum", # type: ASecondaryDataset
):
# type: (...) -> None
self.uid_name = uid_name
self.sum_name = sum_name
self.secondary_set = secondary_set
Expand All @@ -276,7 +276,7 @@ def setup(self, registrar):
registrar.hook(scanning.hooks.RunHook, self.on_run)
registrar.hook(scanning.hooks.PostRunReadyHook, self.on_post_run_ready)
registrar.hook(scanning.hooks.AbortHook, self.on_abort)
registrar.hook(scanning.hooks.PauseHook, self.on_pause)
# registrar.hook(scanning.hooks.PauseHook, self.on_pause)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why is this commented out?


@add_call_types
def on_pause(self, context):
Expand All @@ -301,8 +301,12 @@ def on_configure(self,

# On initial configure, expect to get the demanded number of frames
self.done_when_reaches = completed_steps + steps_to_do
self.unique_id_offset = 0
if completed_steps == 0:
self.unique_id_offset = 1
self.frame_offset = 0
child = context.block_view(self.mri)
child.uidOffset.put_value(self.unique_id_offset)
child.frameOffset.put_value(self.frame_offset)
file_dir = fileDir.rstrip(os.sep)

# derive file path from template as AreaDetector would normally do
Expand All @@ -318,7 +322,7 @@ def on_configure(self,
assert "." in vds_full_filename, \
"File extension for %r should be supplied" % vds_full_filename
futures = child.put_attribute_values_async(dict(
numCapture=steps_to_do,
numCapture=self.done_when_reaches,
filePath=file_dir + os.sep,
fileName=raw_file_basename))
context.wait_all_futures(futures)
Expand Down Expand Up @@ -347,13 +351,27 @@ def on_seek(self,
):
# type: (...) -> None
# This is rewinding or setting up for another batch, so the detector
# will skip to a uniqueID that has not been produced yet
self.unique_id_offset = completed_steps - self.done_when_reaches
self.done_when_reaches += steps_to_do
# will reset count to zero and set UID offset appropriately

child = context.block_view(self.mri)
# Just reset the array counter_block
child.arrayCounter.put_value(0)
# Start a future waiting for the first array
# Wait until Odin stops receiving frames
# try:
# child.when_value_matches(
# "numCaptured", self.done_when_reaches,
# event_timeout=self.exposure_time + 5)
# except errors.TimeoutError:
# pass
#current_count = child.numCaptured.value

self.unique_id_offset = self.done_when_reaches
self.frame_offset = completed_steps

child.uidOffset.put_value(self.unique_id_offset)
child.frameOffset.put_value(self.frame_offset)

# TODO: point this to check written UID instead
# self.done_when_reaches = steps_to_do + current_count

self.array_future = child.when_value_matches_async(
"numCaptured", greater_than_zero)

Expand Down
11 changes: 11 additions & 0 deletions malcolm/modules/scanning/controllers/runnablecontroller.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@
AConfigureParams = ConfigureParams
with Anno("Step to mark as the last completed step, -1 for current"):
ALastGoodStep = int
with Anno("Quantise seek to integer multiples of"):
ASeekMultiple = int

# Pull re-used annotypes into our namespace in case we are subclassed
AConfigDir = builtin.controllers.AConfigDir
Expand Down Expand Up @@ -149,6 +151,7 @@ def __init__(self,
initial_design="", # type: AInitialDesign
use_git=True, # type: AUseGit
description="", # type: ADescription
seek_multiple_of=1, # type: ASeekMultiple
):
# type: (...) -> None
super(RunnableController, self).__init__(
Expand Down Expand Up @@ -176,6 +179,8 @@ def __init__(self,
self.steps_per_run = 0 # type: int
# Create sometimes writeable attribute for the current completed scan
# step
self.seek_multiple_of = seek_multiple_of
# Quantise seek to integer multiples of this value
self.completed_steps = NumberMeta(
"int32", "Readback of number of scan steps",
tags=[Widget.METER.tag()] # Widget.TEXTINPUT.tag()]
Expand Down Expand Up @@ -565,6 +570,8 @@ def pause(self, lastGoodStep=-1):
elif lastGoodStep >= total_steps:
lastGoodStep = total_steps - 1

lastGoodStep -= (lastGoodStep % self.seek_multiple_of)

if self.state.value in [ss.ARMED, ss.FINISHED]:
# We don't have a run process, free to go anywhere we want
next_state = ss.ARMED
Expand All @@ -581,10 +588,14 @@ def do_pause(self, completed_steps):
# type: (int) -> None
self.run_hooks(
PauseHook(p, c) for p, c in self.create_part_contexts().items())

completed_steps -= (completed_steps % self.seek_multiple_of)

in_run_steps = completed_steps % self.steps_per_run
steps_to_do = self.steps_per_run - in_run_steps
part_info = self.run_hooks(
ReportStatusHook(p, c) for p, c in self.part_contexts.items())

self.completed_steps.set_value(completed_steps)
self.run_hooks(
SeekHook(p, c, completed_steps, steps_to_do, part_info, **kwargs)
Expand Down