siemensfile 0.1.6__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024 Fernando
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,38 @@
1
+ Metadata-Version: 2.1
2
+ Name: siemensfile
3
+ Version: 0.1.6
4
+ Summary: Paquete para leer archivos .dat de Siemens y realizar reconstrucciones de imágenes.
5
+ Home-page: https://github.com/cenarius1985/SIEMENSFile
6
+ Author: Fernando Jose Ramirez
7
+ Author-email: tu_email@example.com
8
+ Classifier: Programming Language :: Python :: 3
9
+ Classifier: License :: OSI Approved :: MIT License
10
+ Classifier: Operating System :: OS Independent
11
+ Requires-Python: >=3.12.4
12
+ Description-Content-Type: text/markdown
13
+ License-File: LICENSE
14
+ Requires-Dist: numpy
15
+ Requires-Dist: matplotlib
16
+ Requires-Dist: pandas
17
+ Requires-Dist: mri-nufft
18
+ Requires-Dist: scipy
19
+ Requires-Dist: tqdm
20
+ Requires-Dist: tables
21
+ Requires-Dist: gt-twixtools
22
+
23
+ ## Uso
24
+
25
+ Aquí un ejemplo básico de cómo usar SIEMENSFile:
26
+
27
+ ```python
28
+ from siemensfile import process_siemens_file
29
+
30
+ # Procesar un archivo con reconstrucción Cartesiana
31
+ metadata, rawdata = process_siemens_file('ruta/a/tu/archivo.dat', reconstruction="Cartesiana")
32
+
33
+ # Procesar un archivo sin reconstrucción
34
+ metadata, rawdata = process_siemens_file('ruta/a/tu/archivo.dat')
35
+
36
+ # Trabajar con los resultados
37
+ print(metadata)
38
+ print(rawdata.shape)
@@ -0,0 +1,16 @@
1
+ ## Uso
2
+
3
+ Aquí un ejemplo básico de cómo usar SIEMENSFile:
4
+
5
+ ```python
6
+ from siemensfile import process_siemens_file
7
+
8
+ # Procesar un archivo con reconstrucción Cartesiana
9
+ metadata, rawdata = process_siemens_file('ruta/a/tu/archivo.dat', reconstruction="Cartesiana")
10
+
11
+ # Procesar un archivo sin reconstrucción
12
+ metadata, rawdata = process_siemens_file('ruta/a/tu/archivo.dat')
13
+
14
+ # Trabajar con los resultados
15
+ print(metadata)
16
+ print(rawdata.shape)
@@ -0,0 +1,8 @@
1
+ [tool:pytest]
2
+ filterwarnings =
3
+ ignore:pkg_resources is deprecated as an API:DeprecationWarning
4
+
5
+ [egg_info]
6
+ tag_build =
7
+ tag_date = 0
8
+
@@ -0,0 +1,31 @@
1
+ import os
2
+ from setuptools import setup, find_packages
3
+
4
+ setup(
5
+ name="siemensfile", # Cambiado a minúsculas
6
+ version="0.1.6",
7
+ packages=find_packages(where="src"), # Especifica dónde buscar los paquetes
8
+ package_dir={"": "src"}, # Indica que los paquetes están en el directorio src
9
+ install_requires=[
10
+ "numpy",
11
+ "matplotlib",
12
+ "pandas",
13
+ "mri-nufft",
14
+ "scipy",
15
+ "tqdm",
16
+ "tables",
17
+ "gt-twixtools"
18
+ ],
19
+ author="Fernando Jose Ramirez",
20
+ author_email="tu_email@example.com",
21
+ description="Paquete para leer archivos .dat de Siemens y realizar reconstrucciones de imágenes.",
22
+ long_description=open("README.md", encoding="utf-8").read(),
23
+ long_description_content_type="text/markdown",
24
+ url="https://github.com/cenarius1985/SIEMENSFile",
25
+ classifiers=[
26
+ "Programming Language :: Python :: 3",
27
+ "License :: OSI Approved :: MIT License",
28
+ "Operating System :: OS Independent",
29
+ ],
30
+ python_requires='>=3.12.4',
31
+ )
@@ -0,0 +1,3 @@
1
+ from .core import process_siemens_file
2
+
3
+ __all__ = ['process_siemens_file']
@@ -0,0 +1,36 @@
1
+ import os
2
+ import numpy as np
3
+ import pandas as pd
4
+ from twixtools import read_twix
5
+ from .utils import extraer_metadata_recursivamente
6
+ from .reconstruction import reconstruct_image
7
+
8
+ def process_siemens_file(file_path, reconstruction=None):
9
+ try:
10
+ twix = read_twix(file_path, parse_pmu=False)
11
+ metadata = []
12
+ rawdata = []
13
+
14
+ for i, scan in enumerate(twix):
15
+ hdr = scan['hdr']
16
+ metadata_planos = extraer_metadata_recursivamente(hdr)
17
+ metadata.append(metadata_planos)
18
+
19
+ image_mdbs = [mdb for mdb in scan['mdb'] if mdb.is_image_scan()]
20
+ if image_mdbs:
21
+ n_line = 1 + max([mdb.cLin for mdb in image_mdbs])
22
+ n_channel, n_column = image_mdbs[0].data.shape
23
+ kspace = np.zeros([n_line, n_channel, n_column], dtype=np.complex64)
24
+ for mdb in image_mdbs:
25
+ if mdb.cLin < n_line and mdb.data.shape == (n_channel, n_column):
26
+ kspace[mdb.cLin] = mdb.data
27
+ rawdata.append(kspace)
28
+
29
+ if reconstruction:
30
+ reconstruct_image(rawdata, reconstruction)
31
+
32
+ return metadata, rawdata
33
+
34
+ except Exception as e:
35
+ print(f"Error processing SIEMENS file: {str(e)}")
36
+ return None, None
@@ -0,0 +1,22 @@
1
+ import matplotlib.pyplot as plt
2
+ import numpy as np
3
+ from .utils import ifftnd, rms_comb
4
+
5
+ def reconstruct_image(rawdata, method="Cartesiana"):
6
+ if method.lower() == "cartesiana":
7
+ fig, axs = plt.subplots(len(rawdata), 2, figsize=(10, 5*len(rawdata)))
8
+ for i, kspace in enumerate(rawdata):
9
+ # Espacio K
10
+ axs[i, 0].imshow(np.abs(kspace[:, 0])**0.2, cmap='gray')
11
+ axs[i, 0].set_title(f'Espacio K - Scan {i+1}')
12
+
13
+ # Reconstrucción IFFT
14
+ image_ifft = ifftnd(kspace, [0, -1])
15
+ image_ifft = rms_comb(image_ifft)
16
+ axs[i, 1].imshow(np.abs(image_ifft), cmap='gray')
17
+ axs[i, 1].set_title(f'Reconstrucción IFFT - Scan {i+1}')
18
+
19
+ plt.tight_layout()
20
+ plt.show()
21
+ else:
22
+ print(f"Método de reconstrucción '{method}' no implementado.")
@@ -0,0 +1,71 @@
1
+ from twixtools.twixtools import read_twix
2
+ import numpy as np
3
+ import os
4
+ import pandas as pd
5
+ import shutil
6
+ import traceback
7
+ from .utils import *
8
+
9
+ def lectura_twix(ruta_archivo):
10
+ carpeta_destino = os.path.join(os.path.dirname(__file__), "output")
11
+ if os.path.exists(carpeta_destino):
12
+ shutil.rmtree(carpeta_destino)
13
+ os.makedirs(carpeta_destino)
14
+
15
+ try:
16
+ twix = read_twix(ruta_archivo, parse_pmu=False)
17
+ datos = []
18
+ for i, scan in enumerate(twix):
19
+ hdr = scan['hdr']
20
+ metadata_planos = extraer_metadata_recursivamente(hdr)
21
+ datos.append(metadata_planos)
22
+
23
+ df = pd.DataFrame(datos)
24
+ nombre_archivo_json = os.path.join(carpeta_destino, 'datos_twix.json')
25
+ df.to_json(nombre_archivo_json, index=False)
26
+ print(f'Datos guardados exitosamente en {nombre_archivo_json}')
27
+
28
+ print('\nNúmero de escaneos separados (multi-raid):', len(twix))
29
+ for i, scan in enumerate(twix):
30
+ print(f'\nProcesando Scan {i+1}:')
31
+ try:
32
+ print('TR = %d ms\n' % (scan['hdr']['Phoenix']['alTR'][0] / 1000))
33
+
34
+ image_mdbs = [mdb for mdb in scan['mdb'] if mdb.is_image_scan()]
35
+ if not image_mdbs:
36
+ print(f"No se encontraron escaneos de imagen válidos para el Scan {i+1}. No se puede reconstruir la imagen.")
37
+ continue
38
+
39
+ n_line = 1 + max([mdb.cLin for mdb in image_mdbs])
40
+ n_channel, n_column = image_mdbs[0].data.shape
41
+ kspace = np.zeros([n_line, n_channel, n_column], dtype=np.complex64)
42
+ for mdb in image_mdbs:
43
+ if mdb.cLin < n_line and mdb.data.shape == (n_channel, n_column):
44
+ kspace[mdb.cLin] = mdb.data
45
+ print('Forma del espacio k:', kspace.shape)
46
+
47
+ # Guardar imagen del espacio k
48
+ save_image(kspace[:, 0]**0.2,
49
+ os.path.join(carpeta_destino, f'espacio_k_scan_{i+1}.png'),
50
+ f'Espacio K - Scan {i+1}',
51
+ 'Datos crudos antes de la reconstrucción')
52
+
53
+ # Reconstrucción IFFT (Cartesiana)
54
+ image_ifft = ifftnd(kspace, [0, -1])
55
+ image_ifft = rms_comb(image_ifft)
56
+ save_image(image_ifft,
57
+ os.path.join(carpeta_destino, f'reconstruccion_cartesiana_ifft_scan_{i+1}.png'),
58
+ f'Reconstrucción Cartesiana (IFFT) - Scan {i+1}',
59
+ 'Transformada inversa de Fourier rápida')
60
+
61
+ print(f"Imágenes guardadas para el Scan {i+1}")
62
+ except Exception as e:
63
+ print(f"Error procesando Scan {i+1}: {str(e)}")
64
+ traceback.print_exc()
65
+
66
+ print(f"Procesamiento completado. Resultados guardados en {carpeta_destino}")
67
+ except Exception as e:
68
+ print(f"Error al leer el archivo twix: {str(e)}")
69
+ traceback.print_exc()
70
+
71
+ return os.path.abspath(carpeta_destino)
@@ -0,0 +1,58 @@
1
+ import numpy as np
2
+ import matplotlib.pyplot as plt
3
+ import pandas as pd
4
+
5
+ def ifftnd(kspace, axes=None):
6
+ from numpy.fft import fftshift, ifftshift, ifftn
7
+ if axes is None:
8
+ axes = range(kspace.ndim)
9
+ elif isinstance(axes, int):
10
+ axes = [axes]
11
+
12
+ if any(ax >= kspace.ndim or ax < -kspace.ndim for ax in axes):
13
+ raise ValueError("Invalid axis. Axis must be less than the dimensions of kspace.")
14
+
15
+ axes = [ax if ax >= 0 else kspace.ndim + ax for ax in axes]
16
+
17
+ # Aplicar ifftshift solo a los ejes especificados
18
+ kspace_shifted = ifftshift(kspace, axes=axes)
19
+
20
+ # Realizar la IFFT
21
+ img = ifftn(kspace_shifted, axes=axes, norm="ortho")
22
+
23
+ # Aplicar fftshift solo a los ejes especificados
24
+ img = fftshift(img, axes=axes)
25
+
26
+ return img
27
+
28
+
29
+ def rms_comb(sig, axis=1):
30
+ if axis >= sig.ndim:
31
+ raise ValueError(f"Invalid axis {axis} for array with {sig.ndim} dimensions.")
32
+ return np.sqrt(np.mean(np.abs(sig)**2, axis=axis))
33
+
34
+ def extraer_metadata_recursivamente(hdr, prefijo=''):
35
+ datos_planos = {}
36
+ for clave, valor in hdr.items():
37
+ nueva_clave = f"{prefijo}.{clave}" if prefijo else clave
38
+ if isinstance(valor, dict):
39
+ datos_planos.update(extraer_metadata_recursivamente(valor, nueva_clave))
40
+ else:
41
+ try:
42
+ if pd.isna(valor) or valor is None:
43
+ valor = 'Desconocido'
44
+ datos_planos[nueva_clave] = valor
45
+ except:
46
+ datos_planos[nueva_clave] = 'Valor no compatible'
47
+ return datos_planos
48
+
49
+ def save_image(data, filename, title, subtitle=None):
50
+ plt.figure(figsize=(8, 8))
51
+ plt.imshow(np.abs(data), cmap='gray', origin='lower')
52
+ plt.title(title, fontsize=14, fontweight='bold')
53
+ if subtitle:
54
+ plt.text(0.5, -0.05, subtitle, ha='center', va='center', transform=plt.gca().transAxes, fontsize=10)
55
+ plt.axis('off')
56
+ plt.tight_layout()
57
+ plt.savefig(filename, dpi=300, bbox_inches='tight')
58
+ plt.close()
@@ -0,0 +1,38 @@
1
+ Metadata-Version: 2.1
2
+ Name: siemensfile
3
+ Version: 0.1.6
4
+ Summary: Paquete para leer archivos .dat de Siemens y realizar reconstrucciones de imágenes.
5
+ Home-page: https://github.com/cenarius1985/SIEMENSFile
6
+ Author: Fernando Jose Ramirez
7
+ Author-email: tu_email@example.com
8
+ Classifier: Programming Language :: Python :: 3
9
+ Classifier: License :: OSI Approved :: MIT License
10
+ Classifier: Operating System :: OS Independent
11
+ Requires-Python: >=3.12.4
12
+ Description-Content-Type: text/markdown
13
+ License-File: LICENSE
14
+ Requires-Dist: numpy
15
+ Requires-Dist: matplotlib
16
+ Requires-Dist: pandas
17
+ Requires-Dist: mri-nufft
18
+ Requires-Dist: scipy
19
+ Requires-Dist: tqdm
20
+ Requires-Dist: tables
21
+ Requires-Dist: gt-twixtools
22
+
23
+ ## Uso
24
+
25
+ Aquí un ejemplo básico de cómo usar SIEMENSFile:
26
+
27
+ ```python
28
+ from siemensfile import process_siemens_file
29
+
30
+ # Procesar un archivo con reconstrucción Cartesiana
31
+ metadata, rawdata = process_siemens_file('ruta/a/tu/archivo.dat', reconstruction="Cartesiana")
32
+
33
+ # Procesar un archivo sin reconstrucción
34
+ metadata, rawdata = process_siemens_file('ruta/a/tu/archivo.dat')
35
+
36
+ # Trabajar con los resultados
37
+ print(metadata)
38
+ print(rawdata.shape)
@@ -0,0 +1,15 @@
1
+ LICENSE
2
+ README.md
3
+ setup.cfg
4
+ setup.py
5
+ src/siemensfile/__init__.py
6
+ src/siemensfile/core.py
7
+ src/siemensfile/reconstruction.py
8
+ src/siemensfile/siemensfile.py
9
+ src/siemensfile/utils.py
10
+ src/siemensfile.egg-info/PKG-INFO
11
+ src/siemensfile.egg-info/SOURCES.txt
12
+ src/siemensfile.egg-info/dependency_links.txt
13
+ src/siemensfile.egg-info/requires.txt
14
+ src/siemensfile.egg-info/top_level.txt
15
+ tests/test_siemensfile.py
@@ -0,0 +1,8 @@
1
+ numpy
2
+ matplotlib
3
+ pandas
4
+ mri-nufft
5
+ scipy
6
+ tqdm
7
+ tables
8
+ gt-twixtools
@@ -0,0 +1 @@
1
+ siemensfile
@@ -0,0 +1,141 @@
1
+ import unittest
2
+ import numpy as np
3
+ import shutil
4
+ import os
5
+ from unittest.mock import patch
6
+ from src.siemensfile.siemensfile import lectura_twix
7
+ from src.siemensfile.utils import ifftnd, rms_comb, extraer_metadata_recursivamente, save_image
8
+ import os
9
+
10
+ class TestSIEMENSFile(unittest.TestCase):
11
+
12
+ @classmethod
13
+ def setUpClass(cls):
14
+ cls.ruta_archivo = os.path.join(os.path.dirname(__file__), "datatest", "siemens_file_test_cartesian_sample.dat")
15
+
16
+ def test_ifftnd(self):
17
+ kspace = np.zeros((4, 4), dtype=np.complex64)
18
+ kspace[0, 0] = 1.0 + 1.0j
19
+ result = ifftnd(kspace)
20
+ print("Resultado de ifftnd:", result)
21
+ self.assertEqual(result.shape, (4, 4))
22
+ print("Valor en [2, 0]:", np.abs(result[2, 0]))
23
+ self.assertAlmostEqual(np.abs(result[0, 0]), 0.3535533905932738, places=7)
24
+
25
+ def test_ifftnd_2d(self):
26
+ kspace = np.zeros((4, 4), dtype=np.complex64)
27
+ kspace[0, 0] = 1.0
28
+ result = ifftnd(kspace, axes=[0, 1])
29
+ self.assertEqual(result.shape, (4, 4))
30
+ # Verificar que todos los valores tienen la misma magnitud
31
+ self.assertTrue(np.allclose(np.abs(result), 0.25, atol=1e-7))
32
+ # Verificar que la suma de los valores absolutos al cuadrado es cercana a 1
33
+ self.assertAlmostEqual(np.sum(np.abs(result)**2), 1.0, places=7)
34
+
35
+ def test_ifftnd_complex(self):
36
+ kspace = np.zeros((8, 8), dtype=np.complex64)
37
+ kspace[0, 0] = 1 + 1j
38
+ kspace[0, 1] = 1 - 1j
39
+ result = ifftnd(kspace)
40
+ # Verificar que el resultado tiene valores complejos
41
+ self.assertFalse(np.allclose(result.imag, 0, atol=1e-7))
42
+ # Verificar que la suma de los valores absolutos al cuadrado es igual a la entrada
43
+ input_energy = np.sum(np.abs(kspace)**2)
44
+ output_energy = np.sum(np.abs(result)**2)
45
+ self.assertAlmostEqual(output_energy, input_energy, places=6) # Reducimos la precisión a 6 decimales
46
+ # Verificar que la energía total es cercana a 4 (2^2 + 2^2)
47
+ self.assertAlmostEqual(output_energy, 4.0, places=6) # Reducimos la precisión a 6 decimales
48
+ # Verificar que la diferencia relativa entre energías es pequeña
49
+ relative_error = abs(output_energy - input_energy) / input_energy
50
+ self.assertLess(relative_error, 1e-6) # Permitimos un error relativo de hasta 0.0001%
51
+
52
+ def test_rms_comb(self):
53
+ sig = np.array([[1.0, 2.0], [3.0, 4.0]])
54
+ result = rms_comb(sig, axis=1)
55
+ self.assertEqual(result.shape, (2,))
56
+ self.assertAlmostEqual(result[0], 1.5811388300841898, places=7)
57
+ self.assertAlmostEqual(result[1], 3.5355339059327378, places=7)
58
+
59
+ def test_rms_comb_3d(self):
60
+ sig = np.ones((2, 3, 4))
61
+ result = rms_comb(sig, axis=2)
62
+ self.assertEqual(result.shape, (2, 3))
63
+ self.assertAlmostEqual(result[0, 0], 1.0, places=7)
64
+
65
+ def test_rms_comb_invalid_axis(self):
66
+ sig = np.array([[1, 2], [3, 4]])
67
+ with self.assertRaises(ValueError):
68
+ rms_comb(sig, axis=2) # Invalid axis
69
+
70
+ def test_extraer_metadata_recursivamente(self):
71
+ hdr = {
72
+ 'Phoenix': {
73
+ 'alTR': [2000],
74
+ 'alTE': [30]
75
+ },
76
+ 'Meas': {
77
+ 'TE': 30
78
+ }
79
+ }
80
+ result = extraer_metadata_recursivamente(hdr)
81
+ expected = {
82
+ 'Phoenix.alTR': [2000],
83
+ 'Phoenix.alTE': [30],
84
+ 'Meas.TE': 30
85
+ }
86
+ self.assertDictEqual(result, expected)
87
+ def test_extraer_metadata_recursivamente_nested(self):
88
+ hdr = {
89
+ 'level1': {
90
+ 'level2': {
91
+ 'key': 'value'
92
+ }
93
+ },
94
+ 'another_key': 42
95
+ }
96
+ result = extraer_metadata_recursivamente(hdr)
97
+ self.assertEqual(result['level1.level2.key'], 'value')
98
+ self.assertEqual(result['another_key'], 42)
99
+
100
+ def test_save_image(self):
101
+ data = np.random.rand(4, 4)
102
+ filename = "test_image.png"
103
+ save_image(data, filename, "Test Image")
104
+ self.assertTrue(os.path.exists(filename))
105
+ os.remove(filename)
106
+
107
+ def test_save_image_with_subtitle(self):
108
+ data = np.random.rand(10, 10)
109
+ filename = "test_image_subtitle.png"
110
+ save_image(data, filename, "Test Title", "Test Subtitle")
111
+ self.assertTrue(os.path.exists(filename))
112
+ os.remove(filename)
113
+
114
+ def test_lectura_twix(self):
115
+ if os.path.exists(self.ruta_archivo):
116
+ result_dir = lectura_twix(self.ruta_archivo)
117
+ self.assertTrue(os.path.exists(result_dir))
118
+ self.assertTrue(os.path.exists(os.path.join(result_dir, 'datos_twix.json')))
119
+ self.assertTrue(os.path.exists(os.path.join(result_dir, 'espacio_k_scan_1.png')))
120
+ self.assertTrue(os.path.exists(os.path.join(result_dir, 'reconstruccion_cartesiana_ifft_scan_1.png')))
121
+ print(f"Imágenes guardadas en: {result_dir}")
122
+ shutil.rmtree(result_dir)
123
+ else:
124
+ self.fail(f"Archivo de prueba no encontrado: {self.ruta_archivo}")
125
+
126
+ @patch('src.siemensfile.siemensfile.read_twix')
127
+ def test_lectura_twix_mocked(self, mock_read_twix):
128
+ mock_scan = {
129
+ 'hdr': {'Phoenix': {'alTR': [2000]}},
130
+ 'mdb': []
131
+ }
132
+ mock_read_twix.return_value = [mock_scan]
133
+ with patch('builtins.print') as mock_print:
134
+ result = lectura_twix("mock_file.dat")
135
+ self.assertTrue(os.path.exists(result))
136
+ self.assertTrue(os.path.exists(os.path.join(result, 'datos_twix.json')))
137
+ mock_print.assert_any_call("No se encontraron escaneos de imagen válidos para el Scan 1. No se puede reconstruir la imagen.")
138
+ shutil.rmtree(result)
139
+
140
+ if __name__ == '__main__':
141
+ unittest.main()