Process reference: camera_fluctuation
Generated from the docstrings of vaft.process.camera_fluctuation:
7 public functions. Each entry is what
vaft.process.describe("camera_fluctuation.<name>") prints.
All process categories
Overview
Reproduces the image-processing chain published for VEST by E. C. Jung: temporal background removal, per-pixel short-time Fourier analysis at the MHD frequency the magnetics report, and the normalised band power that those papers use directly as the pixel intensity of the processed image. Everything here is array in, array out – no ODS, no file I/O, no plotting – so the same operations serve any camera whose frames arrive as a time-ordered cube.
This is the first image-cube consumer in vaft.process; every other module
there works one channel at a time. The one-dimensional definitions in
vaft.process.fluctuation remain the reference: the transform, the closed
band edges and the trapezoidal integration are the same, applied over an extra
pair of pixel axes rather than redefined.
The frame axis is first, matching how the camera mapping stores a sequence and
how vaft.plot.models.ImageSequence consumes one. Spectrogram magnitudes
carry (frequency, time) first and the pixel axes last, so integrating the
frequency axis away returns a cube in the original frame-first layout.
Optical intensity is not density. VEST visible emission is dominated by line radiation, so every quantity here is an emission fluctuation measure; nothing in this module may be renamed to suggest a calibrated density perturbation.
Functions
mhd_band_power | Integrate each pixel’s spectrogram over a band around the MHD frequency. Convention |
normalize_by_local_emission | Divide MHD-band power by each pixel’s local average emission. Convention |
pixelwise_spectrogram | Treat every pixel as its own signal and take its short-time Fourier magnitude. Convention |
poisson_window | The exponentially decreasing peak-shaped window the published analysis uses. Convention |
subtract_temporal_background | Remove each pixel’s slowly varying emission by subtracting its local temporal mean. Convention |
summed_region_signal | Sum the intensity inside one image region, giving a single time series. Convention |
track_reference_frequency | Follow the dominant MHD frequency a magnetic probe reports, window by window. Convention |
No function matches.
mhd_band_power
Machine-independentConvention-sensitive
source
mhd_band_power(spectrogram, *, centre_frequency, half_width=500.0)
Integrate each pixel’s spectrogram over a band around the MHD frequency.
| Name | Type | Unit | Description |
|---|---|---|---|
spectrogram | PixelSpectrogram | any | Per-pixel magnitude map. |
centre_frequency | float or array_like | Hz | Band centre, one value or one per window. |
half_width | float, optional | Hz | Half the band width; the band is |
| Returns | |||
| — | np.ndarray | any | Band magnitude per window, frame-first as |
Convention
Band edges are closed, as in
vaft.process.fluctuation.compute_band_power, but the reduction is a
sum over bins, not an integral over hertz, and the two are not
interchangeable: that function consumes a power spectral density, where
integrating against frequency is what produces a power, while a short-time
transform in mode="magnitude" yields an amplitude per bin, which has no
per-hertz meaning to integrate. A sum also degrades correctly at the
published settings, where a 1 ms window resolves 1 kHz and the +/- 0.5 kHz
band is exactly one bin wide – the single-bin case the legacy
implementation takes directly.
A band containing no bin at all contributes zero. A nan centre – what
track_reference_frequency returns for a window it could not judge –
yields nan for that window, not zero.
Processing steps · Defaults · Applicability · Limitations · Provenance
Processing steps
- Select the frequency bins inside the band for each window.
- Sum the magnitudes over them.
Defaults
MHD_BAND_HALF_WIDTH_HZ = 500 is a literature value; the published images
are filtered over 6 +/- 0.5 kHz.
Applicability
Machine-independent.
Limitations
Because this is a sum rather than a density integral, its magnitude depends on how many bins the band happens to hold: doubling the window length halves the bin width and changes the number summed. Compare images computed with the same window, not across windows.
Provenance
- [Jung2022] section 4.1 and figure 3, which filter about the probe’s dominant frequency with a 0.5 kHz bandwidth.
- [CameraFFT], which takes the single Fourier bin matched to the magnetic probe’s peak, with a three-bin mean left in the source as the alternative it did not use.
normalize_by_local_emission
Machine-independentConvention-sensitive
source
normalize_by_local_emission(power, frames, *, frame_time, power_time, window_frames=10, floor=0.0)
Divide MHD-band power by each pixel’s local average emission.
| Name | Type | Unit | Description |
|---|---|---|---|
power | array_like | any | Band power per window, frame-first. |
frames | array_like | any | The image cube the power was computed from, frame axis first. |
frame_time | array_like | s | Time of each frame. |
power_time | array_like | s | Time of each power window. |
window_frames | int, optional | - | Width of the local emission average, in frames. |
floor | float, optional | any | Local means at or below this are treated as carrying no emission. |
| Returns | |||
| — | np.ndarray | - | Normalised power, same shape as |
Convention
The local mean is centred on the analysed frame with the same shrinking-edge
rule as subtract_temporal_background, and is taken from the
unsubtracted frames: the divisor is the emission level, not the
fluctuation.
Processing steps · Input semantics · Output semantics · Defaults · Applicability · Limitations · Provenance
Processing steps
- Take each pixel’s centred rolling mean emission over
window_frames. - Sample that mean at the frame nearest each power window’s time.
- Divide, leaving pixels whose local mean is at or below
floorat zero.
Input semantics
power and frames are on different time bases – one value per
transform window against one per frame – so both bases are required rather
than assumed alignable by index.
Output semantics
The result is dimensionless: a fluctuation power relative to the emission that produced it, which is what makes frames of different brightness comparable. It is not a density perturbation.
Defaults
EMISSION_NORMALISATION_FRAMES_50KFPS = 10 is a literature value, the
published ten images or 0.2 ms at 50 kFrames/s. floor = 0 is a
hard-coded choice: only a non-positive local mean is refused, because any
larger threshold is a judgement about what counts as dark that belongs to
the caller and their camera.
Applicability
Machine-independent.
Limitations
Where the local mean is small but positive the ratio is large and noisy; this is inherent to the published normalisation, not an artefact of this implementation. A pixel refused by the floor is indistinguishable in the output from one with genuinely no fluctuation.
Provenance
- [Jung2022] section 4.1: “the powers of the MHD frequency fluctuations divided by the averaged emission intensity (over ten images, 0.2 ms) are directly used as the intensities of each pixel of the final image”.
pixelwise_spectrogram
Machine-independentConvention-sensitive
source
pixelwise_spectrogram(frames, times, *, window_frames=50, overlap=0.5, decay_frames=None, detrend='constant')
Treat every pixel as its own signal and take its short-time Fourier magnitude.
| Name | Type | Unit | Description |
|---|---|---|---|
frames | array_like | any | Time-ordered image cube, frame axis first. |
times | array_like | s | Uniformly sampled frame times. |
window_frames | int, optional | - | Short-time window length in frames. |
overlap | float, optional | - | Fractional overlap between successive windows. |
decay_frames | float, optional | - | Passed to |
detrend | str or bool, optional | - | Per-window detrending, passed to the transform. |
| Returns | |||
| — | PixelSpectrogram | any | Frequency and time axes and the per-pixel magnitude map. |
Convention
The time axis is returned in the caller’s own base, offset back from the
transform’s window-relative one, exactly as
vaft.process.fluctuation.compute_spectrogram does, so a feature can be
read against the shot clock without correction. Magnitudes are amplitudes,
not densities, from the same mode="magnitude" transform that function uses.
Processing steps · Defaults · Applicability · Limitations · Provenance
Processing steps
- Build the exponential peak-shaped window of
window_frames. - Transform every pixel’s time series with that window.
- Return the magnitudes with the frequency and time axes first.
Defaults
SPECTRAL_WINDOW_FRAMES_50KFPS = 50 is a literature value: 1 ms at
50 kFrames/s. The overlap is a numerical convenience.
Applicability
Machine-independent.
Limitations
Cost and memory scale with the pixel count times the window count; a 208x208 sequence is comfortable, a 1024x1280 one is not, and nothing here tiles the image for the caller. A record shorter than one window raises rather than returning an empty map, because for imaging that is a mistake rather than an edge case.
Provenance
- [Jung2022] section 4.1, which treats each pixel of a sequence of images as an independent signal and transforms it.
- [CameraFFT], the legacy implementation of that step, which used a rectangular window rather than the published one.
poisson_window
Machine-independentConvention-sensitive
source
poisson_window(window_frames, *, decay_frames=None)
Show source vaft/process/camera_fluctuation.py, lines 273–324
def poisson_window(window_frames: int, *, decay_frames: float | None = None) -> np.ndarray:
"""The exponentially decreasing peak-shaped window the published analysis uses.
Parameters
----------
window_frames : int
Window length in frames [-].
decay_frames : float, optional
Length over which the weight falls by ``1/e`` [-].
Returns
-------
np.ndarray
Window weights, peak in the middle [-].
Convention
----------
Periodic rather than symmetric, which is what a short-time transform wants
and what :func:`scipy.signal.spectrogram` would build from a window name.
Defaults
--------
``decay_frames = window_frames / 8`` is an assumed value. The publications
name the window shape but not its rate, so this is a VAFT choice, recorded
rather than presented as published: it leaves the window edges at about 0.02
of the peak, tapering hard enough to suppress the transform's edge artefacts
without narrowing the effective window far below the stated 1 ms.
Applicability
-------------
Machine-independent.
Limitations
-----------
Because the rate is a choice, band powers from this window are not
numerically comparable with published values; the structure they show is.
Provenance
----------
.. [1] [Jung2022]_ section 4.1: "an exponential decreasing peak-shape time
window of 1 ms (50 images)".
.. [2] :func:`scipy.signal.windows.exponential`.
"""
from scipy.signal import windows
length = int(window_frames)
if length < 2:
raise ValueError(f"window_frames must be at least 2; got {window_frames!r}")
tau = float(decay_frames) if decay_frames is not None else length / 8.0
if tau <= 0.0:
raise ValueError(f"decay_frames must be positive; got {decay_frames!r}")
return windows.exponential(length, tau=tau, sym=False)The exponentially decreasing peak-shaped window the published analysis uses.
| Name | Type | Unit | Description |
|---|---|---|---|
window_frames | int | - | Window length in frames. |
decay_frames | float, optional | - | Length over which the weight falls by |
| Returns | |||
| — | np.ndarray | - | Window weights, peak in the middle. |
Convention
Periodic rather than symmetric, which is what a short-time transform wants
and what scipy.signal.spectrogram would build from a window name.
Defaults · Applicability · Limitations · Provenance
Defaults
decay_frames = window_frames / 8 is an assumed value. The publications
name the window shape but not its rate, so this is a VAFT choice, recorded
rather than presented as published: it leaves the window edges at about 0.02
of the peak, tapering hard enough to suppress the transform’s edge artefacts
without narrowing the effective window far below the stated 1 ms.
Applicability
Machine-independent.
Limitations
Because the rate is a choice, band powers from this window are not numerically comparable with published values; the structure they show is.
Provenance
- [Jung2022] section 4.1: “an exponential decreasing peak-shape time window of 1 ms (50 images)”.
scipy.signal.windows.exponential.
subtract_temporal_background
Machine-independentConvention-sensitive
source
subtract_temporal_background(frames, *, window_frames=15)
Show source vaft/process/camera_fluctuation.py, lines 138–213
def subtract_temporal_background(frames, *, window_frames: int = BACKGROUND_FRAMES_50KFPS) -> np.ndarray:
"""Remove each pixel's slowly varying emission by subtracting its local temporal mean.
Parameters
----------
frames : array_like
Time-ordered image cube, frame axis first [any].
window_frames : int, optional
Width of the local mean, in frames [-].
Returns
-------
np.ndarray
``frames`` minus its local temporal mean, same shape, float [any].
Processing steps
----------------
1. Take the centred rolling mean of every pixel over ``window_frames``.
2. Subtract it from the frame at the centre of that window.
Convention
----------
The window is centred on the analysed frame and spans
``[i - w//2, i + (w-1)//2]``, clipped to the record, so the first and last
frames average over a shrinking window rather than over zeros and the output
keeps the input's length. For an odd window -- both published presets are
odd -- that span is symmetric; an even window takes one more frame from
before than after.
A window wider than the record is allowed and clips the same way, so every
frame is measured against the whole record. Refusing it would make the
result depend on how many frames a caller happened to pass rather than on
the window it asked for: a fourteen-frame acquisition and a fourteen-frame
slice of a long one would come out differently.
Frame values and their order are otherwise untouched: this subtracts, it does
not filter, reorder or resample.
Defaults
--------
``BACKGROUND_FRAMES_50KFPS = 15`` and ``BACKGROUND_FRAMES_2500FPS = 3`` are
literature values. The 50 kFrames/s preset is the analysed frame plus seven
before and seven after, which is what reproduces the published filter
behaviour: measured at 50 kFrames/s this window passes 0.89 of a 3 kHz
component and 1.11 of a 6 kHz one, matching the statement that it removes
components below about 3 kHz while enhancing the several-kHz structures. A
15-frame window is therefore not interchangeable with a 7-frame one, which
passes only 0.26 at 3 kHz and 0.81 at 6 kHz -- it would attenuate the MHD
band this analysis exists to see.
Applicability
-------------
Machine-independent.
Limitations
-----------
The subtraction is zero-phase but the shrinking edge windows bias the first
and last ``window_frames / 2`` frames toward the local level; a burst there is
partly removed along with the background. A single very bright frame pulls
the mean toward itself and appears, inverted, in its neighbours.
Provenance
----------
.. [1] [Jung2022]_ section 3.1: "subtracts the average value of each pixel in
a series of seven images before and after the analysed image ... removes
slowly varying components (less than about 3 kHz)".
.. [2] [JungThesis]_, the background-removal figure.
"""
cube = _as_cube(frames)
window = int(window_frames)
if window < 2:
raise ValueError(
"window_frames must be at least 2; a one-frame mean is the frame "
f"itself, so subtracting it yields exactly zero. Got {window_frames!r}"
)
return cube - _rolling_mean(cube, window)Remove each pixel’s slowly varying emission by subtracting its local temporal mean.
| Name | Type | Unit | Description |
|---|---|---|---|
frames | array_like | any | Time-ordered image cube, frame axis first. |
window_frames | int, optional | - | Width of the local mean, in frames. |
| Returns | |||
| — | np.ndarray | any |
|
Convention
The window is centred on the analysed frame and spans
[i - w//2, i + (w-1)//2], clipped to the record, so the first and last
frames average over a shrinking window rather than over zeros and the output
keeps the input’s length. For an odd window – both published presets are
odd – that span is symmetric; an even window takes one more frame from
before than after.
A window wider than the record is allowed and clips the same way, so every frame is measured against the whole record. Refusing it would make the result depend on how many frames a caller happened to pass rather than on the window it asked for: a fourteen-frame acquisition and a fourteen-frame slice of a long one would come out differently.
Frame values and their order are otherwise untouched: this subtracts, it does not filter, reorder or resample.
Processing steps · Defaults · Applicability · Limitations · Provenance
Processing steps
- Take the centred rolling mean of every pixel over
window_frames. - Subtract it from the frame at the centre of that window.
Defaults
BACKGROUND_FRAMES_50KFPS = 15 and BACKGROUND_FRAMES_2500FPS = 3 are
literature values. The 50 kFrames/s preset is the analysed frame plus seven
before and seven after, which is what reproduces the published filter
behaviour: measured at 50 kFrames/s this window passes 0.89 of a 3 kHz
component and 1.11 of a 6 kHz one, matching the statement that it removes
components below about 3 kHz while enhancing the several-kHz structures. A
15-frame window is therefore not interchangeable with a 7-frame one, which
passes only 0.26 at 3 kHz and 0.81 at 6 kHz – it would attenuate the MHD
band this analysis exists to see.
Applicability
Machine-independent.
Limitations
The subtraction is zero-phase but the shrinking edge windows bias the first
and last window_frames / 2 frames toward the local level; a burst there is
partly removed along with the background. A single very bright frame pulls
the mean toward itself and appears, inverted, in its neighbours.
Provenance
- [Jung2022] section 3.1: “subtracts the average value of each pixel in a series of seven images before and after the analysed image … removes slowly varying components (less than about 3 kHz)”.
- [JungThesis], the background-removal figure.
summed_region_signal
Machine-independentConvention-sensitive
source
summed_region_signal(frames, *, region=None)
Show source vaft/process/camera_fluctuation.py, lines 216–270
def summed_region_signal(frames, *, region: Sequence[int] | None = None) -> np.ndarray:
"""Sum the intensity inside one image region, giving a single time series.
Parameters
----------
frames : array_like
Time-ordered image cube, frame axis first [any].
region : sequence of int, optional
Pixel box as ``(row_start, row_stop, column_start, column_stop)``, the
stops exclusive; ``None`` sums the whole frame [-].
Returns
-------
np.ndarray
One value per frame [any].
Convention
----------
Bounds are half-open and in row-then-column order, the order an image array
is indexed with -- which is the opposite of the column-then-row order
:func:`vaft.process.camera_geometry.project_points` returns pixels in. A
region is stated explicitly rather than inferred, because which part of the
image faces the low-field side depends on the camera pose.
Applicability
-------------
Machine-independent.
Limitations
-----------
A plain sum, not a mean: two regions of different size are not comparable in
magnitude. Nothing checks that the region holds plasma rather than wall.
Provenance
----------
.. [1] [Jung2022]_ figure 3, the summed camera pixel intensity viewing the
low-field side whose spectrogram is compared with the magnetic probe's.
"""
cube = _as_cube(frames)
if region is None:
return cube.reshape(cube.shape[0], -1).sum(axis=1)
if len(region) != 4:
raise ValueError(
"region must be (row_start, row_stop, column_start, column_stop); "
f"got {region!r}"
)
r0, r1, c0, c1 = (int(value) for value in region)
if r0 >= r1 or c0 >= c1:
raise ValueError(f"region bounds must be increasing; got {region!r}")
window = cube[:, r0:r1, c0:c1]
if window.size == 0:
raise ValueError(
f"region {region!r} selects no pixels of a {cube.shape[1:]} frame"
)
return window.reshape(window.shape[0], -1).sum(axis=1)Sum the intensity inside one image region, giving a single time series.
| Name | Type | Unit | Description |
|---|---|---|---|
frames | array_like | any | Time-ordered image cube, frame axis first. |
region | sequence of int, optional | - | Pixel box as |
| Returns | |||
| — | np.ndarray | any | One value per frame. |
Convention
Bounds are half-open and in row-then-column order, the order an image array
is indexed with – which is the opposite of the column-then-row order
vaft.process.camera_geometry.project_points returns pixels in. A
region is stated explicitly rather than inferred, because which part of the
image faces the low-field side depends on the camera pose.
Applicability · Limitations · Provenance
Applicability
Machine-independent.
Limitations
A plain sum, not a mean: two regions of different size are not comparable in magnitude. Nothing checks that the region holds plasma rather than wall.
Provenance
- [Jung2022] figure 3, the summed camera pixel intensity viewing the low-field side whose spectrogram is compared with the magnetic probe’s.
track_reference_frequency
Machine-independentConvention-sensitive
source
track_reference_frequency(spectrogram, *, search_range=(3000.0, 15000.0))
Show source vaft/process/camera_fluctuation.py, lines 449–511
def track_reference_frequency(
spectrogram,
*,
search_range: Sequence[float] = REFERENCE_SEARCH_RANGE_HZ,
) -> np.ndarray:
"""Follow the dominant MHD frequency a magnetic probe reports, window by window.
Parameters
----------
spectrogram : Any
Any object carrying ``time``, ``frequency`` and a ``(frequency, time)``
``magnitude``, such as a magnetics spectrogram result [-].
search_range : sequence of float, optional
Lowest and highest frequency the peak may be found at [Hz].
Returns
-------
np.ndarray
One frequency per window, ``nan`` where no bin lies in range [Hz].
Convention
----------
The frequency is tracked, not assumed. The published filtering is "centred
at the dominant MHD mode frequency measured by the magnetic probe", which
moves during a shot; a fixed 6 kHz is the value of one example, not the
method.
Defaults
--------
``REFERENCE_SEARCH_RANGE_HZ = (3000, 15000)`` is a legacy compatibility
value. The legacy code searched the first fifteen bins of a magnetics
transform whose resolution was 1 kHz and rejected anything below the fourth,
which is this range expressed in hertz rather than in bins, so it means the
same thing at any window length. The lower edge matters: without it the
peak sits on the equilibrium field's own slow variation instead of the mode.
Applicability
-------------
Machine-independent.
Limitations
-----------
A single argmax per window, with no continuity requirement between windows,
so it can jump between two comparable modes from one window to the next. It
reports the strongest component in range whether or not one is really there;
a window with no mode still returns its loudest bin.
:func:`vaft.process.fluctuation.track_dominant_frequency`, which this calls,
offers a floor and a continuity limit for callers that want neither.
Provenance
----------
.. [1] [CameraFFT]_: ``[~, idx_max] = max(P1_mir(1:15, iFT))`` with windows
whose ``idx_max`` falls below four discarded.
.. [2] [Jung2022]_ section 4.1, which specifies the magnetic probe as the
source of the centre frequency.
"""
from vaft.process.fluctuation import track_dominant_frequency
# The shared ridge tracker with no floor and no continuity requirement is
# exactly the legacy per-window argmax; only an empty window reports NaN.
return track_dominant_frequency(
spectrogram, search_range=search_range, floor_ratio=0.0
).frequencyFollow the dominant MHD frequency a magnetic probe reports, window by window.
| Name | Type | Unit | Description |
|---|---|---|---|
spectrogram | Any | - | Any object carrying |
search_range | sequence of float, optional | Hz | Lowest and highest frequency the peak may be found at. |
| Returns | |||
| — | np.ndarray | Hz | One frequency per window, |
Convention
The frequency is tracked, not assumed. The published filtering is “centred at the dominant MHD mode frequency measured by the magnetic probe”, which moves during a shot; a fixed 6 kHz is the value of one example, not the method.
Defaults · Applicability · Limitations · Provenance
Defaults
REFERENCE_SEARCH_RANGE_HZ = (3000, 15000) is a legacy compatibility
value. The legacy code searched the first fifteen bins of a magnetics
transform whose resolution was 1 kHz and rejected anything below the fourth,
which is this range expressed in hertz rather than in bins, so it means the
same thing at any window length. The lower edge matters: without it the
peak sits on the equilibrium field’s own slow variation instead of the mode.
Applicability
Machine-independent.
Limitations
A single argmax per window, with no continuity requirement between windows,
so it can jump between two comparable modes from one window to the next. It
reports the strongest component in range whether or not one is really there;
a window with no mode still returns its loudest bin.
vaft.process.fluctuation.track_dominant_frequency, which this calls,
offers a floor and a continuity limit for callers that want neither.
Provenance
- [Jung2022] section 4.1, which specifies the magnetic probe as the source of the centre frequency.