Reading and processing NeXus choppers#

This guide shows how to extract the relevant data from a NeXus representation of a chopper and create a scippneutron.chopper.DiskChopper object.

We also demonstrate some utilities that Scippneutron provides to process and visualize the data inside NeXus choppers.

[1]:
import scipp as sc

NeXus chopper data#

Here, we use fake data which roughly represents what a real chopper loaded from NeXus looks like. ScippNeutron has a function for generating this data:

[2]:
from scippneutron.data import chopper_mockup

chopper = chopper_mockup()
chopper
[2]:
  • beam_position
    scipp
    Variable
    ()
    float64
    rad
    0.0
  • scipp
    DataGroup
    (time: 1)
      • value
        scipp
        DataArray
        (time: 1)
        int64
        ns
        30500000
  • position
    scipp
    Variable
    ()
    vector3
    m
    [ 0. 0. 60.]
  • radius
    scipp
    Variable
    ()
    float64
    m
    0.35
  • scipp
    DataGroup
    (time: 180)
      • value
        scipp
        DataArray
        (time: 180)
        float64
        Hz
        1.287e-05, 0.359, ..., 0.352, -4.904e-07
  • scipp
    DataGroup
    ()
      • value
        scipp
        Variable
        ()
        float64
        Hz
        14.0
  • slit_height
    scipp
    Variable
    ()
    float64
    m
    0.1
  • slit_edges
    scipp
    Variable
    (slit: 4)
    float64
    deg
    30.0, 160.0, 210.0, 280.0
  • slits
    int
    ()
    2
  • scipp
    DataGroup
    (time: 2354)
      • time
        scipp
        Variable
        (time: 2354)
        datetime64
        ns
        2023-01-19T08:11:06.217830400, 2023-01-19T08:11:08.799053824, ..., 2023-01-19T08:14:39.174524416, 2023-01-19T08:14:40.640919296

We can see that there is some information about the slits and geometry of the chopper as well as some timing-related data. Take a look at the NXdisk_chopper documentation for an overview of the fields.

In this case, there already is a position. This typically needs to be computed first, see scippnexus.compute_positions.

Some fields are nested data groups which happens when a NeXus file contains NXlogs. We can extract the relevant arrays from them using extract_chopper_from_nexus:

[3]:
from scippneutron.chopper import extract_chopper_from_nexus

chopper = extract_chopper_from_nexus(chopper)
chopper
[3]:
  • type
    scippneutron
    DiskChopperType
    ()
    Chopper type single
  • beam_position
    scipp
    Variable
    ()
    float64
    rad
    0.0
  • delay
    scipp
    DataArray
    ()
    int64
    ns
    30500000
  • position
    scipp
    Variable
    ()
    vector3
    m
    [ 0. 0. 60.]
  • radius
    scipp
    Variable
    ()
    float64
    m
    0.35
  • rotation_speed
    scipp
    DataArray
    (time: 180)
    float64
    Hz
    1.287e-05, 0.359, ..., 0.352, -4.904e-07
  • rotation_speed_setpoint
    scipp
    Variable
    ()
    float64
    Hz
    14.0
  • slit_height
    scipp
    Variable
    ()
    float64
    m
    0.1
  • slit_edges
    scipp
    Variable
    (slit: 4)
    float64
    deg
    30.0, 160.0, 210.0, 280.0
  • slits
    int
    ()
    2
  • top_dead_center
    scipp
    Variable
    (time: 2354)
    datetime64
    ns
    2023-01-19T08:11:06.217830400, 2023-01-19T08:11:08.799053824, ..., 2023-01-19T08:14:39.174524416, 2023-01-19T08:14:40.640919296

Converting to a DiskChopper#

We can assemble all data into a scippneutron.chopper.DiskChopper object, which enables better exploration/visualization of the chopper properties.

[4]:
from scippneutron.chopper import DiskChopper

disk_chopper = DiskChopper.from_nexus(chopper)
disk_chopper
[4]:
  • axle_position
    scipp
    Variable
    ()
    vector3
    m
    [ 0. 0. 60.]
  • frequency
    scipp
    Variable
    ()
    float64
    Hz
    14.0
  • beam_position
    scipp
    Variable
    ()
    float64
    rad
    0.0
  • phase
    scipp
    Variable
    ()
    float64
    rad
    2.6829201261656834
  • slit_begin
    scipp
    Variable
    (slit: 2)
    float64
    deg
    30.0, 210.0
  • slit_end
    scipp
    Variable
    (slit: 2)
    float64
    deg
    160.0, 280.0
  • slit_height
    scipp
    Variable
    (slit: 2)
    float64
    m
    0.1, 0.1
  • radius
    scipp
    Variable
    ()
    float64
    m
    0.35
begin0 end0 begin1 end1 TDC beam position

Note that the chopper phase \(\phi\) was derived from the rotation frequency \(f\) and the delay \(\delta_{t}\) using \(\phi = 2 \pi f \delta_{t}\).

With the DiskChopper, we can for example see at what times the chopper is open and closed:

[5]:
pulse_frequency = sc.scalar(14, unit="Hz")

print("Times open:  ", disk_chopper.time_offset_open(pulse_frequency=pulse_frequency))
print("Times closed:", disk_chopper.time_offset_close(pulse_frequency=pulse_frequency))
Times open:   <scipp.Variable> (slit: 4)    float64              [s]  [-0.00124603, -0.0250556, 0.0701825, 0.046373]
Times closed: <scipp.Variable> (slit: 4)    float64              [s]  [0.0245476, -0.0111667, 0.0959762, 0.0602619]

Inspecting time-dependent logs#

Some of the entries in the raw NeXus data group are time-dependent. In this section we take a closer look at these logs to gain more insights in how the chopper data is actually recorded.

Identifying in-phase regions#

Frame unwrapping and time-of-flight computation are only feasible when the choppers are in-phase with the neutron source pulses because, otherwise, the wavelength frames vary pulse-by-pulse.

To identify regions where a chopper is in-phase, we can inspect the rotation_speed which is the rotation frequency of the chopper.

[6]:
rotation_speed = chopper['rotation_speed']
rotation_speed.name = 'rotation_speed'
rotation_speed
[6]:
Show/Hide data repr Show/Hide attributes
scipp.DataArray (3.68 KB)
    • time: 180
    • time
      (time)
      datetime64
      ns
      2023-01-19T08:11:06.205830400, 2023-01-19T08:11:07.412763648, ..., 2023-01-19T08:14:41.039957504, 2023-01-19T08:14:42.246890752
      Values:
      array(['2023-01-19T08:11:06.205830400', '2023-01-19T08:11:07.412763648', '2023-01-19T08:11:08.619696896', '2023-01-19T08:11:09.826630400', '2023-01-19T08:11:11.033563648', '2023-01-19T08:11:12.240496896', '2023-01-19T08:11:13.447430144', '2023-01-19T08:11:14.654363392', '2023-01-19T08:11:15.861296896', '2023-01-19T08:11:17.068230144', '2023-01-19T08:11:18.275163392', '2023-01-19T08:11:19.482096640', '2023-01-19T08:11:20.689029888', '2023-01-19T08:11:21.895963392', '2023-01-19T08:11:23.102896640', '2023-01-19T08:11:24.309829888', '2023-01-19T08:11:25.516763136', '2023-01-19T08:11:26.723696384', '2023-01-19T08:11:27.930629888', '2023-01-19T08:11:29.137563136', '2023-01-19T08:11:30.344496384', '2023-01-19T08:11:31.551429632', '2023-01-19T08:11:32.758362880', '2023-01-19T08:11:33.965296128', '2023-01-19T08:11:35.172229632', '2023-01-19T08:11:36.379162880', '2023-01-19T08:11:37.586096128', '2023-01-19T08:11:38.793029376', '2023-01-19T08:11:39.999962624', '2023-01-19T08:11:41.206896128', '2023-01-19T08:11:42.413829376', '2023-01-19T08:11:43.620762624', '2023-01-19T08:11:44.827695872', '2023-01-19T08:11:46.034629120', '2023-01-19T08:11:47.241562624', '2023-01-19T08:11:48.448495872', '2023-01-19T08:11:49.655429120', '2023-01-19T08:11:50.862362368', '2023-01-19T08:11:52.069295616', '2023-01-19T08:11:53.276229120', '2023-01-19T08:11:54.483162368', '2023-01-19T08:11:55.690095616', '2023-01-19T08:11:56.897028864', '2023-01-19T08:11:58.103962112', '2023-01-19T08:11:59.310895616', '2023-01-19T08:12:00.517828864', '2023-01-19T08:12:01.724762112', '2023-01-19T08:12:02.931695360', '2023-01-19T08:12:04.138628608', '2023-01-19T08:12:05.345562112', '2023-01-19T08:12:06.552495360', '2023-01-19T08:12:07.759428608', '2023-01-19T08:12:08.966361856', '2023-01-19T08:12:10.173295104', '2023-01-19T08:12:11.380228608', '2023-01-19T08:12:12.587161856', '2023-01-19T08:12:13.794095104', '2023-01-19T08:12:15.001028352', '2023-01-19T08:12:16.207961600', '2023-01-19T08:12:17.414895104', '2023-01-19T08:12:18.621828352', '2023-01-19T08:12:19.828761600', '2023-01-19T08:12:21.035694848', '2023-01-19T08:12:22.242628096', '2023-01-19T08:12:23.449561600', '2023-01-19T08:12:24.656494848', '2023-01-19T08:12:25.863428096', '2023-01-19T08:12:27.070361344', '2023-01-19T08:12:28.277294592', '2023-01-19T08:12:29.484227840', '2023-01-19T08:12:30.691161344', '2023-01-19T08:12:31.898094592', '2023-01-19T08:12:33.105027840', '2023-01-19T08:12:34.311961088', '2023-01-19T08:12:35.518894336', '2023-01-19T08:12:36.725827840', '2023-01-19T08:12:37.932761088', '2023-01-19T08:12:39.139694336', '2023-01-19T08:12:40.346627584', '2023-01-19T08:12:41.553560832', '2023-01-19T08:12:42.760494336', '2023-01-19T08:12:43.967427584', '2023-01-19T08:12:45.174360832', '2023-01-19T08:12:46.381294080', '2023-01-19T08:12:47.588227328', '2023-01-19T08:12:48.795160832', '2023-01-19T08:12:50.002094080', '2023-01-19T08:12:51.209027328', '2023-01-19T08:12:52.415960576', '2023-01-19T08:12:53.622893824', '2023-01-19T08:12:54.829827328', '2023-01-19T08:12:56.036760576', '2023-01-19T08:12:57.243693824', '2023-01-19T08:12:58.450627072', '2023-01-19T08:12:59.657560320', '2023-01-19T08:13:00.864493824', '2023-01-19T08:13:02.071427072', '2023-01-19T08:13:03.278360320', '2023-01-19T08:13:04.485293568', '2023-01-19T08:13:05.692226816', '2023-01-19T08:13:06.899160320', '2023-01-19T08:13:08.106093568', '2023-01-19T08:13:09.313026816', '2023-01-19T08:13:10.519960064', '2023-01-19T08:13:11.726893312', '2023-01-19T08:13:12.933826816', '2023-01-19T08:13:14.140760064', '2023-01-19T08:13:15.347693312', '2023-01-19T08:13:16.554626560', '2023-01-19T08:13:17.761559808', '2023-01-19T08:13:18.968493312', '2023-01-19T08:13:20.175426560', '2023-01-19T08:13:21.382359808', '2023-01-19T08:13:22.589293056', '2023-01-19T08:13:23.796226304', '2023-01-19T08:13:25.003159552', '2023-01-19T08:13:26.210093056', '2023-01-19T08:13:27.417026304', '2023-01-19T08:13:28.623959552', '2023-01-19T08:13:29.830892800', '2023-01-19T08:13:31.037826048', '2023-01-19T08:13:32.244759552', '2023-01-19T08:13:33.451692800', '2023-01-19T08:13:34.658626048', '2023-01-19T08:13:35.865559296', '2023-01-19T08:13:37.072492544', '2023-01-19T08:13:38.279426048', '2023-01-19T08:13:39.486359296', '2023-01-19T08:13:40.693292544', '2023-01-19T08:13:41.900225792', '2023-01-19T08:13:43.107159040', '2023-01-19T08:13:44.314092544', '2023-01-19T08:13:45.521025792', '2023-01-19T08:13:46.727959040', '2023-01-19T08:13:47.934892288', '2023-01-19T08:13:49.141825536', '2023-01-19T08:13:50.348759040', '2023-01-19T08:13:51.555692288', '2023-01-19T08:13:52.762625536', '2023-01-19T08:13:53.969558784', '2023-01-19T08:13:55.176492032', '2023-01-19T08:13:56.383425536', '2023-01-19T08:13:57.590358784', '2023-01-19T08:13:58.797292032', '2023-01-19T08:14:00.004225280', '2023-01-19T08:14:01.211158528', '2023-01-19T08:14:02.418092032', '2023-01-19T08:14:03.625025280', '2023-01-19T08:14:04.831958528', '2023-01-19T08:14:06.038891776', '2023-01-19T08:14:07.245825024', '2023-01-19T08:14:08.452758528', '2023-01-19T08:14:09.659691776', '2023-01-19T08:14:10.866625024', '2023-01-19T08:14:12.073558272', '2023-01-19T08:14:13.280491520', '2023-01-19T08:14:14.487425024', '2023-01-19T08:14:15.694358272', '2023-01-19T08:14:16.901291520', '2023-01-19T08:14:18.108224768', '2023-01-19T08:14:19.315158016', '2023-01-19T08:14:20.522091264', '2023-01-19T08:14:21.729024768', '2023-01-19T08:14:22.935958016', '2023-01-19T08:14:24.142891264', '2023-01-19T08:14:25.349824512', '2023-01-19T08:14:26.556757760', '2023-01-19T08:14:27.763691264', '2023-01-19T08:14:28.970624512', '2023-01-19T08:14:30.177557760', '2023-01-19T08:14:31.384491008', '2023-01-19T08:14:32.591424256', '2023-01-19T08:14:33.798357760', '2023-01-19T08:14:35.005291008', '2023-01-19T08:14:36.212224256', '2023-01-19T08:14:37.419157504', '2023-01-19T08:14:38.626090752', '2023-01-19T08:14:39.833024256', '2023-01-19T08:14:41.039957504', '2023-01-19T08:14:42.246890752'], dtype='datetime64[ns]')
    • (time)
      float64
      Hz
      1.287e-05, 0.359, ..., 0.352, -4.904e-07
      Values:
      array([ 1.28712703e-05, 3.58958178e-01, 7.17866971e-01, 1.07691359e+00, 1.43591746e+00, 1.79487320e+00, 2.15385369e+00, 2.51281987e+00, 2.87187121e+00, 3.23081226e+00, 3.58981891e+00, 3.94870842e+00, 4.30769912e+00, 4.66666365e+00, 5.02562219e+00, 5.38460756e+00, 5.74358259e+00, 6.10256219e+00, 6.46145332e+00, 6.82052313e+00, 7.17950960e+00, 7.53843141e+00, 7.89749324e+00, 8.25639496e+00, 8.61538232e+00, 8.97432454e+00, 9.33327696e+00, 9.69229285e+00, 1.00513025e+01, 1.04102649e+01, 1.07692595e+01, 1.11281847e+01, 1.14871807e+01, 1.18461841e+01, 1.22050969e+01, 1.25640798e+01, 1.29231327e+01, 1.32820439e+01, 1.36409727e+01, 1.40000353e+01, 1.40783218e+01, 1.40743732e+01, 1.40368964e+01, 1.39993821e+01, 1.39790818e+01, 1.39768099e+01, 1.39851619e+01, 1.39956710e+01, 1.40030625e+01, 1.40059890e+01, 1.40054666e+01, 1.40033109e+01, 1.40010291e+01, 1.39994742e+01, 1.39987127e+01, 1.39987323e+01, 1.39990919e+01, 1.39995409e+01, 1.39998985e+01, 1.40000804e+01, 1.40002122e+01, 1.40001767e+01, 1.40002364e+01, 1.40000597e+01, 1.40001066e+01, 1.40000218e+01, 1.40000392e+01, 1.39999286e+01, 1.40000235e+01, 1.40000074e+01, 1.39999709e+01, 1.39999340e+01, 1.39999981e+01, 1.40000250e+01, 1.40000138e+01, 1.39999544e+01, 1.40000262e+01, 1.39999248e+01, 1.39999959e+01, 1.40000356e+01, 1.39999859e+01, 1.40000064e+01, 1.40000152e+01, 1.39999306e+01, 1.40000103e+01, 1.40000394e+01, 1.40000453e+01, 1.39999621e+01, 1.39999829e+01, 1.40000255e+01, 1.40000156e+01, 1.40001212e+01, 1.40000104e+01, 1.40000177e+01, 1.40000035e+01, 1.40000708e+01, 1.39999885e+01, 1.40000239e+01, 1.39999674e+01, 1.39999966e+01, 1.40000941e+01, 1.39999697e+01, 1.40000485e+01, 1.39999898e+01, 1.39999805e+01, 1.40000370e+01, 1.39999218e+01, 1.39999741e+01, 1.40000058e+01, 1.40000634e+01, 1.39999932e+01, 1.40000127e+01, 1.39999972e+01, 1.40000141e+01, 1.40000016e+01, 1.39999793e+01, 1.39999465e+01, 1.39997833e+01, 1.39994304e+01, 1.39975883e+01, 1.39909586e+01, 1.39679657e+01, 1.39066992e+01, 1.38161344e+01, 1.37547382e+01, 1.37317718e+01, 1.37251129e+01, 1.37233104e+01, 1.37229065e+01, 1.37227973e+01, 1.37227971e+01, 1.37227141e+01, 1.37227334e+01, 1.37227890e+01, 1.37228152e+01, 1.37227866e+01, 1.37227864e+01, 1.37227676e+01, 1.37227788e+01, 1.37227187e+01, 1.37227271e+01, 1.33708923e+01, 1.30189700e+01, 1.26671450e+01, 1.23152892e+01, 1.19635346e+01, 1.16115960e+01, 1.12596457e+01, 1.09078860e+01, 1.05559769e+01, 1.02041706e+01, 9.85215034e+00, 9.50041097e+00, 9.14844059e+00, 8.79657999e+00, 8.44471935e+00, 8.09286946e+00, 7.74101219e+00, 7.38913836e+00, 7.03729080e+00, 6.68543505e+00, 6.33355645e+00, 5.98173710e+00, 5.62985157e+00, 5.27793131e+00, 4.92608422e+00, 4.57427248e+00, 4.22239068e+00, 3.87061422e+00, 3.51868112e+00, 3.16675315e+00, 2.81483757e+00, 2.46306945e+00, 2.11123897e+00, 1.75938373e+00, 1.40747555e+00, 1.05549357e+00, 7.03716252e-01, 3.51902452e-01, -4.90383720e-07])

The chopper has a long region of near-constant rotation speed surrounded by spin-up and spin-down regions:

[7]:
rotation_speed.plot(markersize=2)
[7]:
../../_images/user-guide_chopper_processing-nexus-choppers_13_0.svg

The central plateau is the section of the log where the chopper is in-phase with the source (the ESS source has a frequency of 14 Hz).

We use find_plateaus and collapse_plateaus to find one or more plateaus in the log data.

Note the atol and min_n_points parameters, they need to be tuned for the specific input data.

Warning

find_plateaus can potentially falsely identify regions with a small but steady slope as a plateau. See the function’s documentation for details.

[8]:
from scippneutron.chopper import collapse_plateaus, find_plateaus

plateaus = find_plateaus(
    rotation_speed, atol=sc.scalar(1e-3, unit='Hz / s'), min_n_points=10
)
plateaus = collapse_plateaus(plateaus)
plateaus
[8]:
Show/Hide data repr Show/Hide attributes
scipp.DataArray (1.18 KB)
    • plateau: 2
    • plateau
      (plateau)
      int64
      0, 1
      Values:
      array([0, 1])
    • time
      (plateau, time [bin-edge])
      datetime64
      ns
      2023-01-19T08:12:10.173295104, 2023-01-19T08:13:28.623959553, 2023-01-19T08:13:39.486359296, 2023-01-19T08:13:55.176492033
      Values:
      array([['2023-01-19T08:12:10.173295104', '2023-01-19T08:13:28.623959553'], ['2023-01-19T08:13:39.486359296', '2023-01-19T08:13:55.176492033']], dtype='datetime64[ns]')
    • (plateau)
      float64
      Hz
      14.000, 13.723
      Values:
      array([13.99993461, 13.7228163 ])

