# Licensed under a 3-clause BSD style license - see LICENSE.rst
import numba as nb
import numpy as np
nb.config.THREADING_LAYER = 'threadsafe'
__all__ = ['find_inconsistencies', 'fix_jumps', 'fix_block',
'flag_block', 'level_block', 'correct_jumps',
'flag_zeroed_channels', 'check_jumps']
[docs]
@nb.njit(cache=True, nogil=False, parallel=False, fastmath=True)
def find_inconsistencies(frame_valid, frame_data, frame_weights,
modeling_frames, frame_parms, sample_flags,
exclude_sample_flag, channel_indices, channel_parms,
min_jump_level_frames, jump_flag, fix_each,
fix_subarray, has_jumps, subarray, jump_counter,
drift_size, flag_before=0, flag_after=0
): # pragma: no cover
"""
Find, fix (or flag) jumps and return the number corrected per channel.
This function is a wrapper around :func:`fix_jumps` the processes chunks
of frames (the size of which is determined by `drift_size`) sequentially,
and returns the number of chunk blocks found with jumps for each channel.
Jumps may be ignored, levelled, or flagged depending on
`min_jump_level_frames`, `fix_each`, and `fix_subarray`.
Parameters
----------
frame_valid : numpy.ndarray (bool)
A boolean mask of shape (n_frames,) where `False` excludes a frame from
any processing.
frame_data : numpy.ndarray (float)
The frame data values of shape (n_frames, all_channels).
frame_weights : numpy.ndarray (float)
The relative frame weights of shape (n_frames,).
modeling_frames : numpy.ndarray (bool)
A boolean mask of shape (n_frames,) where `True` marks a frame as a
modeling frame. Modeling frames will still be levelled, but will not
be included when updating the frame or channel dependents.
frame_parms : numpy.ndarray (float)
The frame dependents. Will be updated in-place if levelling occurs.
sample_flags : numpy.ndarray (int)
The frame data sample flags. Typically non-zero samples will be
excluded from processing. However, those samples not flagged with
only `exclude_sample_flag` will be included.
exclude_sample_flag : int
The sample flag to explicitly exclude from processing.
channel_indices : numpy.ndarray (int)
The channel indices of shape (n_channels,) indicating which channels
to process and mapping n_channels onto all_channels.
channel_parms : numpy.ndarray (float)
The channel dependents of shape (all_channels,). Will be updated
in-place when levelling occurs.
min_jump_level_frames : int
The minimum number of frames in a jump block required for levelling.
If this value is not reached, all samples in the block are flagged
with `jump_flag` instead.
jump_flag : int
The integer flag identifier with which to flag samples if the
jump block length to which they belong is less than
`min_jump_level_frames` and cannot be levelled.
fix_each : bool
If `False`, do not fix any channel with jumps.
fix_subarray : numpy.ndarray (bool)
An array of shape (n_subarrays,) where `True` indicates that any
channel belonging to that subarray should have jumps corrected.
Ignored if fix_each is False.
has_jumps : numpy.ndarray (bool)
A boolean mask of shape (n_channels,) where `True` indicates that a
channel has jumps that may be corrected.
subarray : numpy.ndarray (int)
An array of shape (n_channels,) containing the subarray number for
each channel.
jump_counter : numpy.ndarray (int)
The channel jumps of shape (n_frames, n_channels).
drift_size : int
The number of frames that constitute a single chunk that will have
jumps either levelled or flagged (see `min_jump_level_frames`). Each
chunk will be processed separately.
flag_before : int, optional
The number of frames to flag in the sample flags prior to a jump
detection with `jump_flag`.
flag_after : int, optional
The number of frames to flag in the sample flags following a jump
detection with `jump_flag`.
Returns
-------
inconsistencies : numpy.ndarray (int)
An array of shape (n_channels,) containing the number of frame blocks
found in the data that contain jumps which have either been levelled
or flagged for each channel.
"""
n_frames = frame_data.shape[0]
n_channels = channel_indices.size
inconsistencies = np.zeros(n_channels, dtype=nb.int64)
for start_frame in range(0, n_frames, drift_size):
end_frame = start_frame + drift_size
if end_frame > n_frames:
end_frame = n_frames
no_jumps = fix_jumps(
frame_valid=frame_valid,
frame_data=frame_data,
frame_weights=frame_weights,
modeling_frames=modeling_frames,
frame_parms=frame_parms,
sample_flags=sample_flags,
exclude_sample_flag=exclude_sample_flag,
channel_indices=channel_indices,
channel_parms=channel_parms,
min_jump_level_frames=min_jump_level_frames,
jump_flag=jump_flag,
fix_each=fix_each,
fix_subarray=fix_subarray,
has_jumps=has_jumps,
subarray=subarray,
jump_counter=jump_counter,
start_frame=start_frame,
end_frame=end_frame,
flag_before=flag_before,
flag_after=flag_after)
for i in range(n_channels):
if not no_jumps[i]:
inconsistencies[i] += 1
return inconsistencies
[docs]
@nb.njit(cache=True, nogil=False, parallel=False, fastmath=True)
def fix_jumps(frame_valid, frame_data, frame_weights,
modeling_frames, frame_parms, sample_flags, exclude_sample_flag,
channel_indices, channel_parms, min_jump_level_frames, jump_flag,
fix_each, fix_subarray, has_jumps, subarray, jump_counter,
start_frame=None, end_frame=None, flag_before=0, flag_after=0
): # pragma: no cover
"""
Detect and fix jumps in the frame data for each channel.
If a given channel has jumps and settings imply that jumps may be fixed,
the average frame data level between the jump is subtracted (frame and
channel dependents are also updated). A change in the jump level occurs
when the `jump_counter` value for a given channel changes to a new value.
a block is defined as all frames having the same jump value before a jump
change.
If the number of frames in a block is less than `min_jump_level_frames`,
those frames will be flagged with the `jump_flag` sample flag. Otherwise,
the frame data will be levelled as described above.
Parameters
----------
frame_valid : numpy.ndarray (bool)
A boolean mask of shape (n_frames,) where `False` excludes a frame
from any processing.
frame_data : numpy.ndarray (float)
The frame data values of shape (n_frames, all_channels).
frame_weights : numpy.ndarray (float)
The relative frame weights of shape (n_frames,).
modeling_frames : numpy.ndarray (bool)
A boolean mask of shape (n_frames,) where `True` marks a frame as a
modeling frame. Modeling frames will still be levelled, but will not
be included when updating the frame or channel dependents.
frame_parms : numpy.ndarray (float)
The frame dependents. Will be updated in-place if levelling occurs.
sample_flags : numpy.ndarray (int)
The frame data sample flags. Typically non-zero samples will be
excluded from processing. However, those samples not flagged with
only `exclude_sample_flag` will be included.
exclude_sample_flag : int
The sample flag to explicitly exclude from processing.
channel_indices : numpy.ndarray (int)
The channel indices of shape (n_channels,) indicating which channels
to process and mapping n_channels onto all_channels.
channel_parms : numpy.ndarray (float)
The channel dependents of shape (all_channels,). Will be updated
in-place when levelling occurs.
min_jump_level_frames : int
The minimum number of frames in a jump block required for levelling.
If this value is not reached, all samples in the block are flagged
with `jump_flag` instead.
jump_flag : int
The integer flag identifier with which to flag samples if the jump
block length to which they belong is less than `min_jump_level_frames`
and cannot be levelled.
fix_each : bool
If `False`, do not fix any channel with jumps.
fix_subarray : numpy.ndarray (bool)
An array of shape (n_subarrays,) where `True` indicates that any
channel belonging to that subarray should have jumps corrected.
Ignored if fix_each is False.
has_jumps : numpy.ndarray (bool)
A boolean mask of shape (n_channels,) where `True` indicates that a
channel has jumps that may be corrected.
subarray : numpy.ndarray (int)
An array of shape (n_channels,) containing the subarray number for
each channel.
jump_counter : numpy.ndarray (int)
The channel jumps of shape (n_frames, n_channels).
start_frame : int, optional
The start frame from which to begin correction. The default is the
first frame (0).
end_frame : int, optional
The last frame at which to conclude correction (non-inclusive). The
default is the total number of frames (n_frames).
flag_before : int, optional
The number of frames to flag in the sample flags prior to a jump
detection with `jump_flag`.
flag_after : int, optional
The number of frames to flag in the sample flags following a jump
detection with `jump_flag`.
Returns
-------
no_jumps : numpy.ndarray (bool)
A boolean mask of shape (n_channels,) where `True` indicates that a
channel has no jumps.
"""
no_jumps = np.full(channel_indices.size, True)
if not fix_each or not fix_subarray.any():
return no_jumps
if start_frame is None:
start_frame = 0
if end_frame is None:
end_frame = frame_data.shape[0]
for i, channel in enumerate(channel_indices):
if not has_jumps[i]:
continue
if not fix_subarray[subarray[i]]:
continue
blocks_fixed = 0
started = False
jump_start = -1
from_frame = -1
first_frame = -1
skip_start = 0
skip_end = flag_before
n1_frame = frame_data.shape[0] - 1
for frame in range(start_frame, end_frame):
if not frame_valid[frame]:
continue
if not started:
jump_start = jump_counter[frame, channel]
first_frame = from_frame = frame
started = True
jump = jump_counter[frame, channel]
if jump == jump_start:
continue
if frame == n1_frame:
skip_end = 0
fix_block(from_frame=from_frame,
to_frame=frame,
frame_valid=frame_valid,
frame_data=frame_data,
frame_weights=frame_weights,
modeling_frames=modeling_frames,
frame_parms=frame_parms,
sample_flags=sample_flags,
exclude_sample_flag=exclude_sample_flag,
channel=channel,
channel_parms=channel_parms,
min_jump_level_frames=min_jump_level_frames,
jump_flag=jump_flag,
skip_start=skip_start,
skip_end=skip_end)
if flag_before != 0:
flag_start = max(0, frame - flag_before)
for flag_frame in range(flag_start, frame):
if frame_valid[flag_frame]:
sample_flags[flag_frame, channel] |= jump_flag
if flag_after != 0:
flag_stop = min(frame + flag_after, end_frame)
for flag_frame in range(frame, flag_stop):
if frame_valid[flag_frame]:
sample_flags[flag_frame, channel] |= jump_flag
blocks_fixed += 1
from_frame = frame
jump_start = jump
skip_start = flag_after
if first_frame != from_frame: # Fix the end
fix_block(from_frame=from_frame,
to_frame=end_frame,
frame_valid=frame_valid,
frame_data=frame_data,
frame_weights=frame_weights,
modeling_frames=modeling_frames,
frame_parms=frame_parms,
sample_flags=sample_flags,
exclude_sample_flag=exclude_sample_flag,
channel=channel,
channel_parms=channel_parms,
min_jump_level_frames=min_jump_level_frames,
jump_flag=jump_flag,
skip_start=skip_start,
skip_end=0)
blocks_fixed += 1
no_jumps[i] = blocks_fixed == 0
return no_jumps
[docs]
@nb.njit(cache=True, nogil=False, parallel=False, fastmath=True)
def fix_block(from_frame, to_frame, frame_valid, frame_data, frame_weights,
modeling_frames, frame_parms, sample_flags, exclude_sample_flag,
channel, channel_parms, min_jump_level_frames, jump_flag,
skip_start=0, skip_end=0): # pragma: no cover
"""
Jump correct a block of frames for a given channel.
See :func:`fix_jumps` for further details. This function essentially
compares the jump block length with `min_jump_level_frames` and determines
whether the block should be flagged or levelled. If the number of frames
in the block (`to_frame` - `from_frame`) is >= `min_jump_level_frames`,
then leveling will occur. Otherwise, all samples in the block will be
flagged with the `jump_flag`.
Parameters
----------
from_frame : int
The starting frame from which to begin jump correction.
to_frame : int
The end frame (non-inclusive) at which to conclude jump correction.
frame_valid : numpy.ndarray (bool)
A boolean mask of shape (n_frames,) where `False` excludes a frame
from any processing.
frame_data : numpy.ndarray (float)
The frame data values of shape (n_frames, all_channels).
frame_weights : numpy.ndarray (float)
The relative frame weights of shape (n_frames,).
modeling_frames : numpy.ndarray (bool)
A boolean mask of shape (n_frames,) where `True` marks a frame as a
modeling frame. Modeling frames will still be levelled, but will not
be included when updating the frame or channel dependents.
frame_parms : numpy.ndarray (float)
The frame dependents. Will be updated in-place if levelling occurs.
sample_flags : numpy.ndarray (int)
The frame data sample flags. Typically non-zero samples will be
excluded from processing. However, those samples not flagged with
only `exclude_sample_flag` will be included.
exclude_sample_flag : int
The sample flag to explicitly exclude from processing.
channel : int
The channel to process.
channel_parms : numpy.ndarray (float)
The channel dependents of shape (all_channels,). Will be updated
in-place when levelling occurs.
min_jump_level_frames : int
The minimum number of frames in a jump block required for levelling.
If this value is not reached, all samples in the block are flagged
with `jump_flag` instead.
jump_flag : int
The integer flag identifier with which to flag samples if the jump
block length to which they belong is less than `min_jump_level_frames`
and cannot be levelled.
skip_start : int, optional
The number of frames before the jump to not include in levelling
determinations.
skip_end : int, optional
The number of frames at the end of the block jump to not include in
levelling determinations.
Returns
-------
None
"""
n_level = (to_frame - from_frame) - (skip_start + skip_end)
if n_level < min_jump_level_frames:
flag_block(from_frame=from_frame,
to_frame=to_frame,
frame_valid=frame_valid,
sample_flags=sample_flags,
jump_flag=jump_flag,
channel=channel)
else:
level_block(from_frame=from_frame,
to_frame=to_frame,
frame_valid=frame_valid,
frame_data=frame_data,
frame_weights=frame_weights,
modeling_frames=modeling_frames,
frame_parms=frame_parms,
sample_flags=sample_flags,
exclude_sample_flag=exclude_sample_flag,
channel=channel,
channel_parms=channel_parms,
skip_start=skip_start,
skip_end=skip_end)
[docs]
@nb.njit(cache=True, nogil=False, parallel=False, fastmath=True)
def flag_block(from_frame, to_frame, frame_valid, sample_flags, jump_flag,
channel): # pragma: no cover
"""
Flag all samples within a block for a given channel with a jump flag.
Parameters
----------
from_frame : int
The starting frame from which to begin jump correction.
to_frame : int
The end frame (non-inclusive) at which to conclude jump correction.
frame_valid : numpy.ndarray (bool)
A boolean mask of shape (n_frames,) where `False` excludes a frame
from any processing.
sample_flags : numpy.ndarray (int)
The frame data sample flags. Typically non-zero samples will be
excluded from processing. However, those samples not flagged with
only `exclude_sample_flag` will be included.
jump_flag : int
The integer flag identifier with which to flag samples if the jump
block length to which they belong is less than `min_jump_level_frames`
and cannot be levelled.
channel : int
The channel to process.
Returns
-------
None
"""
for frame in range(from_frame, to_frame):
if not frame_valid[frame]:
continue
sample_flags[frame, channel] |= jump_flag
[docs]
@nb.njit(cache=True, nogil=False, parallel=False, fastmath=True)
def level_block(from_frame, to_frame, frame_valid, frame_data, frame_weights,
modeling_frames, frame_parms, sample_flags,
exclude_sample_flag, channel,
channel_parms, skip_start=0, skip_end=0
): # pragma: no cover
"""
Level a frame block for a given channel.
For a given channel i, all frames that are valid (but may be modeling
frames) will be corrected by:
d[f:t, i] -= sum_{f|nm}(w[f] * d[f]) / w_sum
w_sum = sum_{f|nm}(w[f])
Where f is the `from_frame`, t is the `to_frame`, d are the `frame_data`,
w are the `frame_weights`, and f|nm are the set of frames in the range t:f
that are valid and non-modeling frames.
If w_sum > 0 then the channel dependents are incremented by 1 and the frame
dependents for frame j are updated by:
dp[j] += w[j] / w_sum
if j is valid, non-modeling and the associated sample flag is not excluded
by `exclude_sample_flag`.
Parameters
----------
from_frame : int
The starting frame from which to begin jump correction.
to_frame : int
The end frame (non-inclusive) at which to conclude jump correction.
frame_valid : numpy.ndarray (bool)
A boolean mask of shape (n_frames,) where `False` excludes a frame from
any processing.
frame_data : numpy.ndarray (float)
The frame data values of shape (n_frames, all_channels).
frame_weights : numpy.ndarray (float)
The relative frame weights of shape (n_frames,).
modeling_frames : numpy.ndarray (bool)
A boolean mask of shape (n_frames,) where `True` marks a frame as a
modeling frame. Modeling frames will still be levelled, but will not
be included when updating the frame or channel dependents.
frame_parms : numpy.ndarray (float)
The frame dependents of shape (n_frames,). Will be updated in-place.
sample_flags : numpy.ndarray (int)
The frame data sample flags. Any samples flagged with
`exclude_sample_flag` will not be included in the mean value
calculation or contribute to the frame dependents. However, the
mean value will still be subtracted from them.
exclude_sample_flag : int
The sample flag to explicitly exclude from processing.
channel : int
The channel to process.
channel_parms : numpy.ndarray (float)
The channel dependents of shape (all_channels,). Will be updated
in-place.
skip_start : int, optional
The number of frames to flag in the sample flags prior to a jump
detection with `jump_flag`.
skip_end : int, optional
The number of frames to flag in the sample flags following a jump
detection with `jump_flag`.
Returns
-------
None
"""
d_sum = 0.0
w_sum = 0.0
average_start = from_frame + skip_start
average_end = to_frame - skip_end
if average_end <= average_start:
return
for frame in range(average_start, average_end):
if not frame_valid[frame] or modeling_frames[frame]:
continue
if (sample_flags[frame, channel] & exclude_sample_flag) != 0:
continue
w = frame_weights[frame]
if w == 0:
continue
d_sum += w * frame_data[frame, channel]
w_sum += w
if w_sum <= 0:
return
channel_parms[channel] += 1
average = d_sum / w_sum
for frame in range(from_frame, to_frame):
if not frame_valid[frame]:
continue
frame_data[frame, channel] -= average
if modeling_frames[frame]:
continue
if (sample_flags[frame, channel] & exclude_sample_flag) != 0:
continue
frame_parms[frame] += frame_weights[frame] / w_sum
[docs]
@nb.njit(cache=True, nogil=False, parallel=False, fastmath=False)
def correct_jumps(frame_data, frame_valid, jump_counter, channel_indices,
channel_jumps, jump_range): # pragma: no cover
"""
Correct DAC jumps in the frame data.
Frame data are corrected in-place by:
d[f, c] -= channel_jumps[c] * n_jumps_{f,c}
where d are the frame data, f is the frame, c is the channel and n_jumps
are the number of jumps detected since the first valid frame or:
n_jumps_{f,c} = jump_counter[f, c] - jump_counter[f0, c]
where f0 is the first valid frame. Since the jump counter have byte
values, wrap around values are considered.
Parameters
----------
frame_data : numpy.ndarray (float)
The frame data of shape (n_frames, all_channels). Values are updated
in-place.
frame_valid : numpy.ndarray (bool)
A boolean mask of shape (n_frames,) where `False` excludes a frame from
any processing.
jump_counter : numpy.ndarray (int)
The jump counter of shape (n_frames, all_channels).
channel_indices : numpy.ndarray (int)
The channel indices for which to correct jumps of shape (n_channels,).
Should map n_channels -> all_channels.
channel_jumps : numpy.ndarray (float)
The jump values of shape (n_channels,).
jump_range : int
The maximum jump range (bytes) to check for wrap around values.
Returns
-------
None
"""
n_frames = frame_data.shape[0]
max_jump = jump_range // 2
min_jump = -max_jump
start_frame = -1
for frame in range(n_frames):
if frame_valid[frame]:
start_frame = frame
break
if start_frame < 0: # no valid frames
return
start_jumps = np.empty(channel_indices.size, dtype=nb.int64)
for i, channel in enumerate(channel_indices):
start_jumps[i] = jump_counter[start_frame, channel]
for frame in range(n_frames):
if not frame_valid[frame]:
continue
for i, channel in enumerate(channel_indices):
if channel_jumps[i] == 0:
continue
n_jumps = jump_counter[frame, channel] - start_jumps[i]
if n_jumps == 0:
continue # no change
elif n_jumps > max_jump:
n_jumps -= jump_range # Wrap around in positive direction
elif n_jumps < min_jump:
n_jumps += jump_range # Wrap around in negative direction
jump_correction = n_jumps * channel_jumps[i]
frame_data[frame, channel] -= jump_correction
[docs]
@nb.njit(cache=True, nogil=False, parallel=False, fastmath=True)
def flag_zeroed_channels(frame_data, frame_valid, channel_indices,
channel_flags, discard_flag): # pragma: no cover
"""
Flag channels with the DISCARD flag if all frame data are zero valued.
Parameters
----------
frame_data : numpy.ndarray (float)
The frame data of shape (n_frames, all_channels).
frame_valid : numpy.ndarray (bool)
A boolean mask of shape (n_frames,) where `False` excludes a frame
from checks.
channel_indices : numpy.ndarray (int)
The channel indices for which to check zero-levels of shape
(n_channels,). Should map n_channels -> all_channels.
channel_flags : numpy.ndarray (int)
The channel flags of shape (n_channels,).
discard_flag : int
The flag to apply to channel flags if all frames are zero valued.
Returns
-------
None
"""
n_frames = frame_data.shape[0]
for i, channel in enumerate(channel_indices):
channel_is_flagged = (channel_flags[i] & discard_flag) != 0
for frame in range(n_frames):
if frame_valid[frame] and frame_data[frame, channel] != 0:
# Unflag if necessary
if channel_is_flagged:
channel_flags[i] ^= discard_flag
break
else:
# Flag if all frame data are invalid or zero-valued.
if not channel_is_flagged:
channel_flags[i] |= discard_flag
[docs]
@nb.njit(cache=True, nogil=False, parallel=False, fastmath=True)
def check_jumps(start_counter, jump_counter, frame_valid, has_jumps,
channel_indices): # pragma: no cover
"""
Checks for jumps in each channel.
A jump will be recorded for a channel if the jump counter value is not
constant over all frames.
Parameters
----------
start_counter : numpy.ndarray (int)
The first valid frame jump counter value for each channel of shape
(all_channels,).
jump_counter : numpy.ndarray (int)
The jump counter of shape (n_frames, all_channels).
frame_valid : numpy.ndarray (bool)
A boolean mask of shape (n_frames,) where `False` excludes a frame from
standard processing, although it is not used here except to determine
the number of frames.
has_jumps : numpy.ndarray (bool)
A boolean mask indicating whether a channel has jumps of shape
(n_channels,). Will be updated in-place.
channel_indices : numpy.ndarray (int)
An array containing the channel indices for all channels to be checked
of shape (n_channels,). Should map n_channels -> all_channels.
Returns
-------
jumps_found : int
The number of channels found with jumps.
"""
n_frames = frame_valid.size
jumps_found = 0
for i, channel in enumerate(channel_indices):
jump_start = start_counter[channel]
for frame in range(n_frames):
if jump_counter[frame, channel] != jump_start:
has_jumps[i] = True
jumps_found += 1
break
else:
has_jumps[i] = False
return jumps_found
@nb.njit(cache=True, nogil=False, parallel=False, fastmath=False)
def detect_jumps(data, has_jumps, jumps, threshold, start_pad=0, end_pad=0
): # pragma: no cover
"""
Detect and mark unreported jumps in the frame data.
Parameters
----------
data : numpy.ndarray (float)
The frame data of shape (n_frames, n_channels).
has_jumps : numpy.ndarray (bool)
A boolean array of shape (n_channels,) where `True` indicates a channel
has previously detected jumps.
jumps : numpy.ndarray (int)
The jump counter of shape (n_frames, n_channels). A jump occurs at
frame i if jumps[i - 1, channel] != jumps[i, channel]. Updated
in-place whenever a jump is found.
threshold : float
The threshold determining possible jump locations and sustained
difference between frame data before and after a jump for validation
purposes.
start_pad : int, optional
The number of frames prior to a jump to exclude from any calculations.
Note that if provided, the jump will be marked at frame i + start_pad
where i is actual start of the detected jump. The fixjumps algorithm
should also use the same padding for consistency.
end_pad : int, optional
The number of frames after a jump to exclude from any calculations.
Returns
-------
jumps_found : numpy.ndarray (bool)
A boolean mask of shape (n_frames, n_channels) where `True` marks the
location of a newly found jump.
"""
n_frames, n_channels = data.shape
temp_data = np.zeros(n_frames, dtype=nb.float64)
median_array = np.empty(n_frames, dtype=nb.float64)
jumps_found = np.full((n_frames, n_channels), False)
n1 = n_frames - 1
for channel in range(n_channels):
if has_jumps[channel]:
continue # only want to find unreported jumps
channel_data = data[:, channel]
channel_jumps = jumps[:, channel]
last = channel_data[0]
valid = 0
for frame in range(1, n_frames):
current = channel_data[frame]
if np.isnan(current):
temp_data[frame] = 0.0
continue
if np.isnan(last):
last = current
diff = np.abs(current - last)
temp_data[frame] = diff
median_array[valid] = diff
last = current
valid += 1
if valid < 5:
continue
median_value = np.median(median_array[:valid])
limit = threshold * median_value
last_start = -1
last_end = -1
median = np.nan
last_median = np.nan
current_start = -1
count = 0
for frame in range(n_frames):
if frame <= last_end:
continue
if current_start == -1:
if temp_data[frame] < limit:
# append to median
value = channel_data[frame]
if np.isfinite(value):
median_array[count] = value
count += 1
else: # new jump
current_start = frame
if count > 0:
median = np.median(median_array[:count])
else:
median = last_median
# Check if old jump can be added in
if np.abs(median - last_median) >= limit:
mark_jump = last_start + start_pad - 1
mark_jump = max(min(mark_jump, n1), 0)
for i in range(mark_jump, n_frames):
channel_jumps[i] += 1
jumps_found[mark_jump, channel] = True
has_jumps[channel] = True
elif temp_data[frame] < limit: # jump ended
last_start = current_start
last_median = median
last_end = frame + end_pad
current_start = -1
count = 0
# Check the end
if last_start != -1 and count > 0:
median = np.median(median_array[:count])
if np.abs(median - last_median) >= limit:
mark_jump = last_start + start_pad - 1
mark_jump = max(min(mark_jump, n1), 0)
for i in range(mark_jump, n_frames):
channel_jumps[i] += 1
jumps_found[mark_jump, channel] = True
has_jumps[channel] = True
return jumps_found