Skip to content
Snippets Groups Projects
Commit dff6758d authored by Martina Barreiro Guerra's avatar Martina Barreiro Guerra
Browse files

ImpetomC.

parent fe117bef
No related branches found
No related tags found
No related merge requests found
import threading
import time
from datetime import date, datetime
import numpy as np
import re
from pyeit.eit import jac
from pyeit.eit.interp2d import sim2pts
from Archivos import Archivos
from Arduino import Arduino
from ImagenesDicom import ImagenesDicom
import os
from ImpetomCError import ImpetomCError
from ImpetomCElectrodeError import ImpetomCElectrodeError
import matplotlib.pyplot as plt
#modulos de pyeit
import pyeit.mesh as mesh
from pyeit.eit.fem import Forward
from pyeit.eit.utils import eit_scan_lines
from pyeit.mesh.shape import thorax,circle
flag_InstanceStudy = True
class ImpetomC:
"""
Permite manejar todas las funcionalidades de la aplicación ImpetomC
- Generación y guardado de la imagen tomográfica en formato dicom
- Conexión del arduino ImpetomC al puerto serie
"""
#Parametros para las imagenes reconstruidas
cmap=plt.cm.RdYlBu # color para las imagenes generadas
shape = "thorax"
lamb=0.01
#vectores de datos
datos = np.zeros(208) # Variable para el manejo de los datos de entrada desde Arduino ImpetomC
archiVH = Archivos("homo_py.txt")# vector homogéneo por defecto. Puede cambiarse utilizando la función actualizarVectorDeDatosHomogeneo
vHomogeneo = archiVH.leerArchivo()
#Variables para el manejo del dispositivo ImpetomC
ard = None # se actualiza con startUp() o __arduinoConnect()
arduinoComunicationError = None # Variable para manejar el error de que se desconecte el Arduino de la pc mientras adquiere datos
# Variables globales utilizadas para el manejo de problemas asosciados a los electrodos
voltajesElevados = 0
voltajesElevadosSeguidos = 0
tiraMedidaElevada = 0
#Parámetros del sistema
rootDir=os.path.dirname(os.path.abspath("ImpetomC.py")).replace("ImpetomC.py","") #Ubicación del directorio donde se guardaran las imágenes generadas
today = date.today().strftime("%b-%d-%Y")
#Establecimiento de los parámetros de la imagen dicom
imgDicom = ImagenesDicom(p=0.3,lamb=lamb,file_path_jpg= os.path.join(rootDir, "IMG.jpg"),today=today,rootDir=rootDir, shape=shape)
@classmethod
def startUp(cls):
"""
Esta función permite hacer el start up de la aplicación. Utilizarla para dar inicio a la aplicación
:exception ImpetomCError: Devuelve una excepción en caso de encontrar problemas con la conexión a el dispositivo Arduino
"""
cls.__arduinoConnect()
@classmethod
def __arduinoConnect(cls):
"""
Permite establecer la conexión con el Arduino con el software de ImpetomC
:exception ImpetomCError: Devuelve una excepción con el mensaje correspondiente
"""
cls.ard = Arduino(baudrate = 9600, timeout = 0.1,vThreshold = 4.0) #para cambiar el valor del threshold de voltaje cambiar vThreshold
cls.arduinoComunicationError = None
@classmethod
def reset(cls):
"""
Este método permite realizar el reseteo de la aplicacion, devolviendo la aplicacion a sus valores por defecto
"""
cls.datos = np.zeros(208)
cls.ard.cerrarPuertoSerie()
archiVH = Archivos("homo_py.txt") # vector homogéneo por defecto. Puede cambiarse utilizando la función actualizarVectorDeDatosHomogeneo
cls.vHomogeneo = archiVH.leerArchivo()
cls.startUp()
@classmethod
def actualizarVectorDeDatosHomogeneo(cls):
"""
Permite volver a cargar el vector de datos homogeneo en modo reconstrucción para un mejor detalle del vector
"""
cls.ard.cambiarModoDeOperacion("Reconstruccion")
datosRecalibrados, ve, fa, vS, ta = cls.ard.obtenerMedidas()
archiCalibrar = Archivos("datos_calibrar.txt")
archiCalibrar.escribirArchivo(datosRecalibrados)
cls.vHomogeneo = archiCalibrar.leerArchivo()
@classmethod
def __verificarParametrosParaImagen(cls,mode,shape,vmax,vmin,screenWidth,p,sclEnable):
"""
Permite verificar que los parámetros de entrada para la reconstrucción tomográfica sean correctos
:param mode: Tipo string, debe contener la palabra "reconstruccion" o "setup"
:param shape: Tipo String, debe contener la palabra "thorax" o "circle"
:param vmax: Tipo float, máximo valor para la escala de la imagen
:param vmin: Tipo float, mínimo valor para la escala de la imagen
:param screenWidth: Tipo Int, ancho de la pantalla del usuario
:param p: Parámetro para la reconstrucción con JAC, se recomienda que el valor esté en 0.1 y 0.9
:param SclEnable: Debe ser un booleano. True para utilizar los valores vmax y vmin y false para no utilizarlos
:returns: **meshDensity** - Retorna la densidad de mesh a utilizar, esto depende de el modo de trabajo de la aplicación y el shape utilizado
**retShape** - Retorna un shape reconocible por las funcionalidades de pyEIT
:exception ImpetomCError: Devuelve una excepción con el mensaje de error correspondiente
"""
mode=mode.lower()
if mode == "reconstruccion": # Determino el mesh density
meshDensity= 0.045
elif mode == "setup":
if shape == "thorax": # Determino mesh de setup según modelo
meshDensity = 0.07
elif shape == "circle":
meshDensity = 0.1
else:
raise ImpetomCError("Forma para la reconstrucción inválida, debe ser \"Thorax\" o \"Circle\"")
else:
raise ImpetomCError("Modo de reconstrucción inválido. Debe ser \"Reconstruccion\" o \"Setup\"")
shape=shape.lower()
if shape == "thorax": # Determino la forma de la reconstrucción
retShape=thorax
elif shape == "circle":
retShape = circle
else:
raise ImpetomCError("Forma para la reconstrucción inválida, debe ser \"Thorax\" o \"Circle\"")
if type(vmax) != float or type(vmin) != float or type(screenWidth) != int or type(p) != float or type(sclEnable) != bool:
raise ImpetomCError("vmax, vmin, p deben ser floats , screenWidth debe ser un int y sclEnable un bool")
elif screenWidth <= 0 or p <=0 :
raise ImpetomCError("screenWidth y p deben ser mayores que 0")
return [meshDensity,retShape]
@classmethod
def __obtenerimagen(cls,vectorDatos,SclEnable,vmax,vmin,shape,mode,p,screenWidth):
"""
Pemite generar una imágen tomográfica a partir de un vector de datos, entre otros parámetros
:param vectorDatos: Vector de 208 valores de tensión para reconstruir la imágen tomográfica
:param SclEnable: Debe ser un booleano. True para utilizar los valores vmax y vmin y false para no utilizarlos
:param vmax: Máximo valor para la escala de la imagen
:param vmin: Mínimo valor para la escala de la imagen
:param shape: Forma de la reconstrucción "thorax" o "circle"
:param mode: Modo en el que fueron tomadas las medidas de vectorDatos ("reconstruccion" o "setup")
:param p: Parámetro para la reconstrucción con JAC, se recomienda que el valor esté entre 0.1 y 0.9
:param screenWidth: Ancho de la pantalla del usuario
:exception ImpetomCError: Devuelve una excepción con el mensaje de error correspondiente
"""
meshDensity, correctShape = cls.__verificarParametrosParaImagen(sclEnable=SclEnable,vmax=vmax,vmin=vmin,shape=shape,mode=mode,p=p,screenWidth=screenWidth)
mesh_obj, el_pos = mesh.create(16, h0=meshDensity, fd=correctShape)
pts = mesh_obj["node"]
tri = mesh_obj["element"]
x, y = pts[:, 0], pts[:, 1]
if shape == "circle":
el_pos = el_pos + 12 # Electrodo 1 en las "6" del reloj
for i in range(4, 16):
el_pos[i] = el_pos[i] - 16
el_dist, step = 1, 1
ex_mat = eit_scan_lines(16, el_dist)
eit = jac.JAC(mesh_obj, el_pos, ex_mat=ex_mat, step=step, perm=1.0, parser="fmmu")
eit.setup(p=p, lamb=cls.lamb, method="kotre")
ds_n = eit.solve(vectorDatos, cls.vHomogeneo, normalize=True)
ds = sim2pts(pts, tri, np.real(ds_n))
fig, ax = plt.subplots(1, 1, constrained_layout=True)
fig.set_size_inches(7, 5)
if (SclEnable):
im = ax.tripcolor(x, y, tri, ds, shading="flat", cmap= cls.cmap, vmax=vmax, vmin=vmin)
else:
im = ax.tripcolor(x, y, tri, ds, shading="flat", cmap=cls.cmap)
if shape == "thorax": # Electrodos Thorax
for i, e in enumerate(el_pos):
if i <= 4:
ax.annotate(str(i + 1), xy=(x[e] - 0.05, y[e] - 0.05), color="w")
elif i <= 7:
ax.annotate(str(i + 1), xy=(x[e] - 0.04, y[e]), color="w")
else:
if i <= 11:
ax.annotate(str(i + 1), xy=(x[e], y[e]), color="w")
else:
ax.annotate(str(i + 1), xy=(x[e], y[e] - 0.05), color="w")
elif shape == "circle": # Electrodos Circle
for i, e in enumerate(el_pos):
if 1 <= i <= 7:
if i <= 3:
ax.annotate(str(i + 1), xy=(x[e] - 0.05, y[e] - 0.05), color="w")
else:
ax.annotate(str(i + 1), xy=(x[e] - 0.05, y[e]), color="w")
elif i >= 14:
ax.annotate(str(i + 1), xy=(x[e] + 0.04, y[e] - 0.05), color="w")
elif i == 0:
ax.annotate(str(i + 1), xy=(x[e], y[e] - 0.05), color="w")
else:
ax.annotate(str(i + 1), xy=(x[e], y[e]), color="w")
ax.axis("off")
cbar = fig.colorbar(im)
cbar_yticks = plt.getp(cbar.ax.axes, 'yticklabels')
plt.setp(cbar_yticks, color='w')
ax.set_aspect("equal")
fig.patch.set_facecolor('black')
fig.savefig('IMG.jpg', dpi=(100/1200)*screenWidth)
plt.close(fig)
@classmethod
def generarImagenTomografica(cls,mode,shape,screenWidth,sclEnable,vmax,vmin,p,frecuenciaDDS):
"""
Permite generar la imagen tomográfica y la guarda en la raíz del proyecto como IMG.jpg
:param mode: Modo en el que debe operar el dispositivo ImpetomC
:param shape: Forma de la reconstrucción "thorax" o "circle"
:param screenWidth: Ancho de la pantalla del usuario
:param SclEnable: Debe ser un booleano. True para utilizar los valores vmax y vmin y false para no utilizarlos
:param vmax: Máximo valor para la escala de la imagen
:param vmin: Mínimo valor para la escala de la imagen
:param p: Parámetro para la reconstrucción con JAC, se recomienda que el valor este en 0.1 y 0.9
:param frecuenciaDDS: Valor de frecuencia a la que debera operar el DDS del dispositvo ImpetomC
:exception ImpetomCElectrodeError: Devuelve una excepción con una mensaje que indica el problema detectado con los electrodos
:exception ImpetomCError: Devuelve una excepción con un mensaje de error por errores distintos al anterior
"""
global voltajesElevados, voltajesElevadosSeguidos, tiraMedidaElevada
if(type(frecuenciaDDS) != str):
raise ImpetomCError("La frecuencia del dds debe ser un String")
if cls.datos[0] == 0: #Verifico si es la primera vez que se ejecuta la aplicación o si el dispositivo se desconectó
cls.__tomarMedidasParaImagen(mode, frecuenciaDDS)
threading.Thread(target=cls.__tomarMedidasParaImagen(mode,frecuenciaDDS)).start()
cls.__obtenerimagen(vectorDatos=cls.datos,screenWidth=screenWidth,SclEnable=sclEnable,vmax=vmax,vmin=vmin,p=p,shape=shape,mode=mode)
if voltajesElevados >= 26 and voltajesElevadosSeguidos >= 13:
if tiraMedidaElevada[0] != 0:
imprimir_ele = []
for dato in tiraMedidaElevada:
if dato != 0:
imprimir_ele.append(int(dato))
raise ImpetomCElectrodeError("Se detectó un falso contacto en al menos un electrodo. Revise los electrodos " + str(imprimir_ele))
elif voltajesElevados > 64:
raise ImpetomCElectrodeError("Se detectaron varios electrodos con tensiones elevadas, se recomienda regular la ganancia del dispositivo.",regularGanancia=True)
elif voltajesElevados <= 13:
raise ImpetomCElectrodeError("Se detectaron varios electrodos con tensiones bajas, se recomienda regular la ganancia del dispositivo.", regularGanancia=True)
elif cls.arduinoComunicationError == None: # verifico que no se haya desconectado el dispositivo durante la adquisición
threading.Thread(target=cls.__tomarMedidasParaImagen(mode,frecuenciaDDS)).start()
tiImagen=time.time()
cls.__obtenerimagen(vectorDatos=cls.datos,screenWidth=screenWidth,SclEnable=sclEnable,vmax=vmax,vmin=vmin,p=p,shape=shape,mode=mode)
tfImagen=time.time()
if voltajesElevados >= 26 and voltajesElevadosSeguidos >= 13:
if tiraMedidaElevada[0] != 0:
imprimir_ele = []
for dato in tiraMedidaElevada:
if dato != 0:
imprimir_ele.append(int(dato))
raise ImpetomCElectrodeError("Se detectó un falso contacto en al menos un electrodo. Revise los electrodos " + str(imprimir_ele))
elif voltajesElevados > 64:
raise ImpetomCElectrodeError(msg="Se detectaron varios electrodos con tensiones elevadas, se recomienda regular la ganancia del dispositivo.",regularGanancia=True)
elif voltajesElevados <= 13:
raise ImpetomCElectrodeError(msg="Se detectaron varios electrodos con tensiones bajas, se recomienda regular la ganancia del dispositivo.",regularGanancia=True)
elif cls.arduinoComunicationError != None:# Si se desconectó el dispositivo durante la adquisición
cls.datos= np.zeros(208) #Reinicio el vector de datos
cls.ard.datos=np.zeros(208)
raise ImpetomCError(cls.arduinoComunicationError)
@classmethod
def __tomarMedidasParaImagen(cls,mode,frecuenciaDDS):
"""
Utiliza al objeto arduino generado para comunicarse con este y obtener las medidas de tension necesarias para realizar la reconstruccion tomográfica del paciente.
Las medidas obtenidos son guardadas en la variable datos
:param mode: Modo de operación para el dispositvo ImpetomC
:param frecuenciaDDS: Frecuencia de operación para el dispositvo DDS
"""
global arduinoComunicationError,voltajesElevados, voltajesElevadosSeguidos,tiraMedidaElevada
cls.ard.cambiarModoDeOperacion(mode)
cls.ard.cambiarFrecuenciaDDS(frecuenciaDDS)
try:
tidatos = time.time()
cls.datos,voltajesElevados,frecuenciaArd,voltajesElevadosSeguidos,tiraMedidaElevada= cls.ard.obtenerMedidas()
archi = Archivos("img.txt")
archi.escribirArchivo(cls.datos)
tfdatos = time.time()
cls.arduinoComunicationError = None
except ImpetomCError as e:# Escribo el msj de error en arduinoComunicationError
cls.arduinoComunicationError = str(e)
@classmethod
def __verificarParametrosParaHistoriaClinica(cls,pacientName,pacientCi,p,birthDate,sex,medida,medidaZ,shape,mode):
"""
Permite verificar que los parámetros de entrada para la reconstrucción tomográfica sean correctos
:parameter pacientName: Tipo string, no debe estar vacío
:param pacientCi: Tipo String, no debe estar vacío y ser una cadena de números
:param p: Tipo float, debe ser mayor que 0
:param birthDate: Debe ser un String válido a convertir a fecha en el formato dd-mm-yyyy
:param sex: Tipo String, debe ser "M", "F" o "Otro"
:param medida: Medida de la circunferencia del thorax del paciente
:param medidaZ: Altura aproximada donde está colocado el cinturón de electrodos. El cero se marca desde la apófisis del esternón
:param shape: Forma de la reconstrucción "thorax" o "circle"
:exception ImpetomCError: Devuelve una excepción con el mensaje de error correspondiente
"""
if(type(pacientName) != str or len(pacientName) <=1 ):
raise ImpetomCError("El nombre debe ser un string y no debe estar vacío")
if(type(pacientCi) != str or not re.match('^[0-9]*$',pacientCi)):
raise ImpetomCError("La CI solo puede contener números")
if (type(p)!=float or p <= 0):
raise ImpetomCError("p debe ser un float y mayor que 0")
if (type(medida) != str or not re.match('^[0-9]*$', medida)):
if type(medida) == int and int(medida) < 0 :# explcitamente me fijo si el numero es positivo
raise ImpetomCError("La medida de la circuferencia del tórax solo puede contener números positivos")
elif type(medida )!= int:
raise ImpetomCError("La medida de la circuferencia del tórax solo puede contener números")
if (type(medidaZ) != str or not re.match('^[0-9]*$', medidaZ.strip("-"))):
if type(medidaZ) != int:
raise ImpetomCError("La medida de la altura de colocación del cinturón debe ser un número entero")
mode=mode.lower()
if type(mode) != str:
raise ImpetomCError("El atributo Mode debe ser un string")
elif mode != "reconstruccion" and mode != "setup":
raise ImpetomCError("Modo de reconstrucción inválido. Debe ser \"Reconstruccion\" o \"Setup\"")
shape=shape.lower()
if shape != "thorax" and shape != "circle":
raise ImpetomCError("Forma para la reconstrucción inválida, debe ser \"Thorax\" o \"Circle\"")
try:
patBirthDate = datetime.strptime(birthDate, "%Y%m%d")
except ValueError:
raise ImpetomCError("La fecha de nacimiento no es válida")
sex=sex.lower()
if (sex != "m" and sex != "f" and sex!="otro"):
raise ImpetomCError ("El sexo del paciente debe ser M, F u Otro")
@classmethod
def guardarImagenEnHistoriaClinica(cls,pacientName,pacientCi,p,pacBirthDate,pacSex, medida, shape, medidaZ,mode):
"""
Permite el guardado de las imágenes obtenidas en la carpeta correspondiente a la historia clónica del paciente, solamente cuando el dispositivo se encuentra en modo Reconstrucción
:param pacientName: Nombre del paciente
:param pacientCi: Ci del paciente
:param p: valor de p utilizado durante la reconstrucción
:param birthDate: Fecha de nacimiento del paciente
:param pacSex: Sexo biologico del paciente
:param medida: Medida de la circunferencia del thorax del paciente
:param shape: Forma de la reconstrucción "thorax" o "circle"
:param medidaZ: Altura aproximada donde está colocado el cinturón de electrodos. El cero se marca desde la apófisis del esternón
:param mode: Modo de trabajo del dispositivo. Puede ser "Reconstruccion" o "Setup". Solo guarda imágenes con formato Dicom en caso de estar en modo "Reconstruccion"
"""
cls.__verificarParametrosParaHistoriaClinica(pacientName,pacientCi,p,pacBirthDate,pacSex,medida,medidaZ,shape,mode)
medida=int(medida)
medidaZ=int(medidaZ)
dir_dcm = "\\Pacients_Data\\" + pacientName.strip('\n') + "_" + pacientCi.strip('\n') + "\\" + cls.today
try: # intento crear la capeta donde se guardaran las imágenes tomográficas del paciente
os.makedirs(cls.rootDir + dir_dcm)
except OSError: # en caso de que el directorio exista no importa
pass
finally: # guardo la imagen en la carpeta del paciente con un nombre que depende de la hora, minutos y segundos
mode = mode.lower()
if mode == "reconstruccion":
cls.imgDicom.generarImagenDicom(pacientName, pacientCi, pacSex, pacBirthDate, p, cls.lamb, cls.today, medida, shape, medidaZ)
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment