Repository: openmrslab/suspect

Issue:
Support loading DICOM files
Support loading DICOM SOP 1.2.840.10008.5.1.4.1.1.4.2 files

Comments:

Patch:
diff --git a/suspect/io/__init__.py b/suspect/io/__init__.py
index c024146..040d217 100644
--- a/suspect/io/__init__.py
+++ b/suspect/io/__init__.py
@@ -2,4 +2,5 @@ from suspect.io.rda import load_rda
 from suspect.io.twix import load_twix
 from suspect.io.philips import load_sdat
 from suspect.io.siemens import load_siemens_dicom
-from . import tarquin, lcmodel, felix
\ No newline at end of file
+from . import tarquin, lcmodel, felix
+from .dicom import load_dicom
diff --git a/suspect/io/_common.py b/suspect/io/_common.py
new file mode 100644
index 0000000..754da48
--- /dev/null
+++ b/suspect/io/_common.py
@@ -0,0 +1,29 @@
+import numpy as np
+
+
+def complex_array_from_iter(data_iter, length=-1, shape=None, chirality=1):
+    """
+    Converts an iterable over a series of real, imaginary pairs into
+    a numpy.ndarray of complex64.
+
+    Parameters
+    ----------
+    data_iter : iter
+        The iterator over the points
+    length : int
+        The number of complex points to read. The defaults is -1, which
+        means all data is read.
+    shape : array-like
+        Shape to convert the final array to. The array will be squeezed
+        after reshaping to remove any dimensions of length 1.
+
+    Returns
+    -------
+    out : ndarray
+        The output array
+    """
+    complex_iter = (complex(r, chirality * i) for r, i in zip(data_iter, data_iter))
+    complex_array = np.fromiter(complex_iter, "complex64", length)
+    if shape is not None:
+        complex_array = np.reshape(complex_array, shape).squeeze()
+    return complex_array
diff --git a/suspect/io/dicom.py b/suspect/io/dicom.py
new file mode 100644
index 0000000..e368428
--- /dev/null
+++ b/suspect/io/dicom.py
@@ -0,0 +1,48 @@
+from suspect import MRSData
+
+from ._common import complex_array_from_iter
+
+import pydicom.dicomio
+import pydicom.tag
+
+
+def load_dicom(filename):
+    """
+    Load a file in the DICOM Magnetic Resonance Spectroscopy format
+    (SOP 1.2.840.10008.5.1.4.1.1.4.2)
+
+    Parameters
+    ----------
+    filename : str
+        The name of the file to load
+
+    Returns
+    -------
+    MRSData
+        The loaded data from the file
+    """
+    dataset = pydicom.dicomio.read_file(filename)
+
+    sw = dataset[0x0018, 0x9052].value
+    dt = 1.0 / sw
+
+    f0 = dataset[0x0018, 0x9098].value
+
+    ppm0 = dataset[0x0018, 0x9053].value
+
+    rows = dataset[0x0028, 0x0010].value
+    cols = dataset[0x0028, 0x0011].value
+    frames = dataset[0x0028, 0x0008].value
+    num_second_spectral = dataset[0x0028, 0x9001].value
+    num_points = dataset[0x0028, 0x9002].value
+
+    data_shape = [frames, rows, cols, num_second_spectral, num_points]
+
+    # turn the data into a numpy array
+    data_iter = iter(dataset[0x5600, 0x0020])
+    data = complex_array_from_iter(data_iter, shape=data_shape, chirality=-1)
+
+    return MRSData(data,
+                   dt,
+                   f0,
+                   ppm0=ppm0)
diff --git a/suspect/io/siemens.py b/suspect/io/siemens.py
index 65f3798..ad3763d 100644
--- a/suspect/io/siemens.py
+++ b/suspect/io/siemens.py
@@ -4,6 +4,7 @@ import numpy
 import struct
 
 from suspect import MRSData
+from ._common import complex_array_from_iter
 
 CSA1 = 0
 CSA2 = 1
@@ -143,14 +144,9 @@ def load_siemens_dicom(filename):
     csa_data_bytes = dataset[0x7fe1, 0x0100 * data_index + 0x0010].value
     # the data is stored as a list of 4 byte floats in (real, imaginary) pairs
     data_floats = struct.unpack("<%df" % (len(csa_data_bytes) / 4), csa_data_bytes)
