DASPy-toolbox 1.1.6__tar.gz → 1.2.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (35) hide show
  1. {daspy_toolbox-1.1.6 → daspy_toolbox-1.2.1}/DASPy_toolbox.egg-info/PKG-INFO +1 -1
  2. {daspy_toolbox-1.1.6 → daspy_toolbox-1.2.1}/DASPy_toolbox.egg-info/SOURCES.txt +2 -0
  3. {daspy_toolbox-1.1.6 → daspy_toolbox-1.2.1}/PKG-INFO +1 -1
  4. {daspy_toolbox-1.1.6 → daspy_toolbox-1.2.1}/daspy/advanced_tools/denoising.py +2 -2
  5. {daspy_toolbox-1.1.6 → daspy_toolbox-1.2.1}/daspy/basic_tools/freqattributes.py +34 -2
  6. {daspy_toolbox-1.1.6 → daspy_toolbox-1.2.1}/daspy/basic_tools/preprocessing.py +166 -36
  7. {daspy_toolbox-1.1.6 → daspy_toolbox-1.2.1}/daspy/basic_tools/visualization.py +10 -7
  8. daspy_toolbox-1.2.1/daspy/core/collection.py +480 -0
  9. {daspy_toolbox-1.1.6 → daspy_toolbox-1.2.1}/daspy/core/dasdatetime.py +11 -5
  10. daspy_toolbox-1.2.1/daspy/core/make_example.py +32 -0
  11. daspy_toolbox-1.2.1/daspy/core/read.py +654 -0
  12. {daspy_toolbox-1.1.6 → daspy_toolbox-1.2.1}/daspy/core/section.py +186 -133
  13. daspy_toolbox-1.2.1/daspy/core/util.py +140 -0
  14. daspy_toolbox-1.2.1/daspy/core/write.py +675 -0
  15. {daspy_toolbox-1.1.6 → daspy_toolbox-1.2.1}/setup.py +3 -3
  16. daspy_toolbox-1.1.6/daspy/core/collection.py +0 -362
  17. daspy_toolbox-1.1.6/daspy/core/read.py +0 -590
  18. daspy_toolbox-1.1.6/daspy/core/write.py +0 -304
  19. {daspy_toolbox-1.1.6 → daspy_toolbox-1.2.1}/DASPy_toolbox.egg-info/dependency_links.txt +0 -0
  20. {daspy_toolbox-1.1.6 → daspy_toolbox-1.2.1}/DASPy_toolbox.egg-info/entry_points.txt +0 -0
  21. {daspy_toolbox-1.1.6 → daspy_toolbox-1.2.1}/DASPy_toolbox.egg-info/requires.txt +0 -0
  22. {daspy_toolbox-1.1.6 → daspy_toolbox-1.2.1}/DASPy_toolbox.egg-info/top_level.txt +0 -0
  23. {daspy_toolbox-1.1.6 → daspy_toolbox-1.2.1}/LICENSE +0 -0
  24. {daspy_toolbox-1.1.6 → daspy_toolbox-1.2.1}/README.md +0 -0
  25. {daspy_toolbox-1.1.6 → daspy_toolbox-1.2.1}/daspy/__init__.py +0 -0
  26. {daspy_toolbox-1.1.6 → daspy_toolbox-1.2.1}/daspy/advanced_tools/__init__.py +0 -0
  27. {daspy_toolbox-1.1.6 → daspy_toolbox-1.2.1}/daspy/advanced_tools/channel.py +0 -0
  28. {daspy_toolbox-1.1.6 → daspy_toolbox-1.2.1}/daspy/advanced_tools/decomposition.py +0 -0
  29. {daspy_toolbox-1.1.6 → daspy_toolbox-1.2.1}/daspy/advanced_tools/fdct.py +0 -0
  30. {daspy_toolbox-1.1.6 → daspy_toolbox-1.2.1}/daspy/advanced_tools/strain2vel.py +0 -0
  31. {daspy_toolbox-1.1.6 → daspy_toolbox-1.2.1}/daspy/basic_tools/__init__.py +0 -0
  32. {daspy_toolbox-1.1.6 → daspy_toolbox-1.2.1}/daspy/basic_tools/filter.py +0 -0
  33. {daspy_toolbox-1.1.6 → daspy_toolbox-1.2.1}/daspy/core/__init__.py +0 -0
  34. {daspy_toolbox-1.1.6 → daspy_toolbox-1.2.1}/daspy/core/example.pkl +0 -0
  35. {daspy_toolbox-1.1.6 → daspy_toolbox-1.2.1}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: DASPy-toolbox
3
- Version: 1.1.6
3
+ Version: 1.2.1
4
4
  Summary: DASPy is an open-source project dedicated to provide a python package for DAS (Distributed Acoustic Sensing) data processing, which comprises classic seismic data processing techniques and Specialized algorithms for DAS applications.
5
5
  Home-page: https://github.com/HMZ-03/DASPy
6
6
  Author: Minzhe Hu, Zefeng Li
@@ -23,6 +23,8 @@ daspy/core/__init__.py
23
23
  daspy/core/collection.py
24
24
  daspy/core/dasdatetime.py
25
25
  daspy/core/example.pkl
26
+ daspy/core/make_example.py
26
27
  daspy/core/read.py
27
28
  daspy/core/section.py
29
+ daspy/core/util.py
28
30
  daspy/core/write.py
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: DASPy-toolbox
3
- Version: 1.1.6
3
+ Version: 1.2.1
4
4
  Summary: DASPy is an open-source project dedicated to provide a python package for DAS (Distributed Acoustic Sensing) data processing, which comprises classic seismic data processing techniques and Specialized algorithms for DAS applications.
5
5
  Home-page: https://github.com/HMZ-03/DASPy
6
6
  Author: Minzhe Hu, Zefeng Li
@@ -1,6 +1,6 @@
1
1
  # Purpose: Remove noise from data
2
2
  # Author: Minzhe Hu, Zefeng Li
3
- # Date: 2024.5.13
3
+ # Date: 2025.9.18
4
4
  # Email: hmz2018@mail.ustc.edu.cn
5
5
  import numpy as np
6
6
  from copy import deepcopy
@@ -57,7 +57,7 @@ def common_mode_noise_removal(data, method='median'):
57
57
  common = np.mean(data, 0)
58
58
 
59
59
  xx = np.sum(common ** 2)
60
- data_dn = np.zeros((nch, nt))
60
+ data_dn = np.zeros((nch, nt), dtype=data.dtype)
61
61
  for i in range(nch):
62
62
  xc = np.sum(common * data[i])
63
63
  data_dn[i] = data[i] - xc / xx * common
@@ -1,10 +1,10 @@
1
1
  # Purpose: Analyze frequency attribute and transform in frequency domain
2
2
  # Author: Minzhe Hu
3
- # Date: 2024.6.8
3
+ # Date: 2024.6.17
4
4
  # Email: hmz2018@mail.ustc.edu.cn
5
5
  import numpy as np
6
6
  from numpy.fft import rfft, rfft2, fftshift, fftfreq, rfftfreq
7
- from scipy.signal import stft
7
+ from scipy.signal import stft, welch
8
8
  from daspy.basic_tools.preprocessing import demeaning, detrending, cosine_taper
9
9
 
10
10
 
@@ -47,6 +47,38 @@ def spectrum(data, fs, taper=0.05, nfft='default'):
47
47
  return spec, f
48
48
 
49
49
 
50
+ def psd(data, fs, nperseg=256, noverlap=None, nfft=None, detrend=False,
51
+ average='mean'):
52
+ """
53
+ Computes the power spectral density of the given data.
54
+
55
+ :param data: numpy.ndarray. Data to make spectrum of.
56
+ :param fs: Sampling rate in Hz.
57
+ :param nperseg: int. Length of each segment. Defaults to None, but if window
58
+ is str or tuple, is set to 256, and if window is array_like, is set to
59
+ the length of the window.
60
+ :param noverlap: int. Number of points to overlap between segments. If None,
61
+ noverlap = nperseg // 2. Defaults to None.
62
+ :param nfft: int. Length of the FFT used, if a zero padded FFT is desired.
63
+ If None, the FFT length is nperseg. Defaults to None.
64
+ :param detrend: str or bool. Specifies whether and how to detrend each
65
+ segment. 'linear' or 'detrend' or True = detrend, 'constant' or
66
+ 'demean' = demean.
67
+ :param average: 'mean' or 'median. Method to use when averaging
68
+ periodograms. Defaults to 'mean'.
69
+ :return: Power spectral density or power spectrum and array of sample
70
+ frequencies.
71
+ """
72
+ if len(data.shape) == 1:
73
+ data = data.reshape(1, len(data))
74
+ elif len(data.shape) != 2:
75
+ raise ValueError("Data should be 1-D or 2-D array")
76
+
77
+ f, psd = welch(data, fs=fs, nperseg=nperseg, noverlap=noverlap,
78
+ nfft=nfft, detrend=detrend, axis=1, average=average)
79
+ return psd, f
80
+
81
+
50
82
  def spectrogram(data, fs, nperseg=256, noverlap=None, nfft=None, detrend=False,
51
83
  boundary='zeros'):
52
84
  """
@@ -1,8 +1,10 @@
1
1
  # Purpose: Some preprocess methods
2
2
  # Author: Minzhe Hu
3
- # Date: 2025.3.10
3
+ # Date: 2025.10.30
4
4
  # Email: hmz2018@mail.ustc.edu.cn
5
+ import warnings
5
6
  import numpy as np
7
+ from numpy.fft import rfft, irfft, rfftfreq
6
8
  from scipy.signal import detrend
7
9
  from scipy.signal.windows import tukey
8
10
  from daspy.basic_tools.filter import lowpass_cheby_2
@@ -89,11 +91,11 @@ def stacking(data: np.ndarray, N: int, step: int = None, average: bool = True):
89
91
  return data
90
92
  if step is None:
91
93
  step = N
92
- nch, nt = data.shape
94
+ nch, nsp = data.shape
93
95
  begin = np.arange(0, nch - N + 1, step)
94
96
  end = begin + N
95
97
  nx1 = len(begin)
96
- data_stacked = np.zeros((nx1, nt))
98
+ data_stacked = np.zeros((nx1, nsp))
97
99
  for i in range(nx1):
98
100
  data_stacked[i, :] = np.sum(data[begin[i]:end[i], :], axis=0)
99
101
  if average:
@@ -115,9 +117,9 @@ def cosine_taper(data, p=0.1, side='both'):
115
117
  """
116
118
  if data.ndim == 1:
117
119
  data = data.reshape(1, -1)
118
- nch, nt = data.shape
120
+ nch, nsp = data.shape
119
121
  if not isinstance(p, (tuple, list, np.ndarray)):
120
- win = tukey(nt, p)
122
+ win = tukey(nsp, p)
121
123
  if side == 'left':
122
124
  win[round(nch/2):] = 1
123
125
  elif side == 'right':
@@ -125,7 +127,7 @@ def cosine_taper(data, p=0.1, side='both'):
125
127
  return data * np.tile(win, (nch, 1))
126
128
  else:
127
129
  if p[0] > 0:
128
- data = data * np.tile(tukey(nch, p[0]), (nt, 1)).T
130
+ data = data * np.tile(tukey(nch, p[0]), (nsp, 1)).T
129
131
  return cosine_taper(data, p[1], side=side)
130
132
 
131
133
 
@@ -156,32 +158,144 @@ def downsampling(data, xint=None, tint=None, stack=True, lowpass_filter=True):
156
158
  return data_ds
157
159
 
158
160
 
