Get a frame stream from a merlin detector

This notebook shows how you can access the stream of frames from a Merlin Medipix detector, without using the high-level features of LiberTEM, like user-defined functions (UDFs). You get access to the data in a chunked fashion, that is, you’ll always get a small frame stack to work on.

Usage with the simulator

If you want to use this with the simulated data source, run something like this in the background:

libertem-live-mib-sim ~/Data/default.hdr --cached=MEM --wait-trigger

The --wait-trigger option is important for this notebook to function correctly, to properly model the real behavior.

A suitable MIB dataset can be downloaded at https://zenodo.org/record/5113449.

On Linux, MEMFD is also supported as a cache. Use NONE to deactivate the cache.

[1]:
# set to info, debug or trace to get more detailed logging:
%env LIBERTEM_QD_LOG_LEVEL=error
env: LIBERTEM_QD_LOG_LEVEL=error
[2]:
# set this to the host/port where the merlin data server is listening:
MERLIN_DATA_SOCKET = ('127.0.0.1', 6342)
MERLIN_CONTROL_SOCKET = ('127.0.0.1', 6341)
[3]:
%matplotlib widget
[4]:
import time
import math
import os
import tempfile
import pathlib
import logging
import numpy as np
import matplotlib.pyplot as plt
[5]:
from libertem_qd_mpx import QdConnection, CamClient
from libertem_live.detectors.merlin import MerlinControl
[6]:
def make_socket_path():
    temp_path = tempfile.mkdtemp()
    return os.path.join(temp_path, 'qd-shm-socket')
[7]:
socket_path = make_socket_path()
[8]:
conn = QdConnection(
    data_host=MERLIN_DATA_SOCKET[0],
    data_port=MERLIN_DATA_SOCKET[1],
    frame_stack_size=16,
    shm_handle_path=socket_path,
    drain=False,
    recovery_strategy="immediate_reconnect",
    huge=False,
)
control = MerlinControl(host=MERLIN_CONTROL_SOCKET[0], port=MERLIN_CONTROL_SOCKET[1])
[9]:
# start the background thread:
conn.start_passive()
[10]:
cam_client = CamClient(handle_path=socket_path)
[11]:
# connect the control socket, and close after the block automatically:
with control:
    # Tell the detector to send us some data:
    # (in a real setup, you probably want to properly configure triggering,
    # or even start the microscope scan here)
    control.cmd('STARTACQUISITION')
    control.cmd('SOFTTRIGGER')

    # wait for an acquisition to start, with a timeout
    # (as we just triggered one above, this should succeed immediately):
    header = conn.wait_for_arm(timeout=10.0)
    assert header is not None
    print(f"acquiring {header.frames_in_acquisition()} frames of shape {header.detector_shape()}")

    # current frame temp. buffer (needs to be 3D to work with `decode_range_into_buffer`):
    current_frames = np.zeros((16,) + header.detector_shape(), dtype=np.float32)

    # results
    result = np.zeros(header.detector_shape(), dtype=np.float32)
    result_nav = np.zeros((header.frames_in_acquisition(),), dtype=np.float32)

    index = 0

    # get the data as stacks of frames:
    while True:
        frame_stack = conn.get_next_stack(max_size=16)
        try:
            # decode the whole stack:
            cam_client.decode_range_into_buffer(
                frame_stack,
                current_frames,
                0,
                len(frame_stack),
            )
            result += current_frames[:len(frame_stack)].sum(axis=0)
            result_nav[index:index+len(frame_stack)] = current_frames[:len(frame_stack)].sum(axis=(1, 2))

            index += len(frame_stack)

            # are we done yet?
            if index == np.prod(result_nav.shape):
                print("done!")
                break
        finally:
            # free up space in the shared memory area:
            cam_client.done(frame_stack)
acquiring 16384 frames of shape (256, 256)
done!
[12]:
# close both the cam client and the connection:
cam_client.close()
conn.close()
[13]:
# assuming a square scan:
side = int(math.sqrt(header.frames_in_acquisition()))
side
[13]:
128
[14]:
fig, axes = plt.subplots(1, 2)
axes[0].imshow(np.log1p(result))
axes[1].imshow(np.log1p(result_nav.reshape((side, side))))
[14]:
<matplotlib.image.AxesImage at 0x7f5841acf350>
[15]:
# clean up the shm socket and containing dircetory:
p = pathlib.Path(socket_path)
if p.exists():
    p.unlink()
if p.parent.exists():
    p.parent.rmdir()
[ ]: