Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Request for Code or Guidance on Reversing Gramian Angular Fields Transformation #154

Closed
ljpkok opened this issue Jun 27, 2023 · 3 comments
Closed

Comments

@ljpkok
Copy link

ljpkok commented Jun 27, 2023

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.

@ljpkok
Copy link
Author

ljpkok commented Jun 27, 2023

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

@noneaddress
Copy link

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?

@johannfaouzi
Copy link
Owner

johannfaouzi commented Jul 31, 2023

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).

@ljpkok ljpkok closed this as completed Aug 4, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants