3

Using scipy, is there an easy way to emulate the behaviour of MATLAB's dctmtx function which returns a NxN DCT matrix for some given N? There's scipy.fftpack.dctn but that only applies the DCT. Do I have to implement this from scratch if I don't want use another dependency besides scipy?

feedMe
  • 3,431
  • 2
  • 36
  • 61
Peter
  • 2,919
  • 1
  • 16
  • 35

1 Answers1

7

The DCT is a linear transformation, so one way to get the matrix for the transformation is to apply it to the identity matrix. Here's an example where I find the matrix for sequences of length 8 (change 8 to N for the general case):

In [124]: import numpy as np

In [125]: from scipy.fft import dct

In [126]: D = dct(np.eye(8), axis=0)

D is the matrix:

In [127]: D
Out[127]: 
array([[ 2.        ,  2.        ,  2.        ,  2.        ,  2.        ,  2.        ,  2.        ,  2.        ],
       [ 1.96157056,  1.66293922,  1.11114047,  0.39018064, -0.39018064, -1.11114047, -1.66293922, -1.96157056],
       [ 1.84775907,  0.76536686, -0.76536686, -1.84775907, -1.84775907, -0.76536686,  0.76536686,  1.84775907],
       [ 1.66293922, -0.39018064, -1.96157056, -1.11114047,  1.11114047,  1.96157056,  0.39018064, -1.66293922],
       [ 1.41421356, -1.41421356, -1.41421356,  1.41421356,  1.41421356, -1.41421356, -1.41421356,  1.41421356],
       [ 1.11114047, -1.96157056,  0.39018064,  1.66293922, -1.66293922, -0.39018064,  1.96157056, -1.11114047],
       [ 0.76536686, -1.84775907,  1.84775907, -0.76536686, -0.76536686,  1.84775907, -1.84775907,  0.76536686],
       [ 0.39018064, -1.11114047,  1.66293922, -1.96157056,  1.96157056, -1.66293922,  1.11114047, -0.39018064]])

Verify that D @ x is equivalent to dct(x):

In [128]: x = np.array([1, 2, 0, -1, 3, 0, 1, -1])

In [129]: dct(x)
Out[129]: array([10.        ,  4.02535777, -1.39941754,  7.38025967, -1.41421356, -6.39104653, -7.07401092,  7.51550307])

In [130]: D @ x
Out[130]: array([10.        ,  4.02535777, -1.39941754,  7.38025967, -1.41421356, -6.39104653, -7.07401092,  7.51550307])

Note that D @ x will generally be much slower than dct(x).


To get exact agreement with Matlab's dctmtx, add the argument norm='ortho'. For example, here's dctmtx in Octave (which returns the same array as in Matlab):

octave:1> pkg load image
octave:2> dctmtx(4)
ans =

   0.50000   0.50000   0.50000   0.50000
   0.65328   0.27060  -0.27060  -0.65328
   0.50000  -0.50000  -0.50000   0.50000
   0.27060  -0.65328   0.65328  -0.27060

Here's the calculation using scipy.fft.dct:

In [56]: from scipy.fft import dct

In [57]: dct(np.eye(4), axis=0, norm='ortho')
Out[57]: 
array([[ 0.5       ,  0.5       ,  0.5       ,  0.5       ],
       [ 0.65328148,  0.27059805, -0.27059805, -0.65328148],
       [ 0.5       , -0.5       , -0.5       ,  0.5       ],
       [ 0.27059805, -0.65328148,  0.65328148, -0.27059805]])
Warren Weckesser
  • 110,654
  • 19
  • 194
  • 214
  • Cool, that's simpler than I thought it would be and definitely more readable than explicitly filling the matrix. – Peter Dec 20 '18 at 21:28
  • Wouldn't you do it as a Kronecker product, instead of the identity matrix, as in: `np.kron( dct(np.eye(8), norm='ortho', axis=0), dct(np.eye(8), norm='ortho', axis=0) )`? The output is different. I'm asking because I've been told that `dct()` only does 1D DCT's. – Antoni Parellada May 05 '21 at 23:01
  • @Mathtourist, I updated my answer to show how to get exact agreement with Matlab's `dctmtx`. That is what the question was asking for. In the Matlab documentation linked to above, it is shown how this matrix can be used to do a 2D DCT. – Warren Weckesser May 06 '21 at 14:51