159
- def trimming(data, dx=None, fs=None, xmin=0, xmax=None, tmin=0, tmax=None,
160
- mode=0):
161
+ def _trimming_index(nch, nsp, dx=None, fs=None, start_channel=0,
162
+ start_distance=0, start_time=0, xmin=None, xmax=None,
163
+ chmin=None, chmax=None, tmin=None, tmax=None, spmin=None,
164
+ spmax=None):
165
+ assert None in [tmin, spmin], \
166
+ "Please do not set tmin and spmin at the same time."
167
+ assert None in [tmax, spmax], \
168
+ "Please do not set tmax and spmax at the same time."
169
+ assert None in [xmin, chmin], \
170
+ "Please do not set xmin and chmin at the same time."
171
+ assert None in [xmax, chmax], \
172
+ "Please do not set xmax and chmax at the same time."
173
+ if dx is None:
174
+ assert xmin is None and xmax is None, "Please set dx"
175
+ if fs is None:
176
+ assert tmin is None and tmax is None, "Please set fs"
177
+
178
+ if xmin is None:
179
+ if chmin is None:
180
+ i0 = 0
181
+ else:
182
+ i0 = int(chmin - start_channel)
183
+ if i0 < 0:
184
+ warnings.warn('chmin < start_channel . Set chmin to '
185
+ 'start_channel.')
186
+ i0 = 0
187
+ elif i0 >= nch:
188
+ raise ValueError('chmin >= end_channel.')
189
+ else:
190
+ i0 = round((xmin - start_distance) / dx)
191
+ if i0 < 0:
192
+ warnings.warn('xmin is smaller than start_distance. Set xmin '
193
+ 'to 0.')
194
+ i0 = 0
195
+ elif i0 >= nch:
196
+ raise ValueError('xmin is later than end_distance.')
197
+
198
+ if xmax is None:
199
+ if chmax is None:
200
+ i1 = nch
201
+ else:
202
+ i1 = int(chmax - start_channel)
203
+ if i1 <= 0:
204
+ raise ValueError('chmax <= start_channel.')
205
+ elif i1 > nch:
206
+ warnings.warn('chmax > end_channel. Set chmax to '
207
+ 'end_channel.')
208
+ i1 = nch
209
+ else:
210
+ i1 = round((xmax - start_distance) / dx)
211
+ if i1 <= 0:
212
+ raise ValueError('xmax is smaller than start_distance.')
213
+ if i1 > nch:
214
+ warnings.warn('xmax is later than end_distance. Set xmax '
215
+ 'to the array length.')
216
+ i1 = nch
217
+
218
+ if tmin is None:
219
+ if spmin is None:
220
+ j0 = 0
221
+ else:
222
+ j0 = int(spmin)
223
+ if j0 < 0:
224
+ warnings.warn('spmin < 0. Set spmin to 0.')
225
+ j0 = 0
226
+ elif j0 >= nsp:
227
+ raise ValueError('spmin > nsp.')
228
+ else:
229
+ try:
230
+ j0 = round((tmin - start_time) * fs)
231
+ except TypeError:
232
+ j0 = round(tmin * fs)
233
+ if j0 < 0:
234
+ warnings.warn('tmin is earlier than start_time. Set tmin '
235
+ 'to start_time.')
236
+ j0 = 0
237
+ elif j0 >= nsp:
238
+ raise ValueError('tmin is later than end_time.')
239
+
240
+ if tmax is None:
241
+ if spmax is None:
242
+ j1 = nsp
243
+ else:
244
+ j1 = int(spmax)
245
+ if j1 <= 0:
246
+ raise ValueError('spmax < 0.')
247
+ elif j1 > nsp:
248
+ warnings.warn('spmax > nsp. Set spmax to nsp.')
249
+ j1 = nsp
250
+ else:
251
+ try:
252
+ j1 = round((tmax - start_time) * fs)
253
+ except TypeError:
254
+ j1 = round(tmax * fs)
255
+ if j1 <= 0:
256
+ raise ValueError('tmax is earlier than start_time.')
257
+ if j1 > nsp:
258
+ warnings.warn('tmax is later than end_time. Set tmax to the'
259
+ ' end_time.')
260
+ j1 = nsp
261
+ return i0, i1, j0, j1
262
+
263
+
264
+ def trimming(data, dx=None, fs=None, xmin=None, xmax=None, chmin=None,
265
+ chmax=None, tmin=None, tmax=None, spmin=None, spmax=None,
266
+ **kwargs):
161
267
  """
162
268
  Cut data to given start and end distance/channel or time/sampling points.
163
269
 
164
270
  :param data: numpy.ndarray. Data to trim can be 1-D or 2-D.
165
271
  :param dx: Channel interval in m.
166
272
  :param fs: Sampling rate in Hz.
167
- :param xmin, xmax, tmin, tmax: Boundary for trimming.
168
- :param mode: 0 means the unit of boundary is channel number and sampling
169
- points; 1 means the unit of boundary is meters and seconds.
273
+ :param xmin, xmax: float. Range of distance.
274
+ :param chmin, chmax: int. Channel number range.
275
+ :param tmin, tmax: float or DASDateTime. Range of time.
276
+ :param spmin, spmax: int. Sampling point range.
170
277
  :return: Trimmed data.
171
278
  """
