Repository: openmrslab/suspect

Issue:
Add adjust_phase function
Add a function which can be passed an MRSData object and a zeroth (and optional first) order phase, and returns a new MRSData object which has had the phase shifts applied. This should also be callable with a method on the original MRSData object, by analogy with methods like np.ndarray.clip()

Discussion:

Patch:
diff --git a/docs/index.rst b/docs/index.rst
index 86af541..4726e2a 100644
--- a/docs/index.rst
+++ b/docs/index.rst
@@ -26,4 +26,5 @@ API Reference
    :maxdepth: 1
    :glob:
 
-   mrs_data_api.rst
+   suspect_api.rst
+   mrs_data_api.rst
\ No newline at end of file
diff --git a/docs/suspect_api.rst b/docs/suspect_api.rst
new file mode 100644
index 0000000..0e12fb5
--- /dev/null
+++ b/docs/suspect_api.rst
@@ -0,0 +1,5 @@
+Suspect API Reference
+=====================
+
+.. automodule:: suspect
+    :members: adjust_phase
diff --git a/suspect/__init__.py b/suspect/__init__.py
index bb1d5fd..659b4c4 100644
--- a/suspect/__init__.py
+++ b/suspect/__init__.py
@@ -7,3 +7,4 @@ from . import fitting
 from . import image
 from . import viz
 from ._version import __version__
+from .core import adjust_phase
diff --git a/suspect/core.py b/suspect/core.py
new file mode 100644
index 0000000..ffbde62
--- /dev/null
+++ b/suspect/core.py
@@ -0,0 +1,25 @@
+from .mrsdata import MRSData
+
+
+def adjust_phase(data, zero_phase, first_phase=0, fixed_frequency=0):
+    """
+    Adjust the phase of an MRSData object
+
+    Parameters
+    ----------
+    data : MRSData
+        The MRSData object to be phased
+    zero_phase : scalar
+        The change to the zero order phase, in radians
+    first_phase : scalar, optional
+        The change to the first order phase, in radians per Hz
+    fixed_frequency : scalar, optional
+        The frequency, in Hz, which is unchanged by the first order
+        phase shift
+
+    Returns
+    -------
+    out : MRSData
+        A new MRSData object with adjusted phase.
+    """
+    return data.adjust_phase(zero_phase, first_phase, fixed_frequency)
diff --git a/suspect/mrsdata.py b/suspect/mrsdata.py
index 0be660d..3d251e7 100644
--- a/suspect/mrsdata.py
+++ b/suspect/mrsdata.py
@@ -246,4 +246,26 @@ class MRSData(numpy.ndarray):
 
         transformed_point = numpy.linalg.inv(self.transform) * numpy.matrix([x, y, z, 1]).T
 
-        return numpy.squeeze(numpy.asarray(transformed_point))[0:3]
\ No newline at end of file
+        return numpy.squeeze(numpy.asarray(transformed_point))[0:3]
+
+    def adjust_phase(self, zero_phase, first_phase=0, fixed_frequency=0):
+        """
+        Adjust the phases of the signal.
+
+        Refer to suspect.adjust_phase for full documentation.
+
+        See Also
+        --------
+        suspect.adjust_phase : equivalent function
+        """
+        # easiest to apply the phase shift in the frequency domain
+        # TODO when MRSSpectrum is a real class, this function can delegate
+        # to that one.
+        spectrum = self.spectrum()
+        phase_ramp = numpy.linspace(-self.sw / 2,
+                                    self.sw / 2,
+                                    self.np,
+                                    endpoint=False)
+        phase_shift = zero_phase + first_phase * (fixed_frequency + phase_ramp)
+        phased_spectrum = spectrum * numpy.exp(1j * phase_shift)
+        return self.inherit(numpy.fft.ifft(numpy.fft.ifftshift(phased_spectrum, axes=-1), axis=-1))


Test patch:
diff --git a/tests/test_mrs/test_core.py b/tests/test_mrs/test_core.py
new file mode 100644
index 0000000..97a9f0e
--- /dev/null
+++ b/tests/test_mrs/test_core.py
@@ -0,0 +1,17 @@
+import suspect
+
+import numpy
+
+
+def test_adjust_zero_phase():
+    data = suspect.MRSData(numpy.ones(10), 1e-3, 123)
+    phased_data = suspect.adjust_phase(data, numpy.pi / 2)
+    numpy.testing.assert_allclose(phased_data, 1j * numpy.ones(10))
+
+
+def test_adjust_first_phase():
+    data = suspect.MRSData(numpy.fft.ifft(numpy.ones(10)), 1e-1, 123)
+    phased_data = suspect.adjust_phase(data, 0, numpy.pi / 10).spectrum()
+    numpy.testing.assert_almost_equal(phased_data[0], -1j)
+    numpy.testing.assert_almost_equal(phased_data[-1], numpy.exp(1j * 0.4 * numpy.pi))
+    numpy.testing.assert_almost_equal(phased_data[5], 1)