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.
- siemensfile-0.1.6/LICENSE +21 -0
- siemensfile-0.1.6/PKG-INFO +38 -0
- siemensfile-0.1.6/README.md +16 -0
- siemensfile-0.1.6/setup.cfg +8 -0
- siemensfile-0.1.6/setup.py +31 -0
- siemensfile-0.1.6/src/siemensfile/__init__.py +3 -0
- siemensfile-0.1.6/src/siemensfile/core.py +36 -0
- siemensfile-0.1.6/src/siemensfile/reconstruction.py +22 -0
- siemensfile-0.1.6/src/siemensfile/siemensfile.py +71 -0
- siemensfile-0.1.6/src/siemensfile/utils.py +58 -0
- siemensfile-0.1.6/src/siemensfile.egg-info/PKG-INFO +38 -0
- siemensfile-0.1.6/src/siemensfile.egg-info/SOURCES.txt +15 -0
- siemensfile-0.1.6/src/siemensfile.egg-info/dependency_links.txt +1 -0
- siemensfile-0.1.6/src/siemensfile.egg-info/requires.txt +8 -0
- siemensfile-0.1.6/src/siemensfile.egg-info/top_level.txt +1 -0
- siemensfile-0.1.6/tests/test_siemensfile.py +141 -0
|
@@ -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,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,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 @@
|
|
|
1
|
+
|
|
@@ -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()
|