172
- nch, nt = data.shape
173
- if mode == 0:
174
- if xmax is None:
175
- xmax = nch
176
- if tmax is None:
177
- tmax = nt
178
- elif mode == 1:
179
- xmin = round(xmin / dx)
180
- xmax = (round(xmax / dx), nch)[xmax is None]
181
- tmin = round(tmin * fs)
182
- tmax = (round(tmax * fs), nt)[tmax is None]
183
-
184
- return data[xmin:xmax, tmin:tmax]
279
+ # Compatible with old interfaces and remind users
280
+ if 'mode' in kwargs:
281
+ warnings.warn('In future versions, the mode parameter will be '
282
+ 'deprecated. xmin/xmax will only control the distance'
283
+ ' range, tmin/tmax will only control the time range; '
284
+ 'please use chmin/chmax to control the channel number'
285
+ ' range, and spmin/spmax to control the sampling '
286
+ 'point range', FutureWarning)
287
+ if kwargs['mode'] == 0:
288
+ chmin, chmax = xmin, xmax
289
+ xmin, xmax = None, None
290
+ spmin, spmax = tmin, tmax
291
+ tmin, tmax = None, None
292
+ nch, nsp = data.shape
293
+ i0, i1, j0, j1 = _trimming_index(nch, nsp, dx=dx, fs=fs, xmin=xmin,
294
+ xmax=xmax, chmin=chmin, chmax=chmax,
295
+ tmin=tmin, tmax=tmax, spmin=spmin,
296
+ spmax=spmax)
297
+
298
+ return data[i0:i1, j0:j1].copy()
185
299
 
186
300
 
187
301
  def padding(data, dn, reverse=False):
@@ -194,20 +308,20 @@ def padding(data, dn, reverse=False):
194
308
  :param reverse: bool. Set True to reverse the operation.
195
309
  :return: Padded data.
196
310
  """
197
- nch, nt = data.shape
311
+ nch, nsp = data.shape
198
312
  if isinstance(dn, int):
199
313
  dn = (dn, dn)
200
314
 
201
315
  pad = (dn[0] // 2, dn[0] - dn[0] // 2, dn[1] // 2, dn[1] - dn[1] // 2)
202
316
  if reverse:
203
- return data[pad[0]:nch - pad[1], pad[2]:nt - pad[3]]
317
+ return data[pad[0]:nch - pad[1], pad[2]:nsp - pad[3]]
204
318
  else:
205
- data_pd = np.zeros((nch + dn[0], nt + dn[1]))
206
- data_pd[pad[0]:nch + pad[0], pad[2]:nt + pad[2]] = data
319
+ data_pd = np.zeros((nch + dn[0], nsp + dn[1]))
320
+ data_pd[pad[0]:nch + pad[0], pad[2]:nsp + pad[2]] = data
207
321
  return data_pd
208
322
 
209
323
 
210
- def time_integration(data, fs, c=0):
324
+ def time_integration(data, fs, domain='time', c=0):
211
325
  """
212
326
  Integrate DAS data in time.
213
327
 
@@ -216,10 +330,19 @@ def time_integration(data, fs, c=0):
216
330
  :param c: float. A constant added to the result.
217
331
  :return: Integrated data.
218
332
  """
219
- return np.cumsum(data, axis=1) / fs + c
220
-
221
-
222
- def time_differential(data, fs, prepend=0):
333
+ if domain == 'time':
334
+ return np.cumsum(data, axis=1) / fs + c
335
+ elif domain in ['frequency', 'freq']:
336
+ nsp = data.shape[1]
337
+ freqs = rfftfreq(nsp, d=1/fs)
338
+ spectrum = rfft(data, axis=1)
339
+ H = np.zeros_like(freqs, dtype=complex)
340
+ nonzero = freqs != 0
341
+ H[nonzero] = 1 / (1j * 2 * np.pi * freqs[nonzero])
342
+ return np.real(irfft(spectrum * H))
343
+
344
+
345
+ def time_differential(data, fs, domain='time', prepend=0):
223
346
  """
224
347
  Differentiate DAS data in time.
225
348
 
@@ -229,9 +352,16 @@ def time_differential(data, fs, prepend=0):
229
352
  performing the difference.
230
353
  :return: Differentiated data.