find_plateaus found two plateaus that we can plot with the following helper function:

[9]:
def plot_plateaus(raw_data: sc.DataArray, plateaus: sc.DataArray) -> None:
    to_plot = sc.DataGroup({'Rotation Speed': raw_data})
    for plateau in plateaus:
        i = plateau.coords['plateau'].value
        to_plot[f'Plateau {i}'] = sc.DataArray(
            plateau.data.broadcast(dims=['time'], shape=[2]),
            coords={'time': plateau.coords['time']},
        )
    return to_plot.plot(
        ls={f'Plateau {i}': '-' for i in range(len(plateaus))},
        marker={f'Plateau {i}': '|' for i in range(len(plateaus))},
        markersize={'Rotation Speed': 2},
    )
[10]:
plot_plateaus(rotation_speed, plateaus)
[10]:
../../_images/user-guide_chopper_processing-nexus-choppers_18_0.svg

In this case, the source has a frequency of 14Hz which means that plateau 0 is in phase. But plateau 1 is not, it is a short region where the chopper slowed down before fully stopping.

We can use filter_in_phase to remove all out-of-phase plateaus:

[11]:
pulse_frequency = sc.scalar(14.0, unit='Hz')
[12]:
from scippneutron.chopper import filter_in_phase

frequency_in_phase = filter_in_phase(
    plateaus, reference=pulse_frequency, rtol=sc.scalar(1e-3)
)
frequency_in_phase
[12]:
Show/Hide data repr Show/Hide attributes
scipp.DataArray (1.15 KB)
    • plateau: 1
    • plateau
      (plateau)
      int64
      0
      Values:
      array([0])
    • time
      (plateau, time [bin-edge])
      datetime64
      ns
      2023-01-19T08:12:10.173295104, 2023-01-19T08:13:28.623959553
      Values:
      array([['2023-01-19T08:12:10.173295104', '2023-01-19T08:13:28.623959553']], dtype='datetime64[ns]')
    • (plateau)
      float64
      Hz
      14.000
      Values:
      array([13.99993461])
