paint-brush
¿Se pueden abrir datos médicos (RM, tomografía computarizada, rayos X) en Python y encontrar tumores con IA? Tal vezpor@thebojda
3,808 lecturas
3,808 lecturas

¿Se pueden abrir datos médicos (RM, tomografía computarizada, rayos X) en Python y encontrar tumores con IA? Tal vez

por Laszlo Fazekas8m2024/04/23
Read on Terminal Reader

Demasiado Largo; Para Leer

Cuando un programador oye hablar del procesamiento de datos médicos, podría pensar que es algo serio, algo que sólo las universidades y los institutos de investigación pueden manejar (al menos, eso es lo que pensé). Como puede ver, estamos hablando de imágenes simples en escala de grises, que son muy fáciles de procesar e ideales para cosas como el procesamiento de redes neuronales.
featured image - ¿Se pueden abrir datos médicos (RM, tomografía computarizada, rayos X) en Python y encontrar tumores con IA? Tal vez
Laszlo Fazekas HackerNoon profile picture
0-item


Hace unos días me hicieron una resonancia magnética. Me metieron en un tubo grande y, durante 15 minutos, la máquina a mi alrededor zumbó, zumbó y hizo clic. Al final del examen, recibí un CD que contenía los datos. ¿Qué hace un buen desarrollador en tal situación? Por supuesto, tan pronto como llegan a casa, comienzan a examinar los datos y a pensar en cómo podrían extraerlos usando Python .


El CD contenía un montón de archivos DLL, un EXE y algunos archivos adicionales para un visor de Windows, que obviamente son inútiles en Linux. El quid estaba en una carpeta llamada DICOM. Contenía una serie de archivos sin extensión en ocho carpetas. ChatGPT me ayudó a descubrir que DICOM es un formato estándar que normalmente se usa para almacenar imágenes de resonancia magnética, tomografía computarizada y rayos X. Básicamente, es un formato de empaquetado simple que contiene imágenes en escala de grises junto con algunos metadatos.


El siguiente código le permite exportar fácilmente los archivos en formato PNG:


 import os import pydicom from PIL import Image import sys def save_image(pixel_array, output_path): if pixel_array.dtype != 'uint8': pixel_array = ((pixel_array - pixel_array.min()) / (pixel_array.max() - pixel_array.min()) * 255).astype('uint8') img = Image.fromarray(pixel_array) img.save(output_path) print(f"Saved image to {output_path}") def process_dicom_directory(dicomdir_path, save_directory): if not os.path.exists(save_directory): os.makedirs(save_directory) dicomdir = pydicom.dcmread(dicomdir_path) for patient_record in dicomdir.patient_records: if 'PatientName' in patient_record: print(f"Patient: {patient_record.PatientName}") for study_record in patient_record.children: if 'StudyDate' in study_record: print(f"Study: {study_record.StudyDate}") for series_record in study_record.children: for image_record in series_record.children: if 'ReferencedFileID' in image_record: file_id = list(map(str, image_record.ReferencedFileID)) file_id_path = os.path.join(*file_id) print(f"Image: {file_id_path}") image_path = os.path.join(os.path.dirname(dicomdir_path), file_id_path) modified_filename = '_'.join(file_id) + '.png' image_save_path = os.path.join(save_directory, modified_filename) try: img_data = pydicom.dcmread(image_path) print("DICOM image loaded successfully.") save_image(img_data.pixel_array, image_save_path) except FileNotFoundError as e: print(f"Failed to read DICOM file at {image_path}: {e}") except Exception as e: print(f"An error occurred: {e}") if __name__ == "__main__": if len(sys.argv) < 3: print("Usage: python dicomsave.py <path_to_DICOMDIR> <path_to_save_images>") sys.exit(1) dicomdir_path = sys.argv[1] save_directory = sys.argv[2] process_dicom_directory(dicomdir_path, save_directory)


El código espera dos parámetros. La primera es la ruta al archivo DICOMDIR y la segunda es un directorio donde se guardarán las imágenes en formato PNG.


Para leer los archivos DICOM, utilizamos la biblioteca Pydicom . La estructura se carga mediante la función pydicom.dcmread , de la cual se pueden extraer metadatos (como el nombre del paciente) y estudios que contienen las imágenes. Los datos de la imagen también se pueden leer con dcmread . Se puede acceder a los datos sin procesar a través del campo pixelarray , que la función save_image convierte a PNG. No es demasiado complicado.


El resultado son imágenes en escala de grises de unos pocos cuadrados (normalmente 512x512), que se pueden utilizar fácilmente más adelante.


¿Por qué pensé que era importante escribir esta breve historia sobre mi aventura utilizando el formato DICOM? Cuando un programador oye hablar del procesamiento de datos médicos, podría pensar que es algo serio, algo que sólo las universidades y los institutos de investigación pueden manejar (al menos, eso es lo que pensé). Como puede ver, estamos hablando de imágenes simples en escala de grises, que son muy fáciles de procesar e ideales para cosas como el procesamiento de redes neuronales.


Considere las posibilidades que esto abre. Para algo así como un detector de tumores simple, todo lo que necesita es una red convolucional relativamente sencilla y una cantidad suficiente de muestras. En realidad, la tarea no es muy diferente de reconocer perros o gatos en imágenes.


Para ilustrar cuán cierto es esto, permítanme brindarles un ejemplo. Después de una breve búsqueda en Kaggle, encontré un cuaderno donde se clasifican los tumores cerebrales según datos de resonancia magnética (imágenes en escala de grises). El conjunto de datos clasifica las imágenes en cuatro grupos: tres tipos de tumores cerebrales y una cuarta categoría con imágenes de cerebros sanos. La arquitectura de la red es la siguiente:


 ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━┓ ┃ Layer (type) ┃ Output Shape ┃ Param # ┃ ┡━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━┩ │ conv2d (Conv2D) │ (None, 164, 164, 64) │ 1,664 │ ├─────────────────────────────────┼────────────────────────┼───────────────┤ │ max_pooling2d (MaxPooling2D) │ (None, 54, 54, 64) │ 0 │ ├─────────────────────────────────┼────────────────────────┼───────────────┤ │ conv2d_1 (Conv2D) │ (None, 50, 50, 64) │ 102,464 │ ├─────────────────────────────────┼────────────────────────┼───────────────┤ │ max_pooling2d_1 (MaxPooling2D) │ (None, 16, 16, 64) │ 0 │ ├─────────────────────────────────┼────────────────────────┼───────────────┤ │ conv2d_2 (Conv2D) │ (None, 13, 13, 128) │ 131,200 │ ├─────────────────────────────────┼────────────────────────┼───────────────┤ │ max_pooling2d_2 (MaxPooling2D) │ (None, 6, 6, 128) │ 0 │ ├─────────────────────────────────┼────────────────────────┼───────────────┤ │ conv2d_3 (Conv2D) │ (None, 3, 3, 128) │ 262,272 │ ├─────────────────────────────────┼────────────────────────┼───────────────┤ │ max_pooling2d_3 (MaxPooling2D) │ (None, 1, 1, 128) │ 0 │ ├─────────────────────────────────┼────────────────────────┼───────────────┤ │ flatten (Flatten) │ (None, 128) │ 0 │ ├─────────────────────────────────┼────────────────────────┼───────────────┤ │ dense (Dense) │ (None, 512) │ 66,048 │ ├─────────────────────────────────┼────────────────────────┼───────────────┤ │ dense_1 (Dense) │ (None, 4) │ 2,052 │ └─────────────────────────────────┴────────────────────────┴───────────────┘


Como puede ver, las primeras capas constan de un sándwich de convolución y agrupación máxima, que extrae patrones de tumores, seguido de dos capas densas que realizan la clasificación. Esta es exactamente la misma arquitectura utilizada en el código de muestra CNN de TensorFlow que utiliza el conjunto de datos CIFAR-10. Lo recuerdo bien, ya que fue una de las primeras redes neuronales que encontré. El conjunto de datos CIFAR-10 contiene 60.000 imágenes categorizadas en diez clases, incluidas dos clases para perros y gatos, como mencioné anteriormente. La arquitectura de la red se ve así:


 Model: “sequential” _________________________________________________________________ Layer (type) Output Shape Param # ================================================================= conv2d (Conv2D) (None, 30, 30, 32) 896 _________________________________________________________________ max_pooling2d (MaxPooling2D) (None, 15, 15, 32) 0 _________________________________________________________________ conv2d_1 (Conv2D) (None, 13, 13, 64) 18496 _________________________________________________________________ max_pooling2d_1 (MaxPooling2 (None, 6, 6, 64) 0 _________________________________________________________________ conv2d_2 (Conv2D) (None, 4, 4, 64) 36928 _________________________________________________________________ flatten (Flatten) (None, 1024) 0 _________________________________________________________________ dense (Dense) (None, 64) 65600 _________________________________________________________________ dense_1 (Dense) (None, 10) 650 ================================================================= Total params: 122,570 Trainable params: 122,570 Non-trainable params: 0 _________________________________________________________________


Está claro que estamos ante la misma arquitectura, sólo que con parámetros ligeramente diferentes, por lo que reconocer tumores cerebrales en imágenes de resonancia magnética no es mucho más difícil que identificar perros o gatos en imágenes. Además, ¡el cuaderno Kaggle mencionado es capaz de clasificar tumores con un 99% de efectividad!


Sólo quiero señalar lo sencillo que puede ser extraer y procesar datos médicos en ciertos casos y cómo problemas que parecen complejos, como la detección de tumores cerebrales, a menudo pueden abordarse de manera bastante efectiva con soluciones simples y tradicionales.


Por lo tanto, animo a todos a que no rehuyan los datos médicos. Como puede ver, estamos tratando con formatos relativamente simples y utilizando tecnologías relativamente simples y bien establecidas (redes convolucionales, transformadores de visión, etc.), aquí se puede lograr un impacto significativo. Si te unes a un proyecto de atención médica de código abierto en tu tiempo libre solo como pasatiempo y puedes realizar incluso una ligera mejora en la red neuronal utilizada, ¡podrías salvar vidas!