231
354
  """
232
- if prepend == 'mean':
233
- prepend = np.mean(data, axis=1).reshape((-1, 1))
234
- return np.diff(data, axis=1, prepend=prepend) * fs
355
+ if domain == 'time':
356
+ if prepend == 'mean':
357
+ prepend = np.mean(data, axis=1).reshape((-1, 1))
358
+ return np.diff(data, axis=1, prepend=prepend) * fs
359
+ elif domain in ['frequency', 'freq']:
360
+ nsp = data.shape[1]
361
+ freqs = rfftfreq(nsp, d=1./fs)
362
+ spectrum = rfft(data, axis=1)
363
+ H = 1j * 2 * np.pi * freqs
364
+ return np.real(irfft(spectrum * H))
235
365
 
236
366
 
237
367
  def distance_integration(data, dx, c=0):
@@ -1,6 +1,6 @@
1
1
  # Purpose: Plot data
2
2
  # Author: Minzhe Hu
3
- # Date: 2025.5.20
3
+ # Date: 2025.6.17
4
4
  # Email: hmz2018@mail.ustc.edu.cn
5
5
  import numpy as np
6
6
  import matplotlib.pyplot as plt
@@ -24,7 +24,7 @@ def plot(data: np.ndarray, dx=None, fs=None, ax=None, obj='waveform', dpi=300,
24
24
  figsize. If not specified, the function will directly display the image
25
25
  using matplotlib.pyplot.show().
26
26
  :param obj: str. Type of data to plot. It should be one of 'waveform',
27
- 'phasepick', 'spectrum', 'spectrogram', 'fk', or 'dispersion'.
27
+ 'phasepick', 'spectrum', 'psd', 'spectrogram', 'fk', or 'dispersion'.
28
28
  :param dpi: int. The resolution of the figure in dots-per-inch.
29
29
  :param title: str. The title of this axes.
30
30
  :param transpose: bool. Transpose the figure or not.
@@ -33,7 +33,7 @@ def plot(data: np.ndarray, dx=None, fs=None, ax=None, obj='waveform', dpi=300,
33
33
  P phase, 'S' for S phase and 'N' for unknown phase type. Required if
34
34
  obj=='phasepick'.
35
35
  :param f: Sequence of frequency. Required if obj is one of 'spectrum',
36
- 'spectrogram', 'fk' or 'dispersion'.
36
+ 'psd', 'spectrogram', 'fk' or 'dispersion'.
37
37
  :param k: Wavenumber sequence. Required if obj=='fk'.
38
38
  :param t: Time sequence. Required if obj=='spectrogram'.
39
39
  :param c: Phase velocity sequence. Required if obj=='dispersion'.
@@ -99,14 +99,17 @@ def plot(data: np.ndarray, dx=None, fs=None, ax=None, obj='waveform', dpi=300,
99
99
  if tmode.lower() == 'sampling':
100
100
  pck[:, 1] = pck[:, 1] / fs
101
101
  ax.scatter(pck[:,0], t0 + pck[:,1], marker=',', s=0.1,
102
- c=pick_color[phase])
102
+ c=pick_color[phase])
103
103
 
104
- elif obj in ['spectrum', 'spectrogram', 'fk', 'dispersion']:
104
+ elif obj in ['spectrum', 'spectrogram', 'psd', 'fk', 'dispersion']:
105
105
  if np.iscomplex(data).any():
106
106
  data = abs(data)
107
107
  if dB:
108
108
  data = 20 * np.log10(data)
109
- cmap = 'jet' if cmap is None else cmap
109
+ if obj == 'psd':
110
+ cmap = 'viridis' if cmap is None else cmap
111
+ else:
112
+ cmap = 'jet' if cmap is None else cmap
110
113
 
111
114
  if vmax is None:
112
115
  vmax_per = 80 if vmax_per is None else vmax_per
@@ -115,7 +118,7 @@ def plot(data: np.ndarray, dx=None, fs=None, ax=None, obj='waveform', dpi=300,
115
118
  vmin_per = 20 if vmin_per is None else vmin_per
116
119
  vmin = np.percentile(data, vmin_per)
117
120
 
118
- if obj == 'spectrum':
121
+ if obj in ['spectrum', 'psd']:
119
122
  origin = 'lower'
120
123
  if dx is None or xmode.lower() == 'channel':
121
124
  xlabel_default = 'Channel'