johannfaouzi / pyts

A Python package for time series classification

Home Page:https://pyts.readthedocs.io

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Request for Code or Guidance on Reversing Gramian Angular Fields Transformation

ljpkok opened this issue · comments

commented

Dear johannfaouzi,

I'm reaching out regarding discussion #132 regarding transforming images into time series using Gramian Angular Fields. I am currently working on a project related to ECG generation, and I believe that leveraging this method could greatly benefit my research.

After reviewing our previous conversation, I understand that the inverse transformations for the imaging methods are not currently implemented. However, I kindly ask if you could provide me with any code or guidance on how I could potentially reverse the Gramian Angular Fields transformation to obtain the original time series data.

Thank you for your time and expertise.

commented

A sample image would be like this:

X = csv
X = X.reshape(1, -1)

gasf = GramianAngularField(method='summation')
X_gasf = gasf.fit_transform(X)

# Plot the time series and its recurrence plot
plt.figure(figsize=(16, 8))
plt.subplot(121)
plt.plot(X[0])
plt.title('Time series', fontsize=16)
plt.subplot(122)
plt.imshow(X_gasf[0], cmap='rainbow', origin='lower')
plt.title('Gramian Angular Field', fontsize=16)
plt.show()

output

Dear johannfaouzi,

I'm reaching out regarding discussion #132 regarding transforming images into time series using Gramian Angular Fields. I am currently working on a project related to ECG generation, and I believe that leveraging this method could greatly benefit my research.

After reviewing our previous conversation, I understand that the inverse transformations for the imaging methods are not currently implemented. However, I kindly ask if you could provide me with any code or guidance on how I could potentially reverse the Gramian Angular Fields transformation to obtain the original time series data.

Thank you for your time and expertise.

Hi ljpkok. I am a beginner in GAF and have participated in #132 . I tried to write code for the inverse transformations of GAF by using 2 funcions provided by pyts. Here is my code.

import numpy as np
def GAF(data,method='s',feature_range=(0,1)):
    '''

    :param data: n_samples*n_features
    :param method: d or s
    :param feature_range: default (0,1)
    :return: GAF matrix
    '''

    n_samples,image_size=data.shape[0],data.shape[1]
    from sklearn.preprocessing import MinMaxScaler
    # Minmaxscaler默认采用的是axis=0,需要转置
    # Minmaxscaler uses axis=0 by default, so data needs to be transposed.
    data=data.T
    scaler=MinMaxScaler(feature_range=feature_range)
    data_cos=scaler.fit_transform(data)
    data_cos=data_cos.T
    from pyts.image.gaf import _gasf,_gadf
    data_sin=np.sqrt(np.clip(1 - data_cos ** 2, 0, 1))
    if method in ['s', 'summation']:
        field=_gasf(data_cos,data_sin,n_samples=n_samples,image_size=image_size)
    else:
        field=_gadf(data_cos,data_sin,n_samples=n_samples,image_size=image_size)
    return field,scaler

def invGAF(gaf,scaler):
    '''get diagonal,get phi,get scaled x (x^~), get original x'''
    diag=np.diagonal(gaf, axis1=1, axis2=2)
    phi=np.arccos(diag)
    scaled=np.cos(phi/2)
    return scaler.inverse_transform(scaled.T).T

I created some data and found invGAF works. At the moment, I am not sure whether data=data.T before MinMaxScaler
is correct and necessary. I hope this will work. Your criticism and correction are welcome.
Btw, I am working on a project related to rainfall, which needs to convert 1D rainfall to images. Can you and I talk more?

I'm very sorry for the late response. There are many issues to reverse images into time series for two reasons:

  • An image is not necessarily a GAF image.
  • Even if an image is a GAF image, it's not always possible to reverse it because the cos and sine functions are not bijective on R (the set of real numbers).

I will first illustrate the second point.

Summary of the GAF transforms

Capture d’écran 2023-07-31 à 14 50 15 Capture d’écran 2023-07-31 à 14 50 25 Capture d’écran 2023-07-31 à 14 50 39

Illustration of the issue

Imagine a simple time series, already rescaled in [-1, 1]:

x = np.array([–1, -0.6, -0.2, 0.2, 0.6, 1])

The corresponding GASF image (with default parameters) is (float numbers rounded to 3 decimals):

array([[ 1.   ,  0.6  ,  0.2  , -0.2  , -0.6  , -1.   ],
       [ 0.6  , -0.28 , -0.664, -0.904, -1.   , -0.6  ],
       [ 0.2  , -0.664, -0.92 , -1.   , -0.904, -0.2  ],
       [-0.2  , -0.904, -1.   , -0.92 , -0.664,  0.2  ],
       [-0.6  , -1.   , -0.904, -0.664, -0.28 ,  0.6  ],
       [-1.   , -0.6  , -0.2  ,  0.2  ,  0.6  ,  1.   ]])

Let's call this array X_gasf. If one takes the diagonal and performs the "inverse" transform, then the result is:

>>> np.cos(np.arccos(np.diag(X_gasf)) / 2)
array([1. , 0.6, 0.2, 0.2, 0.6, 1. ])

You can see that it does not match the original time series. What is the issue?

If one performs the transformation, 2 times the angles is equal to:

>>> 2 * np.arccos(x)
array([6.28318531, 4.42859487, 3.5443085 , 2.73887681, 1.85459044,       0.        ])

But if you take the arccos of the diagonal of the GASF image:

>>> np.arccos(np.diag(X_gasf))
array([0.        , 1.85459044, 2.73887681, 2.73887681, 1.85459044,       0.        ])

One can see that the results are different, because the arccos function return values in [0, π], but in our case the sum of two angles is generally in the [0, 2π] interval. In order to always have the sum of two angles lying in [0, π], the angles must be in [0, π/2], meaning that the rescaled values are in [0, 1].

TLDR: A GASF image is sure to be invertible if and only if the rescaled values are all in [0, 1].

Checking if an image is a GASF

A GASF is a n x n image, but there are only n variables, meaning that we have a system of n^2 linear equations with n variables. To check if an image is a GASF, one can solve n systems of n linear equations with n variables and check if the obtained solutions are all identical (almost equal in practice).

Here is some code to perform this:

import numpy as np

def inverse_gasf_transform(x):
    """Computes the inverse transform of GASF.
    
    Parameters
    ----------
    x : array-like, shape = (n_timestamps, n_timestamps)
        A GASF image.

    Returns
    -------
    x_new : array-like, shape = (n_timestamps,)
        Rescaled time series.
    """
    x = np.asarray(x, dtype='float64')
    n = x.shape[0]

    # Get the angles
    x_angle = np.arccos(x)

    # Compute the matrices of the systems of linear equations
    A = [
        np.eye(n) + np.c_[np.full((n, i), 0.), np.full((n, 1), 1.), np.full((n, n - i - 1), 0.)]
        for i in range(n)
    ]

    # Solve the systems of linear equations
    x_new = [np.cos(np.linalg.solve(A[i], x_angle[i])) for i in range(n)]
    
    # If the input data is a GASF image, all the solutions should be identical
    if np.all([np.allclose(x_new[0], x_new[i]) for i in range(1, n)]):
        return x_new[0]
    else:
        raise ValueError("The input data is probably not a GASF image.")

The same ideas apply to GADF images (although one needs to be careful about the diagonal of the GADF image which is always equal to 0, so the corresponding equations must not be used to perform the inverse transform).