[13]:
plot_plateaus(rotation_speed, frequency_in_phase)
[13]:
../../_images/user-guide_chopper_processing-nexus-choppers_22_0.svg

Since there is only one plateau left, we can simply index into it to get the chopper frequency:

[14]:
frequency = frequency_in_phase['plateau', 0]
frequency
[14]:
Show/Hide data repr Show/Hide attributes
scipp.DataArray (1.15 KB)
      • plateau
        ()
        int64
        0
        Values:
        array(0)
      • time
        (time [bin-edge])
        datetime64
        ns
        2023-01-19T08:12:10.173295104, 2023-01-19T08:13:28.623959553
        Values:
        array(['2023-01-19T08:12:10.173295104', '2023-01-19T08:13:28.623959553'], dtype='datetime64[ns]')
      • ()
        float64
        Hz
        13.999934613725587
        Values:
        array(13.99993461)

    Inspecting TDC timestamps#

    The top-dead-center (TDC) timestamps are created every time a marker placed on the chopper disk passes in front of a sensor mounted on the chopper module. It is essentially reporting at what times the chopper completed a full rotation.

    [15]:
    
    tdc = chopper['top_dead_center']
    tdc
    
    [15]:
    
    Show/Hide data repr Show/Hide attributes
    scipp.Variable (18.64 KB)
      • (time: 2354)
        datetime64
        ns
        2023-01-19T08:11:06.217830400, 2023-01-19T08:11:08.799053824, ..., 2023-01-19T08:14:39.174524416, 2023-01-19T08:14:40.640919296
        Values:
        array(['2023-01-19T08:11:06.217830400', '2023-01-19T08:11:08.799053824', '2023-01-19T08:11:09.873177088', ..., '2023-01-19T08:14:38.209520640', '2023-01-19T08:14:39.174524416', '2023-01-19T08:14:40.640919296'], dtype='datetime64[ns]')

    The initial speed-up and late slow-down are visible when simply plotting all the TDC timestamps:

    [16]:
    
    tdc.plot()
    
    [16]:
    
    ../../_images/user-guide_chopper_processing-nexus-choppers_28_0.svg

    We can filter out the TDCs for the time when the chopper was in-phase by using the time range of the plateau we extracted above:

    [17]:
    
    low = frequency.coords['time'][0]
    high = frequency.coords['time'][1]
    tdc_in_phase = tdc[(tdc > low) & (tdc < high)]
    tdc_in_phase
    
    [17]:
    
    Show/Hide data repr Show/Hide attributes
    scipp.Variable (8.83 KB)
      • (time: 1098)
        datetime64
        ns
        2023-01-19T08:12:10.228627200, 2023-01-19T08:12:10.300058880, ..., 2023-01-19T08:13:28.514769152, 2023-01-19T08:13:28.586200832
        Values:
        array(['2023-01-19T08:12:10.228627200', '2023-01-19T08:12:10.300058880', '2023-01-19T08:12:10.371490560', ..., '2023-01-19T08:13:28.443337984', '2023-01-19T08:13:28.514769152', '2023-01-19T08:13:28.586200832'], dtype='datetime64[ns]')

    We can check that the rate at which the TDC triggers is indeed close to 14Hz.

    [18]:
    
    diff = tdc_in_phase[1:] - tdc_in_phase[:-1]
    rate = 1 / diff.to(unit='s', dtype='float64')
    rate.min(), rate.max()
    
    [18]:
    
    (<scipp.Variable> ()    float64             [Hz]  13.9986,
     <scipp.Variable> ()    float64             [Hz]  14.0002)
    

    Computing chopper phase#

    When constructing the DiskChopper at the start of this notebook, the phase was derived from a single delay value and the rotation speed setpoint.

    It is however sometimes useful for debugging to compute the actual (time-dependent) phase of the chopper from the TDC information.

    The phase can be defined as \(\phi = \omega (TDC - T_0)\), where \(\omega\) is the angular frequency of the chopper, \(TDC\) is a TDC timestamp, and \(T_0\) a pulse time.

    We already determined the TDC timestamps above. In practice, we would get \(T_0\) from the input NeXus file, but here, we simply make one up:

    [19]:
    
    pulse_time = sc.datetime('2023-01-19T08:12:03.484012915', unit='ns')
    

    Note

    The pulse time is typically an array of timestamps and it can be difficult to determine which pulse goes with which chopper period.

    [20]:
    
    # We multiply by 1 rad to get the proper `rad*Hz` unit in `omega`
    omega = 2 * sc.constants.pi * frequency.data * sc.scalar(1, unit='rad')
    phase = omega * (tdc - pulse_time)
    phase = phase.to(unit='deg') % sc.scalar(360.0, unit='deg')
    
    phase.plot()
    
    [20]:
    
    ../../_images/user-guide_chopper_processing-nexus-choppers_36_0.svg

    We can clearly see the a central region where the phase is almost constant, and close to the value of 153.7° listed by the DiskChopper table at the top of the notebook (= 2.683 rad).

    To each side of the central region are areas where the chopper is completely out of phase, during the spin-up and spin-down periods.

    We can take a closer look at the data in the ‘in-phase’ region where we see some jitter around the target value:

    [21]:
    
    phase = omega * (tdc_in_phase - pulse_time)
    phase = phase.to(unit='deg') % sc.scalar(360.0, unit='deg')
    phase.plot()
    
    [21]:
    
    ../../_images/user-guide_chopper_processing-nexus-choppers_38_0.svg