def work(self): sig_in = self.requires().output().read() self.update_status("Read raw signal. Applying bandpass filter.") # We cannot directly use fklab's "compute_envelope", as this function # averages all channel envelopes into one. bpf_out = apply_filter( sig_in, axis=0, band=self.freq_band, fs=sig_in.fs, transition_width="20%", attenuation=30, ) self.update_status( "Applied bandpass filter. Calculating raw envelope via Hilbert transform." ) # Use padding to nearest power of 2 or 3 when calculating Hilbert # transform for great speedup (via FFT). N_orig = sig_in.shape[0] N = int(min(array([2, 3])**ceil(log(N_orig) / log([2, 3])))) envelope_raw_padded = abs(analytical(bpf_out, N=N, axis=0)) del bpf_out envelope_raw = envelope_raw_padded[:N_orig, :] self.update_status("Calculated raw envelope. Smoothing envelope.") del envelope_raw_padded envelope_smooth = smooth1d(envelope_raw, delta=1 / sig_in.fs, kernel="gaussian", bandwidth=4e-3) self.update_status("Smoothed envelope. Writing envelope to disk.") del envelope_raw sig_out = Signal(envelope_smooth, sig_in.fs, sig_in.units) del envelope_smooth self.output().write(sig_out) del sig_in, sig_out
def work(self): input_signal = self.multichannel_full[:, self.channels] filter_weights = self.trainer.output().read() filter_output = convolve_spatiotemporal(input_signal, filter_weights, self.delays) envelope = np.abs(filter_output) self.output().write(Signal(envelope, input_signal.fs))
def work(self): raw_recording = self.requires().output() fs_orig = raw_recording.fs q, remainder = divmod(fs_orig, config.fs_target) fs_new = fs_orig / q if remainder > 0: getLogger(__name__).warning( f"Original sampling rate of {self.file_ID} ({fs_orig} Hz) is" f" not an integer multiple of the target sampling rate" f" ({config.fs_target} Hz). Sampling rate after downsampling" f" will instead be {fs_new} Hz." ) self.update_status( f"Decimating {self.file_ID} ({self.file_ID.path}) of size" f" {self.file_ID.path.stat().st_size / 1E9:.1f} GB by a factor {q}." ) t_prev = time() def track_downsampling_progress(progress: float): nonlocal t_prev t_now = time() time_passed = t_now - t_prev if time_passed > 5: self.update_progress(progress, "Downsampling progress") t_prev = t_now signal_down = decimate_chunkwise( raw_recording.signal, factor=q, loop_callback=track_downsampling_progress, ) signal_down *= raw_recording.to_microvolts raw_recording.close() self.output().write(Signal(signal_down, fs_new, "μV"))
def hilbert_fast(sig: Signal): # Zero-pad signal to nearest power of 2 or 3 in order to speed up # computation exponents = ceil(log(sig.num_samples) / log([2, 3])) N = int(min(array([2, 3])**exponents)) analytic = hilbert(sig, N) return Signal(analytic[:sig.num_samples], sig.fs)
def work(self): fs = self.input_signal.fs self.ripple_filter.order = self.order self.ripple_filter.fs = fs b, a = self.ripple_filter.tf filtered = lfilter(b, a, self.input_signal) envelope = abs(filtered) self.output().write(Signal(envelope, fs))
def read(self) -> Signal: log.info(f"Reading signal file at {self} ({self.size}) into memory.") with self.open_file_for_read() as f: dataset = f[self.KEY_SIG] array = dataset[:] fs = dataset.attrs[self.KEY_FS] units = dataset.attrs.get(self.KEY_UNITS, None) return Signal(array, fs, units)
def e_t(self): logger.info("Smoothing envelope..") unsmoothed = abs(self.analytic) rm = self.reference_maker envelope = smooth1d(self.envelope_unsmoothed, delta=1 / self.fs, **rm.smooth_options) logger.info("Done") return Signal(envelope, self.fs)
def o_t(self): logger.info("Band-pass-filtering signal..") rm = self.reference_maker filter_output = apply_filter(self.x_t, rm.band, fs=self.fs, **rm.filter_options) logger.info("Done") return Signal(filter_output, self.fs)
def ripple_envelope(self) -> Signal: """ Offline and ripple-only algorithm. """ ripple_envelope = compute_envelope( self.ripple_channel, self.ripple_band, fs=self.ripple_channel.fs, filter_options=self.ripple_filter_options, smooth_options=self.ripple_smooth_options, ) return Signal(ripple_envelope, self.ripple_channel.fs)
def work(self): signal = self.multichannel_train[:, self.channels] segments = self.reference_segs_train data = Signal(data=delay_stack(signal, self.delays), fs=signal.fs) reference = _concat_extracts(data, segments) background = _concat_extracts(data, segments.invert()) log.info(f"Reference data length: {reference.duration} s") log.info(f"Background data length: {background.duration} s") # Columns = channels = variables. Rows are (time) samples. Rss = _as_matrix(cov(reference, rowvar=False)) Rnn = _as_matrix(cov(background, rowvar=False)) GEVals, GEVecs = eigh(Rss, Rnn) first_GEVec = GEVecs[:, argmax(GEVals)] self.output().write(first_GEVec)
def target_signal(self) -> BinarySignal: """ Binary target, or training signal, as a one-column matrix. """ segs = self.reference_segs_train.scale( 1 + config.reference_seg_extension, reference=1 ) N = self.reference_channel_train.shape[0] sig = np.zeros(N) # Convert segment times to a binary signal (in a one-column matrix) that # is as long as the full training input signal. if config.target_fullrect: self._add_rects(sig, segs) else: self._add_start_rects(sig, segs) # self._add_triangles(sig, segs) return Signal(sig, self.reference_channel_train.fs).as_matrix()
def _concat_extracts(data: Signal, segs: Segment, max_duration=60) -> Signal: """ :return: Concatenated array of `segs` extracted from `data`. :param data: :param segs: :param max_duration: Resulting array will be no longer than this. (Goal: limit memory usage). """ # Might wanna randomize segs order here. duration = 0 extracts = [] for extract in data.extract(segs): duration += extract.duration if duration > max_duration: break else: extracts.append(extract) catena = concatenate(extracts) return Signal(catena, data.fs)
def work(self): envelope_chunks = [] inputt = self.multichannel_full.as_matrix()[:, self.channels] # Full input signal is too big for GPU memory. # Thus: split input, pass through h, concatenate results num_chunks = inputt.duration // self.seconds_per_chunk input_chunks = array_split(inputt, num_chunks, axis=inputt.time_axis) model: RNN = self.model_selector.output().read() h = model.get_init_h() for i, input_chunk in enumerate(input_chunks): log.info(f"Transforming chunk {i} of {num_chunks}") with torch.no_grad(): input_torch = self.as_model_io(input_chunk) output, h = model(input_torch, h) # Cannot use torch.nn.functional.sigmoid (deprecated). envelope: TorchArray = torch.sigmoid(output.squeeze()) envelope_cpu = envelope.to("cpu") envelope_numpy = envelope_cpu.numpy() envelope_chunks.append(envelope_numpy) envelope_full = concatenate(envelope_chunks) sig = Signal(envelope_full, inputt.fs) self.output().write(sig)
def SW_LPF(self, signal: Signal) -> Signal: fn = signal.fs / 2 order = 5 ba = butter(order, self.SW_cutoff / fn, "low") out = filtfilt(*ba, signal) return Signal(out, signal.fs)
def plot_input(self, ax, trange): LFP_data = stack([rm.sr_channel, rm.ripple_channel, rm.toppyr_channel], axis=1) LFP = Signal(LFP_data, rm.sr_channel.fs) plot_sig(LFP, ax, trange) add_voltage_scalebar(ax)