def set_data(self, data, sf=1., method='fourier', nperseg=256, f_min=1., f_max=160., f_step=1., baseline=None, norm=None, n_window=None, overlap=0., window=None, c_parameter=20, clim=None, cmap='viridis', vmin=None, under=None, vmax=None, over=None): """Compute TF and set data to the ImageObj.""" # ======================= CHECKING ======================= assert isinstance(data, np.ndarray) and data.ndim == 1 assert isinstance(sf, (int, float)) assert method in ('fourier', 'wavelet', 'multitaper') if not isinstance(window, str): window = 'hamming' if method is 'fourier' else 'flat' assert 0. <= overlap < 1. # Wavelet args : assert isinstance(f_min, (int, float)) assert isinstance(f_max, (int, float)) assert isinstance(f_step, (int, float)) # Spectrogram and Multi-taper args : noverlap = int(round(overlap * nperseg)) assert isinstance(nperseg, int) assert isinstance(c_parameter, int) # Update color arguments : self._update_cbar_args(cmap, clim, vmin, vmax, under, over) logger.info(" Compute time-frequency decomposition using the" " %s method" % method) if method == 'fourier': freqs, time, tf = spectrogram(data, sf, nperseg=nperseg, noverlap=noverlap, window=window) if method == 'wavelet': n_pts = len(data) freqs = np.arange(f_min, f_max, f_step) time = np.arange(n_pts) / sf tf = np.zeros((len(freqs), n_pts), dtype=data.dtype) # Compute TF and inplace normalization : logger.info(" Compute the time-frequency map (" "normalization=%r)" % norm) for i, k in enumerate(freqs): tf[i, :] = np.square(np.abs(morlet(data, sf, k))) normalization(tf, norm=norm, baseline=baseline, axis=1) # Averaging : if isinstance(n_window, int): logger.info(" Averaging time-frequency map using windows of" " size %i with a %f overlap" % (n_window, overlap)) kw = dict(overlap=overlap, window=window) tf = averaging(tf, n_window, axis=1, **kw) time = averaging(time, n_window, **kw) elif method == 'multitaper': is_lspopt_installed(raise_error=True) from lspopt import spectrogram_lspopt freqs, time, tf = spectrogram_lspopt(data, sf, nperseg=nperseg, noverlap=noverlap, c_parameter=c_parameter) # Set data to the image object : ImageObj.set_data(self, tf, xaxis=time, yaxis=freqs, **self.to_kwargs())
def set_data(self, data, sf=1., method='fourier', nperseg=256, f_min=1., f_max=160., f_step=1., baseline=None, norm=None, n_window=None, overlap=0., window=None, c_parameter=20, clim=None, cmap='viridis', vmin=None, under=None, vmax=None, over=None): """Compute TF and set data to the ImageObj.""" # ======================= CHECKING ======================= assert isinstance(data, np.ndarray) and data.ndim == 1 assert isinstance(sf, (int, float)) assert method in ('fourier', 'wavelet', 'multitaper') if not isinstance(window, str): window = 'hamming' if method is 'fourier' else 'flat' assert 0. <= overlap < 1. # Wavelet args : assert isinstance(f_min, (int, float)) assert isinstance(f_max, (int, float)) assert isinstance(f_step, (int, float)) # Spectrogram and Multi-taper args : noverlap = int(round(overlap * nperseg)) assert isinstance(nperseg, int) assert isinstance(c_parameter, int) # Update color arguments : self._update_cbar_args(cmap, clim, vmin, vmax, under, over) logger.info("Compute time-frequency decomposition using the" " %s method" % method) if method == 'fourier': freqs, time, tf = spectrogram(data, sf, nperseg=nperseg, noverlap=noverlap, window=window) if method == 'wavelet': n_pts = len(data) freqs = np.arange(f_min, f_max, f_step) time = np.arange(n_pts) / sf tf = np.zeros((len(freqs), n_pts), dtype=data.dtype) # Compute TF and inplace normalization : logger.info("Compute the time-frequency map (" "normalization=%r)" % norm) for i, k in enumerate(freqs): tf[i, :] = np.square(np.abs(morlet(data, sf, k))) normalization(tf, norm=norm, baseline=baseline, axis=1) # Averaging : if isinstance(n_window, int): logger.info("Averaging time-frequency map using windows of " "size %i with a %f overlap" % (n_window, overlap)) kw = dict(overlap=overlap, window=window) tf = averaging(tf, n_window, axis=1, **kw) time = averaging(time, n_window, **kw) elif method == 'multitaper': is_lspopt_installed(raise_error=True) from lspopt import spectrogram_lspopt freqs, time, tf = spectrogram_lspopt(data, sf, nperseg=nperseg, noverlap=noverlap, c_parameter=c_parameter) # Set data to the image object : ImageObj.set_data(self, tf, xaxis=time, yaxis=freqs, **self.to_kwargs())
def test_spectrogram_method(): """Test the spectrogram method's functionality.""" fs = 10e3 N = 1e5 amp = 2 * np.sqrt(2) noise_power = 0.001 * fs / 2 time = np.arange(N) / fs freq = np.linspace(1e3, 2e3, N) x = amp * chirp(time, 1e3, 2.0, 6e3, method='quadratic') + \ np.random.normal(scale=np.sqrt(noise_power), size=time.shape) f, t, Sxx = spectrogram_lspopt(x, fs, c_parameter=20.0) f_sp, t_sp, Sxx_sp = spectrogram(x, fs) assert True
def transform(self, x): psg = x[:, 0, :] #psg = x padding = self.window // 2 - self.stride // 2 psg = np.pad(psg, pad_width=((0, 0), (padding, padding)), mode='edge') from lspopt import spectrogram_lspopt f, t, sxx = spectrogram_lspopt( psg, self.sampling_rate, c_parameter=5.0, nperseg=self.window, noverlap=self.window - self.stride, ) # [num_epochs, 1, nfreqbins, time_domain] sxx = sxx[:, np.newaxis, :, :] return sxx
def plot_spectrogram(data, sf, hypno=None, win_sec=30, fmin=0.5, fmax=25, trimperc=2.5, cmap='RdBu_r'): """ Plot a full-night multi-taper spectrogram, optionally with the hypnogram on top. For more details, please refer to the `Jupyter notebook <https://github.com/raphaelvallat/yasa/blob/master/notebooks/10_spectrogram.ipynb>`_ .. versionadded:: 0.1.8 Parameters ---------- data : :py:class:`numpy.ndarray` Single-channel EEG data. Must be a 1D NumPy array. sf : float The sampling frequency of data AND the hypnogram. hypno : array_like Sleep stage (hypnogram), optional. The hypnogram must have the exact same number of samples as ``data``. To upsample your hypnogram, please refer to :py:func:`yasa.hypno_upsample_to_data`. .. note:: The default hypnogram format in YASA is a 1D integer vector where: - -2 = Unscored - -1 = Artefact / Movement - 0 = Wake - 1 = N1 sleep - 2 = N2 sleep - 3 = N3 sleep - 4 = REM sleep win_sec : int or float The length of the sliding window, in seconds, used for multitaper PSD calculation. Default is 30 seconds. Note that ``data`` must be at least twice longer than ``win_sec`` (e.g. 60 seconds). fmin, fmax : int or float The lower and upper frequency of the spectrogram. Default 0.5 to 25 Hz. trimperc : int or float The amount of data to trim on both ends of the distribution when normalizing the colormap. This parameter directly impacts the contrast of the spectrogram plot (higher values = higher contrast). Default is 2.5, meaning that the min and max of the colormap are defined as the 2.5 and 97.5 percentiles of the spectrogram. cmap : str Colormap. Default to 'RdBu_r'. Returns ------- fig : :py:class:`matplotlib.figure.Figure` Matplotlib Figure Examples -------- 1. Full-night multitaper spectrogram on Cz, no hypnogram .. plot:: >>> import yasa >>> import numpy as np >>> # In the next 5 lines, we're loading the data from GitHub. >>> import requests >>> from io import BytesIO >>> r = requests.get('https://github.com/raphaelvallat/yasa/raw/master/notebooks/data_full_6hrs_100Hz_Cz%2BFz%2BPz.npz', stream=True) >>> npz = np.load(BytesIO(r.raw.read())) >>> data = npz.get('data')[0, :] >>> sf = 100 >>> fig = yasa.plot_spectrogram(data, sf) 2. Full-night multitaper spectrogram on Cz with the hypnogram on top .. plot:: >>> import yasa >>> import numpy as np >>> # In the next lines, we're loading the data from GitHub. >>> import requests >>> from io import BytesIO >>> r = requests.get('https://github.com/raphaelvallat/yasa/raw/master/notebooks/data_full_6hrs_100Hz_Cz%2BFz%2BPz.npz', stream=True) >>> npz = np.load(BytesIO(r.raw.read())) >>> data = npz.get('data')[0, :] >>> sf = 100 >>> # Load the 30-sec hypnogram and upsample to data >>> hypno = np.loadtxt('https://raw.githubusercontent.com/raphaelvallat/yasa/master/notebooks/data_full_6hrs_100Hz_hypno_30s.txt') >>> hypno = yasa.hypno_upsample_to_data(hypno, 1/30, data, sf) >>> fig = yasa.plot_spectrogram(data, sf, hypno, cmap='Spectral_r') """ # Increase font size while preserving original old_fontsize = plt.rcParams['font.size'] plt.rcParams.update({'font.size': 18}) # Safety checks assert isinstance(data, np.ndarray), 'Data must be a 1D NumPy array.' assert isinstance(sf, (int, float)), 'sf must be int or float.' assert data.ndim == 1, 'Data must be a 1D (single-channel) NumPy array.' assert isinstance(win_sec, (int, float)), 'win_sec must be int or float.' assert isinstance(fmin, (int, float)), 'fmin must be int or float.' assert isinstance(fmax, (int, float)), 'fmax must be int or float.' assert fmin < fmax, 'fmin must be strictly inferior to fmax.' assert fmax < sf / 2, 'fmax must be less than Nyquist (sf / 2).' # Calculate multi-taper spectrogram nperseg = int(win_sec * sf) assert data.size > 2 * nperseg, 'Data length must be at least 2 * win_sec.' f, t, Sxx = spectrogram_lspopt(data, sf, nperseg=nperseg, noverlap=0) Sxx = 10 * np.log10(Sxx) # Convert uV^2 / Hz --> dB / Hz # Select only relevant frequencies (up to 30 Hz) good_freqs = np.logical_and(f >= fmin, f <= fmax) Sxx = Sxx[good_freqs, :] f = f[good_freqs] t /= 3600 # Convert t to hours # Normalization vmin, vmax = np.percentile(Sxx, [0 + trimperc, 100 - trimperc]) norm = Normalize(vmin=vmin, vmax=vmax) if hypno is None: fig, ax = plt.subplots(nrows=1, figsize=(12, 4)) im = ax.pcolormesh(t, f, Sxx, norm=norm, cmap=cmap, antialiased=True, shading="auto") ax.set_xlim(0, t.max()) ax.set_ylabel('Frequency [Hz]') ax.set_xlabel('Time [hrs]') # Add colorbar cbar = fig.colorbar(im, ax=ax, shrink=0.95, fraction=0.1, aspect=25) cbar.ax.set_ylabel('Log Power (dB / Hz)', rotation=270, labelpad=20) return fig else: hypno = np.asarray(hypno).astype(int) assert hypno.ndim == 1, 'Hypno must be 1D.' assert hypno.size == data.size, 'Hypno must have the same sf as data.' t_hyp = np.arange(hypno.size) / (sf * 3600) # Make sure that REM is displayed after Wake hypno = pd.Series(hypno).map({-2: -2, -1: -1, 0: 0, 1: 2, 2: 3, 3: 4, 4: 1}).values hypno_rem = np.ma.masked_not_equal(hypno, 1) fig, (ax0, ax1) = plt.subplots(nrows=2, figsize=(12, 6), gridspec_kw={'height_ratios': [1, 2]}) plt.subplots_adjust(hspace=0.1) # Hypnogram (top axis) ax0.step(t_hyp, -1 * hypno, color='k') ax0.step(t_hyp, -1 * hypno_rem, color='r') if -2 in hypno and -1 in hypno: # Both Unscored and Artefacts are present ax0.set_yticks([2, 1, 0, -1, -2, -3, -4]) ax0.set_yticklabels(['Uns', 'Art', 'W', 'R', 'N1', 'N2', 'N3']) ax0.set_ylim(-4.5, 2.5) elif -2 in hypno and -1 not in hypno: # Only Unscored are present ax0.set_yticks([2, 0, -1, -2, -3, -4]) ax0.set_yticklabels(['Uns', 'W', 'R', 'N1', 'N2', 'N3']) ax0.set_ylim(-4.5, 2.5) elif -2 not in hypno and -1 in hypno: # Only Artefacts are present ax0.set_yticks([1, 0, -1, -2, -3, -4]) ax0.set_yticklabels(['Art', 'W', 'R', 'N1', 'N2', 'N3']) ax0.set_ylim(-4.5, 1.5) else: # No artefacts or Unscored ax0.set_yticks([0, -1, -2, -3, -4]) ax0.set_yticklabels(['W', 'R', 'N1', 'N2', 'N3']) ax0.set_ylim(-4.5, 0.5) ax0.set_xlim(0, t_hyp.max()) ax0.set_ylabel('Stage') ax0.xaxis.set_visible(False) ax0.spines['right'].set_visible(False) ax0.spines['top'].set_visible(False) # Spectrogram (bottom axis) im = ax1.pcolormesh(t, f, Sxx, norm=norm, cmap=cmap, antialiased=True, shading="auto") ax1.set_xlim(0, t.max()) ax1.set_ylabel('Frequency [Hz]') ax1.set_xlabel('Time [hrs]') # Revert font-size plt.rcParams.update({'font.size': old_fontsize}) return fig
def set_data(self, sf, data, time, method='Fourier transform', cmap='rainbow', nfft=30., overlap=0., fstart=.5, fend=20., contrast=.5, interp='nearest', norm=0): """Set data to the spectrogram. Use this method to change data, colormap, spectrogram settings, the starting and ending frequencies. Parameters ---------- sf: float The sampling frequency. data: array_like The data to use for the spectrogram. Must be a row vector. time: array_like The time vector. method: string | 'Fourier transform' Computation method. cmap : string | 'viridis' The matplotlib colormap to use. nfft : float | 30. Number of fft points for the spectrogram (in seconds). overlap : float | .5 Ovelap proprotion (0 <= overlap <1). fstart : float | .5 Frequency from which the spectrogram have to start. fend : float | 20. Frequency from which the spectrogram have to finish. contrast : float | .5 Contrast of the colormap. interp : string | 'nearest' Interpolation method. norm : int | 0 Normalization method for TF. """ # =================== PREPARE DATA =================== # Prepare data (only if needed) if self: data = self._prepare_data(sf, data.copy(), time) nperseg = int(round(nfft * sf)) # =================== TF // SPECTRO =================== if method == 'Wavelet': self.tf.set_data(data, sf, f_min=fstart, f_max=fend, cmap=cmap, contrast=contrast, n_window=nperseg, overlap=overlap, window='hamming', norm=norm) self.tf._image.interpolation = interp self.rect = self.tf.rect self.freq = self.tf.freqs else: # =================== CONVERSION =================== overlap = int(round(overlap * nperseg)) if method == 'Multitaper': from lspopt import spectrogram_lspopt freq, _, mesh = spectrogram_lspopt(data, fs=sf, nperseg=nperseg, c_parameter=20, noverlap=overlap) elif method == 'Fourier transform': freq, _, mesh = scpsig.spectrogram(data, fs=sf, nperseg=nperseg, noverlap=overlap, window='hamming') mesh = 20 * np.log10(mesh) # =================== FREQUENCY SELECTION =================== # Find where freq is [fstart, fend] : f = [0., 0.] f[0] = np.abs(freq - fstart).argmin() if fstart else 0 f[1] = np.abs(freq - fend).argmin() if fend else len(freq) # Build slicing and select frequency vector : sls = slice(f[0], f[1] + 1) freq = freq[sls] self._fstart, self._fend = freq[0], freq[-1] # =================== COLOR =================== # Get clim : _mesh = mesh[sls, :] contrast = 1. if contrast is None else contrast clim = (contrast * _mesh.min(), contrast * _mesh.max()) # Turn mesh into color array for selected frequencies: self.mesh.set_data(_mesh) _min, _max = _mesh.min(), _mesh.max() _cmap = cmap_to_glsl(limits=(_min, _max), clim=clim, cmap=cmap) self.mesh.cmap = _cmap self.mesh.clim = 'auto' self.mesh.interpolation = interp # =================== TRANSFORM =================== tm, th = time.min(), time.max() # Re-scale the mesh for fitting in time / frequency : fact = (freq.max() - freq.min()) / len(freq) sc = (th / mesh.shape[1], fact, 1) tr = [0., freq.min(), 0.] self.mesh.transform.translate = tr self.mesh.transform.scale = sc # Update object : self.mesh.update() # Get camera rectangle : self.rect = (tm, freq.min(), th - tm, freq.max() - freq.min()) self.freq = freq # Visibility : self.mesh.visible = 0 if method == 'Wavelet' else 1 self.tf.visible = 1 if method == 'Wavelet' else 0
def specgram_multitaper(data, sfreq, sperseg=30, perc_overlap=1 / 3, lfreq=0, ufreq=40, show_plot=True, title='', ax=None): """ Display EEG spectogram using a multitaper from 0-30 Hz :param data: the data to visualize, should be of rank 1 :param sfreq: the sampling frequency of the data :param sperseg: number of seconds to use per FFT :param noverlap: percentage of overlap between segments :param lfreq: Lower frequency to display :param ufreq: Upper frequency to display :param show_plot: If false, only the mesh is returned, but not Figure opened :param ax: An axis where to plot. Else will create a new Figure :returns: the resulting mesh as it would be plotted """ if ax is None: plt.figure() ax = plt.subplot(1, 1, 1) assert isinstance(show_plot, bool), 'show_plot must be boolean' nperseg = int(round(sperseg * sfreq)) overlap = int(round(perc_overlap * nperseg)) f_range = [lfreq, ufreq] freq, xy, mesh = spectrogram_lspopt(data, sfreq, nperseg=nperseg, noverlap=overlap, c_parameter=20.) if mesh.ndim == 3: mesh = mesh.squeeze().T mesh = 20 * np.log10(mesh + 0.0000001) idx_notfinite = np.isfinite(mesh) == False mesh[idx_notfinite] = np.min(mesh[~idx_notfinite]) f_range[1] = np.abs(freq - ufreq).argmin() sls = slice(f_range[0], f_range[1] + 1) freq = freq[sls] mesh = mesh[sls, :] mesh = mesh - mesh.min() mesh = mesh / mesh.max() if show_plot: ax.imshow(np.flipud(mesh), aspect='auto') formatter = matplotlib.ticker.FuncFormatter(lambda s, x: time.strftime( '%H:%M', time.gmtime(int(s * (sperseg - overlap / sfreq))))) ax.xaxis.set_major_formatter(formatter) if xy[-1] < 3600 * 7: # 7 hours is half hourly tick_distance = max(np.argmax(xy > sperseg * 60), 5) #plot per half hour else: # more than 7 hours hourly ticks tick_distance = np.argmax( xy > sperseg * 60) * 2 #plot per half hour two_hz_pos = np.argmax(freq > 1.99999999) ytick_pos = np.arange(0, len(freq), two_hz_pos) ax.set_xticks(np.arange(0, mesh.shape[1], tick_distance)) ax.set_yticks(ytick_pos) ax.set_yticklabels(np.arange(ufreq, lfreq - 1, -2)) ax.set_xlabel('Time after onset') ax.set_ylabel('Frequency') ax.set_title(title) warnings.filterwarnings( "ignore", message='This figure includes Axes that are not compatible') plt.tight_layout() return mesh
def set_data(self, sf, data, time, method='Fourier transform', cmap='rainbow', nfft=30., overlap=0., fstart=.5, fend=20., contrast=.5, interp='nearest', norm=0): """Set data to the spectrogram. Use this method to change data, colormap, spectrogram settings, the starting and ending frequencies. Parameters ---------- sf: float The sampling frequency. data: array_like The data to use for the spectrogram. Must be a row vector. time: array_like The time vector. method: string | 'Fourier transform' Computation method. cmap : string | 'viridis' The matplotlib colormap to use. nfft : float | 30. Number of fft points for the spectrogram (in seconds). overlap : float | .5 Ovelap proprotion (0 <= overlap <1). fstart : float | .5 Frequency from which the spectrogram have to start. fend : float | 20. Frequency from which the spectrogram have to finish. contrast : float | .5 Contrast of the colormap. interp : string | 'nearest' Interpolation method. norm : int | 0 Normalization method for TF. """ # =================== PREPARE DATA =================== # Prepare data (only if needed) if self: data = self._prepare_data(sf, data.copy(), time) nperseg = int(round(nfft * sf)) # =================== TF // SPECTRO =================== if method == 'Wavelet': self.tf.set_data(data, sf, f_min=fstart, f_max=fend, cmap=cmap, contrast=contrast, n_window=nperseg, overlap=overlap, window='hamming', norm=norm) self.tf._image.interpolation = interp self.rect = self.tf.rect self.freq = self.tf.freqs else: # =================== CONVERSION =================== overlap = int(round(overlap * nperseg)) if method == 'Multitaper': from lspopt import spectrogram_lspopt freq, _, mesh = spectrogram_lspopt(data, fs=sf, nperseg=nperseg, c_parameter=20, noverlap=overlap) elif method == 'Fourier transform': freq, _, mesh = scpsig.spectrogram(data, fs=sf, nperseg=nperseg, noverlap=overlap, window='hamming') mesh = 20 * np.log10(mesh) # =================== FREQUENCY SELECTION =================== # Find where freq is [fstart, fend] : f = [0., 0.] f[0] = np.abs(freq - fstart).argmin() if fstart else 0 f[1] = np.abs(freq - fend).argmin() if fend else len(freq) # Build slicing and select frequency vector : sls = slice(f[0], f[1] + 1) freq = freq[sls] self._fstart, self._fend = freq[0], freq[-1] # =================== COLOR =================== # Get clim : _mesh = mesh[sls, :] is_finite = np.isfinite(_mesh) _mesh[~is_finite] = np.percentile(_mesh[is_finite], 5) contrast = 1. if contrast is None else contrast clim = (contrast * _mesh.min(), contrast * _mesh.max()) # Turn mesh into color array for selected frequencies: self.mesh.set_data(_mesh) _min, _max = _mesh.min(), _mesh.max() _cmap = cmap_to_glsl(limits=(_min, _max), clim=clim, cmap=cmap) self.mesh.cmap = _cmap self.mesh.clim = 'auto' self.mesh.interpolation = interp # =================== TRANSFORM =================== tm, th = time.min(), time.max() # Re-scale the mesh for fitting in time / frequency : fact = (freq.max() - freq.min()) / len(freq) sc = (th / mesh.shape[1], fact, 1) tr = [0., freq.min(), 0.] self.mesh.transform.translate = tr self.mesh.transform.scale = sc # Update object : self.mesh.update() # Get camera rectangle : self.rect = (tm, freq.min(), th - tm, freq.max() - freq.min()) self.freq = freq # Visibility : self.mesh.visible = 0 if method == 'Wavelet' else 1 self.tf.visible = 1 if method == 'Wavelet' else 0
def plot_spectrogram(data, sf, hypno=None, win_sec=30, fmin=0.5, fmax=25, trimperc=2.5, cmap='Spectral_r'): __all__ = ['plot_spectrogram'] # Set default font size to 12 plt.rcParams.update({'font.size': 12}) # Calculate multi-taper spectrogram nperseg = int(win_sec * sf) assert data.size > 2 * nperseg, 'Data length must be at least 2 * win_sec.' f, t, Sxx = spectrogram_lspopt(data, sf, nperseg=nperseg, noverlap=0) Sxx = 10 * np.log10(Sxx) # Convert uV^2 / Hz --> dB / Hz # Select only relevant frequencies (up to 30 Hz) good_freqs = np.logical_and(f >= fmin, f <= fmax) Sxx = Sxx[good_freqs, :] f = f[good_freqs] t /= 3600 # Convert t to hours # Normalization vmin, vmax = np.percentile(Sxx, [0 + trimperc, 100 - trimperc]) norm = Normalize(vmin=vmin, vmax=vmax) if hypno is None: fig, ax = plt.subplots(nrows=1, figsize=(12, 4)) im = ax.pcolormesh(t, f, Sxx, norm=norm, cmap=cmap, antialiased=True) ax.set_xlim(0, t.max()) ax.set_ylabel('Frequency [Hz]') ax.set_xlabel('Time [sec]') # Add colorbar cbar = fig.colorbar(im, ax=ax, shrink=0.95, fraction=0.1, aspect=25) cbar.ax.set_ylabel('Log Power (dB / Hz)', rotation=270, labelpad=20) return fig else: hypno = np.asarray(hypno).astype(int) assert hypno.ndim == 1, 'Hypno must be 1D.' assert hypno.size == data.size, 'Hypno must have the same sf as data.' t_hyp = np.arange(hypno.size) / (sf * 3600) # Make sure that REM is displayed after Wake hypno = pd.Series(hypno).map({-2: -2, -1: -1, 0: 0, 1: 2, 2: 3, 3: 4, 4: 1}).values hypno_rem = np.ma.masked_not_equal(hypno, 1) fig = plt.figure(constrained_layout = True, figsize=(22, 14)) grid_spec = fig.add_gridspec(3, 2, height_ratios=[1, 1, 1.6]) ax0 = fig.add_subplot(grid_spec[0, :]) ax1 = fig.add_subplot(grid_spec[1, :]) # plt.subplots_adjust(hspace=0.1) fig.tight_layout(pad=1) # Hypnogram (top axis) ax0.step(t_hyp, -1 * hypno, color='k', linewidth=4) ax0.step(t_hyp, -1 * hypno_rem, color='r') if -2 in hypno and -1 in hypno: # Both Unscored and Artefacts are present ax0.set_yticks([2, 1, 0, -1, -2, -3, -4]) ax0.set_yticklabels(['Uns', 'Art', 'W', 'R', 'N1', 'N2', 'N3']) ax0.set_ylim(-4.5, 2.5) elif -2 in hypno and -1 not in hypno: # Only Unscored are present ax0.set_yticks([2, 0, -1, -2, -3, -4]) ax0.set_yticklabels(['Uns', 'W', 'R', 'N1', 'N2', 'N3']) ax0.set_ylim(-4.5, 2.5) elif -2 not in hypno and -1 in hypno: # Only Artefacts are present ax0.set_yticks([1, 0, -1, -2, -3, -4]) ax0.set_yticklabels(['Art', 'W', 'R', 'N1', 'N2', 'N3']) ax0.set_ylim(-4.5, 1.5) else: # No artefacts or Unscored ax0.set_yticks([0, -1, -2, -3, -4]) ax0.set_yticklabels(['W', 'R', 'N1', 'N2', 'N3']) ax0.set_ylim(-4.5, 0.5) ax0.set_xlim(0, t_hyp.max()) ax0.set_ylabel('Stage') ax0.xaxis.set_visible(False) ax0.spines['right'].set_visible(False) ax0.spines['top'].set_visible(False) # Spectrogram (bottom axis) im = ax1.pcolormesh(t, f, Sxx, norm=norm, cmap=cmap, antialiased=True) ax1.set_xlim(0, t.max()) ax1.set_ylabel('Frequency [Hz]') ax1.set_xlabel('Time [hrs]') return fig, grid_spec, ax0, ax1
from SleepData import SleepRecord import ospath from tqdm import tqdm from lspopt import spectrogram_lspopt import numpy as np files = ospath.list_files('C:/Users/SimonKern/Desktop/nt1-hrv/', subfolders=True, exts='edf') mins = [] maxs = [] for file in tqdm(files): sr = SleepRecord(file) data = sr.raw sfreq = sr.sfreq perc_overlap = 1 / 3 sperseg = 30 nperseg = int(round(sperseg * sfreq)) overlap = int(round(perc_overlap * nperseg)) freq, xy, mesh = spectrogram_lspopt(data, sfreq, nperseg=nperseg, noverlap=overlap, c_parameter=20.) mesh = 20 * np.log10(mesh) idx_notfinite = np.isfinite(mesh) == False mesh[idx_notfinite] = np.min(mesh[~idx_notfinite]) mins.append(mesh.min()) maxs.append(mesh.max())
for i in range(len(list_data)): s = str(i) curr_data = list_data[s] # Create the subplots if i ==0 : fig, axs = plt.subplots(len_data,1, figsize=(26, 14)) # Choose current axes plt.axes(axs[i]) # Make the spectrograms try: f, t, Sxx = spectrogram_lspopt(x=curr_data, fs=fs, c_parameter=20.0, nperseg=int(30*fs), \ scaling='density') except ValueError: f, t, Sxx = spectrogram_lspopt(x=curr_data, fs=fs, c_parameter=20.0, nperseg=int(30*fs), \ scaling='density') Sxx = 10 * np.log10(Sxx) #power to db # Limit Sxx to the largest freq of interest: f_tmp = f[0:900] Sxx_tmp = Sxx[0][0:900, :] plt.pcolormesh(t, f_tmp, Sxx_tmp, cmap = 'jet') plt.ylim([0, 30]) # Assign the number of night plt.ylabel(f'Night {i+1}')