-    # form an iterator which will provide the numbers one at a time, then an iterator which calls that iterator twice
-    # each cycle to give a complex pair
-    data_iter = iter(data_floats)
-    complex_iter = (complex(r, i) for r, i in zip(data_iter, data_iter))
-    # give this iterator to numpy to build the data array
-    complex_data = numpy.fromiter(complex_iter, "complex128", int(len(csa_data_bytes) / 8))
-    # reshape the array to structure of rows, columns and slices
-    complex_data = numpy.reshape(complex_data, data_shape).squeeze()
+    complex_data = complex_array_from_iter(iter(data_floats),
+                                           length=len(data_floats) // 2,
+                                           shape=data_shape)
 
     return MRSData(complex_data,
                    csa_header["RealDwellTime"] * 1e-9,
diff --git a/suspect/processing/denoising.py b/suspect/processing/denoising.py
index fd49a14..f55b7ae 100644
--- a/suspect/processing/denoising.py
+++ b/suspect/processing/denoising.py
@@ -34,7 +34,7 @@ def sliding_window(input_signal, window_width):
     window /= numpy.sum(window)
     # pad the signal to cover half the window width on each side
     padded_input = _pad(input_signal, len(input_signal) + window_width - 1)
-    result = numpy.zeros(len(input_signal))
+    result = numpy.zeros_like(input_signal)
     for i in range(len(input_signal)):
         result[i] = numpy.dot(window, padded_input[i:(i + window_width)])
     return result
@@ -62,7 +62,7 @@ def sift(input_signal, threshold):
 def svd(input_signal, rank):
     matrix_width = int(len(input_signal) / 2)
     matrix_height = len(input_signal) - matrix_width + 1
-    hankel_matrix = numpy.zeros((matrix_width, matrix_height))
+    hankel_matrix = numpy.zeros((matrix_width, matrix_height), input_signal.dtype)
     for i in range(matrix_height):
         hankel_matrix[:, i] = input_signal[i:(i + matrix_width)]
     # perform the singular value decomposition
diff --git a/suspect/processing/frequency_correction.py b/suspect/processing/frequency_correction.py
index 6ea3a89..6cd6e6c 100644
--- a/suspect/processing/frequency_correction.py
+++ b/suspect/processing/frequency_correction.py
@@ -29,10 +29,10 @@ def spectral_registration(data, target, initial_guess=(0.0, 0.0), frequency_rang
 
     Parameters
     ----------
-    data :
-    target :
-    initial_guess :
-    frequency_range:
+    data : MRSData
+    target : MRSData
+    initial_guess : tuple
+    frequency_range : 
 
     Returns
     -------


Test patch:
diff --git a/tests/test_mrs/test_io.py b/tests/test_mrs/test_io.py
index d80a0a1..240855c 100644
--- a/tests/test_mrs/test_io.py
+++ b/tests/test_mrs/test_io.py
@@ -6,9 +6,25 @@ import builtins
 from unittest.mock import patch
 import os
 
+from suspect.io._common import complex_array_from_iter
+
 import numpy
 
 
+def test_complex_from_iter():
+    float_list = [1.0, 0.0, 0.0, 1.0]
+    array = complex_array_from_iter(iter(float_list))
+    assert array.shape == (2,)
+    assert array[0] == 1
+    assert array[1] == 1j
+
+
+def test_shaped_complex_from_iter():
+    float_list = [1.0, 1.0, 0.0, 0.0, 1.0, 0.0, 0.0, 1.0]
+    array = complex_array_from_iter(iter(float_list), shape=[2, 2])
+    assert array.shape == (2, 2)
+
+
 def test_write_dpt():
     data = suspect.MRSData(numpy.zeros(1), 1e-3, 123.456)
     mock = unittest.mock.mock_open()
diff --git a/tests/test_mrs/test_processing.py b/tests/test_mrs/test_processing.py
index 40ea9c3..9bf35ef 100644
--- a/tests/test_mrs/test_processing.py
+++ b/tests/test_mrs/test_processing.py
@@ -1,6 +1,9 @@
 import suspect
 
 import numpy
+import warnings
+
+warnings.filterwarnings('error')
 
 
 def test_null_transform():
@@ -81,6 +84,24 @@ def test_gaussian_denoising():
     numpy.testing.assert_almost_equal(data, denoised_data)
 
 
+def test_svd_dtype():
+    data = numpy.ones(128, dtype=complex)
+    denoised_data = suspect.processing.denoising.svd(data, 8)
+    assert data.dtype == denoised_data.dtype
+
+
+def test_sliding_window_dtype():
+    data = numpy.ones(128, dtype=complex)
+    denoised_data = suspect.processing.denoising.sliding_window(data, 30)
+    assert data.dtype == denoised_data.dtype
+
+
+def test_sliding_gaussian_dtype():
+    data = numpy.ones(128, dtype=complex)
+    denoised_data = suspect.processing.denoising.sliding_gaussian(data, 30)
+    assert data.dtype == denoised_data.dtype
+
+
 def test_water_suppression():
     data = suspect.io.load_twix("tests/test_data/siemens/twix_vb.dat")
     channel_combined_data = data.inherit(numpy.average(data, axis=1))