@@ -406,7 +406,8 @@ def upcoef(part, coeffs, wavelet, level=1, take=0):
406406def pad (x , pad_widths , mode ):
407407 """Extend a 1D signal using a given boundary mode.
408408
409- This is like `numpy.pad` but supports all PyWavelets boundary modes.
409+ This function operates like `numpy.pad` but supports all signal extension
410+ modes that can be used by PyWavelets discrete wavelet transforms.
410411
411412 Parameters
412413 ----------
@@ -427,54 +428,86 @@ def pad(x, pad_widths, mode):
427428 Padded array of rank equal to array with shape increased according to
428429 `pad_width`.
429430
431+ Notes
432+ -----
433+ The performance of padding in dimensions > 1 will be substantially slower
434+ for modes `smooth` and `antisymmetric` as these modes are not supported in
435+ an efficient manner by the underlying `numpy.pad` function.
430436 """
431437 if np .isscalar (pad_widths ):
432- pad_widths = (pad_widths , pad_widths )
433-
434- if x .ndim > 1 :
435- raise ValueError ( "This padding function is only for 1D signals." )
438+ pad_widths = (pad_widths , )
439+ if len ( pad_widths ) == 1 :
440+ pad_widths = ( pad_widths [ 0 ], ) * x .ndim
441+ pad_widths = [( p , p ) if np . isscalar ( p ) else p for p in pad_widths ]
436442
437443 if mode in ['symmetric' , 'reflect' ]:
438444 xp = np .pad (x , pad_widths , mode = mode )
439445 elif mode in ['periodic' , 'periodization' ]:
440- if mode == 'periodization' and x .size % 2 == 1 :
441- raise ValueError ("periodization expects an even length signal." )
446+ if mode == 'periodization' :
447+ # Promote odd-sized dimensions to even length by duplicating the
448+ # last value.
449+ edge_pad_widths = [(0 , x .shape [ax ] % 2 )
450+ for ax in range (x .ndim )]
451+ x = np .pad (x , edge_pad_widths , mode = 'edge' )
442452 xp = np .pad (x , pad_widths , mode = 'wrap' )
443- elif mode == 'zeros ' :
453+ elif mode == 'zero ' :
444454 xp = np .pad (x , pad_widths , mode = 'constant' , constant_values = 0 )
445455 elif mode == 'constant' :
446456 xp = np .pad (x , pad_widths , mode = 'edge' )
447457 elif mode == 'smooth' :
448- xp = np .pad (x , pad_widths , mode = 'linear_ramp' ,
449- end_values = (x [0 ] + pad_widths [0 ] * (x [0 ] - x [1 ]),
450- x [- 1 ] + pad_widths [1 ] * (x [- 1 ] - x [- 2 ])))
458+ def pad_smooth (vector , pad_width , iaxis , kwargs ):
459+ # smooth extension to left
460+ left = vector [pad_width [0 ]]
461+ slope_left = (left - vector [pad_width [0 ] + 1 ])
462+ vector [:pad_width [0 ]] = \
463+ left + np .arange (pad_width [0 ], 0 , - 1 ) * slope_left
464+
465+ # smooth extension to right
466+ right = vector [- pad_width [1 ] - 1 ]
467+ slope_right = (right - vector [- pad_width [1 ] - 2 ])
468+ vector [- pad_width [1 ]:] = \
469+ right + np .arange (1 , pad_width [1 ] + 1 ) * slope_right
470+ return vector
471+ xp = np .pad (x , pad_widths , pad_smooth )
451472 elif mode == 'antisymmetric' :
452- # implement by flipping portions symmetric padding
453- npad_l , npad_r = pad_widths
454- xp = np .pad (x , pad_widths , mode = 'symmetric' )
455- r_edge = npad_l + x .size - 1
456- l_edge = npad_l
457- # width of each reflected segment
458- seg_width = x .size
459- # flip reflected segments on the right of the original signal
460- n = 1
461- while r_edge <= xp .size :
462- segment_slice = slice (r_edge + 1 ,
463- min (r_edge + 1 + seg_width , xp .size ))
464- if n % 2 :
465- xp [segment_slice ] *= - 1
466- r_edge += seg_width
467- n += 1
468-
469- # flip reflected segments on the left of the original signal
470- n = 1
471- while l_edge >= 0 :
472- segment_slice = slice (max (0 , l_edge - seg_width ), l_edge )
473- if n % 2 :
474- xp [segment_slice ] *= - 1
475- l_edge -= seg_width
476- n += 1
473+ def pad_antisymmetric (vector , pad_width , iaxis , kwargs ):
474+ # smooth extension to left
475+ # implement by flipping portions symmetric padding
476+ npad_l , npad_r = pad_width
477+ vsize_nonpad = vector .size - npad_l - npad_r
478+ # Note: must modify vector in-place
479+ vector [:] = np .pad (vector [pad_width [0 ]:- pad_width [- 1 ]],
480+ pad_width , mode = 'symmetric' )
481+ vp = vector
482+ r_edge = npad_l + vsize_nonpad - 1
483+ l_edge = npad_l
484+ # width of each reflected segment
485+ seg_width = vsize_nonpad
486+ # flip reflected segments on the right of the original signal
487+ n = 1
488+ while r_edge <= vp .size :
489+ segment_slice = slice (r_edge + 1 ,
490+ min (r_edge + 1 + seg_width , vp .size ))
491+ if n % 2 :
492+ vp [segment_slice ] *= - 1
493+ r_edge += seg_width
494+ n += 1
495+
496+ # flip reflected segments on the left of the original signal
497+ n = 1
498+ while l_edge >= 0 :
499+ segment_slice = slice (max (0 , l_edge - seg_width ), l_edge )
500+ if n % 2 :
501+ vp [segment_slice ] *= - 1
502+ l_edge -= seg_width
503+ n += 1
504+ return vector
505+ xp = np .pad (x , pad_widths , pad_antisymmetric )
477506 elif mode == 'antireflect' :
478507 npad_l , npad_r = pad_widths
479508 xp = np .pad (x , pad_widths , mode = 'reflect' , reflect_type = 'odd' )
509+ else :
510+ raise ValueError (
511+ ("unsupported mode: {}. The supported modes are {}" ).format (
512+ mode , Modes .modes ))
480513 return xp
0 commit comments