def to_physiosignal(uuid, waveform_name, t, s, dt): physio_label = '' t_first_trigger = None # specify suffix: if 'PULS' in waveform_name: physio_label = 'cardiac' elif 'RESP' in waveform_name: physio_label = 'respiratory' elif 'EXT' in waveform_name: physio_label = 'external_trigger' elif 'ECG' in waveform_name: physio_label = 'ecg' elif "ACQUISITION_INFO" in waveform_name: physio_label = 'trigger' # We only care about the trigger for each volume, so keep only # the timepoints for which the trigger signal is 1: t = t[np.where(s == 1)] s = np.full(len(t), True) # time for the first trigger: t_first_trigger = t[0] / 1000 physio_signal = PhysioSignal( label=physio_label, uuid=uuid, samples_per_second=1000 / dt, # dt is in ms. sampling_times=t / 1000, physiostarttime=t[0] / 1000, signal=s) return physio_label, physio_signal, t_first_trigger
def test_calculate_trigger_events(capfd, mySignal, trigger_timing): """ Make sure you get as many triggers in the trigger signal as elements there are in the trigger timing (between the beginning of the recording and the end) """ # 1) If you try to calculate it for a signal for which we cannot calculate # the timing, it should print an error and return None: assert PhysioSignal(label='simulated', physiostarttime=PHYSIO_START_TIME ).calculate_trigger_events(trigger_timing) is None assert capfd.readouterr( ).out == "Unable to calculate the recording timing\n" # 2) Run it successfully: # calculate trigger events: trig_signal = mySignal.calculate_trigger_events(trigger_timing) assert isinstance(trig_signal, np.ndarray) # calculate how many triggers there are between the first and last sampling_times: num_trig_within_physio_samples = np.bitwise_and( np.array(trigger_timing) >= mySignal.sampling_times[0], np.array(trigger_timing) <= mySignal.sampling_times[-1]) assert (sum(trig_signal) == sum(num_trig_within_physio_samples))
def pmu2bids(physio_files, verbose=False): """ Function to read a list of Siemens PMU physio files and save them as a BIDS physiological recording. Parameters ---------- physio_files : list of str list of paths to files with a Siemens PMU recording verbose : bool verbose flag Returns ------- physio : PhysioData PhysioData with the contents of the file """ # In case we are handled just a single file, make it a one-element list: if isinstance(physio_files, str): physio_files = [physio_files] # Init PhysioData object to hold physio signals: physio = PhysioData() # Read the files from the list, extract the relevant information and # add a new PhysioSignal to the list: for f in physio_files: physio_type, MDHTime, sampling_rate, physio_signal = readpmu(f, verbose=verbose) testSamplingRate( sampling_rate = sampling_rate, Nsamples = len(physio_signal), logTimes=MDHTime ) # specify label: if 'PULS' in physio_type: physio_label = 'cardiac' elif 'RESP' in physio_type: physio_label = 'respiratory' elif "TRIGGER" in physio_type: physio_label = 'trigger' else: physio_label = physio_type physio.append_signal( PhysioSignal( label=physio_label, units='', samples_per_second=sampling_rate, physiostarttime=MDHTime[0], signal=physio_signal ) ) return physio
def test_plug_missing_data(): """ Test for plug_missing_data """ # generate a PhysioSignal with a temporal series and corresponding fake signal with # missing timepoints: st = [i / 1 for i in range(35) if i % 10] spamSignal = PhysioSignal(label='simulated', samples_per_second=1, sampling_times=st, signal=[i for i in range(len(st))]) spamSignal.plug_missing_data() assert all(np.ediff1d(spamSignal.sampling_times)) == 1 assert all( np.isnan(spamSignal.signal[[ i for i in range(len(spamSignal.signal)) if not (i + 1) % 10 ]])) assert len(spamSignal.signal) == spamSignal.samples_count
def mySignal(scope="module"): """ Simulate a PhysioSignal object """ mySignal = PhysioSignal( label='simulated', samples_per_second=PHYSIO_SAMPLES_PER_SECOND, physiostarttime=PHYSIO_START_TIME, neuralstarttime=PHYSIO_START_TIME + SCANNER_DELAY, signal=PHYSIO_SAMPLES_COUNT * [0] # fill with zeros ) return mySignal
def myphysiodata(scope="module"): """ Create a "PhysioData" object with barebones content """ myphysiodata = PhysioData([ PhysioSignal(label=l, samples_per_second=PHYSIO_SAMPLES_PER_SECOND, physiostarttime=PHYSIO_START_TIME, neuralstarttime=PHYSIO_START_TIME + SCANNER_DELAY, signal=[i for i in range(PHYSIO_SAMPLES_COUNT)]) for l in LABELS ]) return myphysiodata
def test_append_signal(myphysiodata): """ Tests that "append_signal" does what it is supposed to do """ # Make a copy of myphysiodata to make sure we don't modify it, # so that it is later available unmodified to other tests: physdata = copy.deepcopy(myphysiodata) physdata.append_signal(PhysioSignal(label='extra_signal')) mylabels = LABELS.copy() mylabels.append('extra_signal') assert physdata.labels() == mylabels
def myphysiodata_with_edf_trigger(myphysiodata, simulated_edf_trigger_signal, scope="module"): myphysiodata_with_edf_trigger = copy.deepcopy(myphysiodata) # add a trigger signal to the physiodata_with_trigger: myphysiodata_with_edf_trigger.append_signal( PhysioSignal(label='trigger', samples_per_second=TRIGGER_SAMPLES_PER_SECOND, physiostarttime=TRIGGER_START_TIME, neuralstarttime=TRIGGER_START_TIME, signal=simulated_edf_trigger_signal)) return myphysiodata_with_edf_trigger
def test_calculate_timing(mySignal): """ Test for calculate_timing It checks that it gives an error when it is supposed to, and it returns the correct timing when the neccessary parameters are present """ # 1) Try with a PhysioSignal without sampling rate: with pytest.raises(Exception) as e_info: PhysioSignal(label='simulated', physiostarttime=PHYSIO_START_TIME).calculate_timing() # 2) With a correct signal: mySignal.calculate_timing() assert len(mySignal.sampling_times) == PHYSIO_SAMPLES_COUNT assert mySignal.sampling_times[0] == mySignal.physiostarttime np.testing.assert_allclose(np.ediff1d(mySignal.sampling_times), 1 / mySignal.samples_per_second, 1e-10)
def test_matching_trigger_signal(mySignal, trigger_timing): """ Test that both PhysioSignals (the original signal and the derived one with the trigger) have the same fields. It requires the result of "test_calculate_trigger_events" """ # calculate trigger events: trig_signal = mySignal.calculate_trigger_events(trigger_timing) trigger_physiosignal = PhysioSignal.matching_trigger_signal( mySignal, trig_signal) assert isinstance(trigger_physiosignal, PhysioSignal) assert trigger_physiosignal.label == 'trigger' assert trigger_physiosignal.samples_per_second == mySignal.samples_per_second assert trigger_physiosignal.physiostarttime == mySignal.physiostarttime assert trigger_physiosignal.neuralstarttime == mySignal.neuralstarttime assert trigger_physiosignal.sampling_times == mySignal.sampling_times assert all(trigger_physiosignal.signal == trig_signal)
def acq2bids(physio_acq_files, trigger_labels=['trigger', 'digital input']): """Reads the physiological data from a series of AcqKnowledge files and stores it in a PhysioData member Parameters ---------- physio_acq_files : list of str List of paths of the original physio files trigger_labels : list of str List with labels of the channel that carries the scanner trigger. Just one word from the channel name is enough Returns ------- physio : PhysioData PhysioData with the contents of the file """ # In case we are handled just a single file, make it a one-element list: if isinstance(physio_acq_files, str): physio_acq_files = [physio_acq_files] if not isinstance(trigger_labels, list): trigger_labels = [trigger_labels] # Init PhysioData object to hold physio signals: physio = PhysioData() # Read the files from the list, extract the relevant information and # add a new PhysioSignal to the list: trigger_channel = '' for physio_acq in physio_acq_files: # Extract data from AcqKnowledge file: physio_data = bioread.read(physio_acq) # Get the time the file was created: physiostarttime = physio_data.earliest_marker_created_at for item in physio_data.channels: physio_label = '' # specify label: if 'puls' in item.name.lower(): physio_label = 'cardiac' elif 'resp' in item.name.lower(): physio_label = 'respiratory' elif any( [tl.lower() in item.name.lower() for tl in trigger_labels]): physio_label = 'trigger' trigger_channel = item.name else: physio_label = item.name if physio_label: physio.append_signal( PhysioSignal(label=physio_label, samples_per_second=item.samples_per_second, sampling_times=item.time_index, physiostarttime=physiostarttime.timestamp(), signal=item.data, units=item.units)) # Get the "neuralstarttime" for the PhysioSignals by finding the first trigger. # We do this after we have read all signals to make sure we have read the trigger # (if present in the file. If not present, use the physiostart time. This is the # same as assuming the physiological recording started at the same time as the # neural recording.) # This assumes that the channel named "trigger" indeed contains the scanner trigger # and not something else (e.g., stimulus trigger). So we print a warning. neuralstarttime = '' if trigger_channel: print( 'Warning: Assuming "{}" channel corresponds to the scanner trigger' .format(trigger_channel)) # The sampling_times are w.r.t. the start of the recording, so we need # to also add the 'physiostarttime' (time when file was created): neuralstarttime = physio.get_scanner_onset( ) + physiostarttime.timestamp() for p_signal in physio.signals: p_signal.neuralstarttime = neuralstarttime or p_signal.physiostarttime # we also fill with NaNs the places for which there is missing data: p_signal.plug_missing_data() return physio