Inverse transformation of time series into images using Gram’s Angle Field (GAF)

In the process of studying “Imaging Time-Series to Improve Classification and Imputation”, I had a question, that is, how to reconstruct the original time series from the obtained Gram sum/difference field (GASF/GADF)? That is, the inverse transformation of time series to image transformation.

For the process of converting time series to GASF/GADF, refer to this article and use the pyts library to implement:

Several methods to use python to convert time series signals or one-dimensional arrays into images_How to convert time series data whose features do not correspond to images-CSDN Blog

However, difficulties were encountered in the process of reconstructing time series from GASF/GADF. The original article states:

From the main diagonal, the time series can be reconstructed.

So I referred to the method in this article:

Gram Matrix Inverse Transform_Elk Not Lost 11’s Blog-CSDN Blog

Thanks to the author for inspiring me. After calculation, in GASF:

G_{i,i}=2\tilde{x}_i^2-1, where G represents GASF, \tilde{x} represents the original time series normalized by the maximum and minimum values.

In GADF, the result of the above article is wrong and should be:

G_{i,i}=0

Therefore, you can pass \tilde{x}_i=\sqrt{\frac{G_{i,i}-1}{2}} Calculate the values of a time series in reverse. However, the time series values obtained in this way cannot distinguish between positive and negative signs. In order to refactor correctly, you can use the parameter sample_range=(0, 1) when building GASF. The default sample_range is (-1,1), so GramianAngularField will internally use MinMaxScaler(sample_range=self.sample_range) to standardize the original time series to between [0,1], which is a non-negative number, so it can be re-produced without ambiguity. structure. The code is:

gasf = GramianAngularField(image_size=image_size, method='summation', sample_range=(0, 1))
X_gasf = gasf.fit_transform(X)

Supplement the complete code and reconstructed results, generate GASF/GADF and use GASF to reconstruct the original data:

# In[] Data
X = [10,8,6,4,2,0.5,2,0.5,2,4,6,8,7,8,6,4,2,2.5,0,1,1.5,3.5,5.5,7.5 ,9.5]
X = np.array(X)
X = X.reshape(1, -1)
# In[] Transform the time series into Gramian Angular Fields
import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.axes_grid1 import ImageGrid, make_axes_locatable
from pyts.image import GramianAngularField

image_size = 10
gasf = GramianAngularField(image_size=image_size, method='summation', sample_range=(0, 1))
X_gasf = gasf.fit_transform(X)
gadf = GramianAngularField(image_size=image_size, method='difference', sample_range=(0, 1))
X_gadf = gadf.fit_transform(X)
 
# Show the results for the first time series
plt.figure(figsize=(5, 10))
axs = plt.subplots()
plt.subplot(211)
plt.imshow(X_gasf[0], cmap='rainbow', origin='lower')
plt.title("GASF", fontsize=16)
plt.subplot(212)
plt.imshow(X_gadf[0], cmap='rainbow', origin='lower')
plt.title("GADF", fontsize=16)
 
cax = plt.axes([0.7, 0.1, 0.02, 0.8])
plt.colorbar(cax = cax)
plt.suptitle('Gramian Angular Fields', y=0.98, fontsize=16)
plt.tight_layout()
plt.show()
# In[] Inverse transformation
x = X.reshape(-1)
y = np.array([X_gasf[0, i, i] for i in range(image_size)]) # main diagonal
x_rec = np.sqrt( (y + 1) /2)

plt.suptitle('GASF Inverse')
plt.subplot(211)
plt.plot(x, label='x')
plt.legend()
plt.subplot(212)
plt.plot(x_rec, label='x_rec')
plt.legend()
plt.show()

Result of reconstruction:

It can be seen that the reconstructed results have been normalized to be between [0,1]. The image_size=25 here is equal to the original data length. If you use a smaller image_size, the original data cannot be reconstructed accurately. Here is the reconstruction result for image_size=10:

Regarding how to reconstruct the original data while maintaining the [-1,1] value range, I only have some personal immature ideas, just for reference~

Refer to the formula in the original article:

In other words, the value of GASF can be easily understood as the cosine of the sum of two angles, and GADF is the sine of the difference between the two angles. At this time, if the user already knows a \tilde{x}_j angle (\phi_{j}) is 0, then you can eliminate a variable and convert the above two formulas into the sine and cosine values of the same angle, and you can use GASF/ The original value is calculated from the value in the i-th row (column) of GADF. But here\tilde{x}_j also refers to the value after MinMax, so the user may not necessarily be sure which value is exactly 0, because the MinMax step is included inside GramianAngularField:

 def transform(self, X):
        """Transform each time series into a GAF image.

        Parameters
        ----------
        X : array-like, shape = (n_samples, n_timestamps)

        Returns
        -------
        X_new : array-like, shape = (n_samples, image_size, image_size)
            Transformed data. If ``flatten=True``, the shape is
            `(n_samples, image_size * image_size)`.

        """
        X = check_array(X)
        n_samples, n_timestamps = X.shape
        image_size = self._check_params(n_timestamps)

        paa = PiecewiseAggregateApproximation(
            window_size=None, output_size=image_size,
            overlapping=self.overlapping
        )
        X_paa = paa.fit_transform(X)
        if self.sample_range is None:
            X_min, X_max = np.min(X_paa), np.max(X_paa)
            if (X_min < -1) or (X_max > 1):
                raise ValueError("If 'sample_range' is None, all the values "
                                 "of X must be between -1 and 1.")
            X_cos = X_paa
        else:
            scaler = MinMaxScaler(sample_range=self.sample_range)
            X_cos = scaler.fit_transform(X_paa)
        X_sin = np.sqrt(np.clip(1 - X_cos ** 2, 0, 1))
        if self.method in ['s', 'summation']:
            X_new = _gasf(X_cos, X_sin, n_samples, image_size)
        else:
            X_new = _gadf(X_cos, X_sin, n_samples, image_size)

        if self.flatten:
            return X_new.reshape(n_samples, -1)
        return X_new

With sample_range=None, this step can be skipped and MinMax can be done externally in advance. At the same time, you also need to pay attention to the execution of the PiecewiseAggregateApproximation function, which will perform some downsampling on the original data, and may also cause the user’s preset 0 value to be changed. Therefore, it may be more appropriate to set (add) the first or last value of the time series to 0. This manually obtains an angle of 0.

However, this does not guarantee lossless recovery. The existence of the float type and the arg trigonometric function operation may cause small errors. Welcome everyone to correct me. If you have a better solution, please feel free to post it~

I’m new to GAF and I’m not very good at math. Thanks for the reference~

The knowledge points of the article match the official knowledge files, and you can further learn related knowledge. OpenCV skill tree Home page Overview 24004 people are learning the system