enero 21, 2006

Mi visor volumétrico de imágenes médicas

Este proyecto es un visor volumétrico de imágenes médicas, realmente a este proyecto no se le ha dedicado el tiempo que se debiera porque han habido otros proyectos que han ido surgiendo y han hecho que este se vea pospuesto y tambien el tiempo de desarrollo hasta ahora ha sido muy corto (Solo me llevó alrededor de un mes y medio desarrollarlo). De lo que se trata es de que recibe como entrada una serie de imágenes DICOM de un estudio de algun paciente y este programa construye un modelo 3D de lo que esas imágenes y ese estudio representan. Originalmente fué desarrollado sobre Delphi 6, pero ahora compila sin problemas sobre Delphi 2006, usa como motor gráfico a OpenGL y como motor de BD a Firebird.

Esta dividido en dos partes, la parte de analisis bidimensional de las imágenes, en donde se puede de una manera muy simple y práctica adecuar los niveles de brillo y contraste en los colores de la imágen, efectuar mediciones milimétricas dentro de la imagen al tamaño real del objeto del que fue tomada, cambiar los tonos y los niveles, efectuar acercamientos a ciertas areas (Zoom), etc...

Y la parte de vision volumétrica, la cual es la que permite hacer la exploración 3D de los cuerpos que se representan en dichas imágenes médicas, pudiendose aplicar, planos de corte, transparencias, eliminacion de cuerpos basado en la densidad mostrada en las imágenes, rotaciones, traslaciones, etc...

Aquí algunos screenshots de la aplicación. Pueden dar click en las imágenes para verlas en tamaño completo.


La vista 2D permite efectuar mediciones milimétricas sobre el modelo

La vista 3D del estudio anterior; es una resonancia magnetica de una cabeza humana, aqui la vemos de perfil, pero puede ser rotada en tiempo real en cualquier sentido.

El mismo modelo anterior aplicandole 2 cortes en 2 ejes; aqui se puede ver el interior del craneo, los cortes se aplican dinamicamente a gusto del usuario.

Un pie humano con un factor de transparencia alta, lo cual permite ver los huesos a travez de la piel del sujeto.

El mismo pie variando los niveles del umbral visible con lo cual se permite descartar la piel del modelo y mostrar solo los huesos. Este proceso actualmente trabaja sobre las densidades de los objetos, permitiendo hacer la descartación en el modelo final basandose solo en que tan denso aparece el objeto en la imágen, este proceso se piensa modificar para hacerlo mucho mas selectivo y significativo.


DICOM es un estandar en la medicina en cuanto a la forma en como se almacenan y tratan las imágenes médicas, y practicamente todos los equipos modernos de radiografía, tomografía, ultrasonido o resonancia magnética trabajan con imágenes en formato DICOM, por lo que ahora es muy común que al paciente se le entregue ademas o en vez de las tradicionales placas de sus estudios, un CD con las imágenes en formato DICOM, con lo cual este programa puede construir todo el modelo tridimensional sin problema alguno y mostrarselo al paciente en el propio consultorio del médico.

Generalmente esto es posible solo con equipos y software muy caro y el ambito esta restringido a los radiologos que cuentan con este equipo, pero ahora con esta aplicación, la idea es que cualquier médico en su consultorio pueda obtener la imágen volumetrica de los analisis que reciben sus pacientes. y asi incluso mostrarle al paciente el modelo real en 3D de su propio cuerpo en el mismo consultorio, tal vez para mostrar la evolución de alguna fractura o algún otro padecimiento.

Otra aplicación que se me ocurrió es en el ámbito académico, con esta aplicación los alumnos podrán obtener una mejor idea de lo que significan las imágenes médicas así como poder hacer exploraciones mas a detalle de sus estudios pues las posibilidades son infinitas cuando se tiene una vision tridimensional de las cosas a verse forzado a hacer un análisis puramente en 2D.

En este momento nos encontramos al final de la etapa de desarrollo de este producto, pero aún tenemos espectativas que se pueden contemplar para este, tales como el reconocimiento de organos automático dentro del volumen para poder hacer discriminaciones basadas no solo en la densidad de lo que se ve sino en las texturas de los tejidos, etc... asi como la construccion biplanar de modelos para simplificar y abaratar aun mas el proceso para un radiologo de obtener el estudio.

En fin, esas son solo algunas cosas que se me han ocurrido, espero que a partir de esto se me ocurran mas. Y estoy ávido tambien de que quienes lean esto me puedan dar más ideas de todo tipo, de programación, estilo, cientificas, comerciales, etc. Así que pueden sentirse con entera libertad de publicar sus comentarios al respecto.

enero 18, 2006

De vacaciones pero trabajando...

Quiero agradecer a todos y cada uno de los que han hecho el favor de dejar algún comentario en los artículos que he ido publicando en este espacio, es muy reconfortante saber que no se predica en el desierto y que a mas de uno le pueden llegar a interesar las cosas que a veces traigo en la cabeza.

De momento me encuentro de vacaciones, disfrutando del jarochismo en mi estado natal de Veracru' señores, ya extrañaba las arpas, jaranas y marimbas tipicas de esta región, ciertamente no hay lugar como el hogar.

Pero pues hasta en vacaciones no puedo dejar de programar, he aprovechado este viaje para saludar a viejos amigos de la Maestría de Inteligencia Artificial y a la vez pedirles consejo y asesoría para algunos proyectos que traigo entre manos, espero que el tiempo que me quede por aca me alcance para poder despejar el mar de dudas que tengo sobre algunos escabrozos temas como Redes Bayesianas para la minería de datos y Redes Neuronales para un proyecto de reconocimiento de patrones.

También estoy aprovechando estos dias de desasosiego para terminar de estudiar algunas conferencias de la última DevCon son muchas conferencias y con lo agitados que suelen ser los dias de trabajo no hay mucho tiempo para estudiar los videos y presentaciones que estan disponibles para su descarga. Hasta ahora me han parecido muy interesantes las conferencias de Marco Cantú entre otras.

Tambien cuando deja uno de estar en linea por un tiempo quedan muchas cosas por revisar en foros y blog's para ponerse al día, por eso me pareció muy interesante este plugin de búsqueda para Firefox que permite buscar en los grupos de noticias de Borland, uno de los mas grandes y mejores repositorios de respuestas a miles de preguntas sobre el ambiente de la programación.

Bueno por el momento así estan las cosas, ahora permitanme que tengo un asunto pendiente que arreglar con el XBox de mi hermano y el Halo 2

enero 04, 2006

Por fin sobrecarga de operadores en Delphi

Dandole unos cuantos teclazos al BDS 2006 estuve probando la característica de la que tanto se presumia antes de liberarse que era la inclusión de sobrecarga de operadores. Aunque yo esperaba que fuera a nivel de clases parece que nos tendremos que conformar por ahora con su implementación en estructuras de tipo record.

Por ejemplo, para implementar como una estructura el manejo de números fraccionarios usabamos algo como esto:
 TFraccionario = record
numerador, denominador: integer;
end;

Ahora tenemos la posibilidad de agregar operadores que se encarguen de la transferencia de información asi como de las operaciones entre las estructuras, por ejemplo agregando el operador implicit :
 TFraccionario = record
numerador, denominador: integer;
class operator implicit( Value: TFraccionario): string;
end;

y su implementación:
 class operator TFraccionario.implicit(Value: 
TFraccionario): string;
begin
Result := IntToStr(Value.Numerador) + '/'+
IntToStr(Value.Denominador);
end;

Ahora podemos asignar una variable de tipo TFraccionario a una simple y sencilla cadena asi:
var
R1 : TFraccionario;
S: string;
begin
R1.Numerador := 2;
R1.Denominador := 6;
S := R1; //Aqui S valdria literalmente "2/6"

Y también a la inversa, podemos definir un comportamiento de operador para cuando queremos asignar una cadena a una variable de tipo TFraccionario
 class operator implicit( Value: string) : TFraccionario;

Y lo implementamos haciendo un parseo muy simple para la cadena, al fin de cuentas nadamas estamos ilustrando un ejemplo, no es la idea poner un parser completo sino mostrar el funcionamiento de los operadores
class operator TFraccionario.implicit(Value: string): 
TFraccionario;
var p : integer;
begin
Result.numerador := 0;
Result.denominador := 0;
p := Pos('/',Value);
if P = 0 then exit;
Result.numerador := StrToIntDef(Copy(Value,1,P-1),0);
Result.denominador := StrToIntDef(Copy(Value,P+1,
Length(Value)),0);
end;

Y una vez hecho esto ya podemos asignar cadenas a variables de tipo TFraccionario, de esta manera:
 var
R1 : TFraccionario;
begin
R1 := '2/6';

Pero, ¿como sumariamos 2 números fraccionarios usando directamente el operador +?, pues bien para eso pondríamos una definición de operador llamada add dentro de la misma estructura del registro, ahora la declaración del tipo completo sería:
 TFraccionario = record
numerador, denominador: integer;
class operator implicit( Value: TFraccionario): string;
class operator implicit( Value: string) : TFraccionario;
class operator add( v1, v2 : TFraccionario): TFraccionario;
end;

Y la implementación del operador (si mis maestros de la primaria no me engañaron) sería algo como esto:
class operator TFraccionario.add(v1, v2: TFraccionario): 
TFraccionario;
begin
if (V1.denominador = V2.denominador) then
begin
Result.denominador := V1.denominador;
Result.numerador := V1.numerador + V2.numerador;
end
else
begin
Result.numerador := (V1.numerador * V2.denominador) +
(V1.denominador * V2.numerador );
Result.denominador := V1.denominador * V2.denominador;
end;
end;

Si, si, ya se que tambien se podría simplificar el número a su mínima expresión una vez efectuada la suma, pero prefiero dejarlo así por ahora para que no se complique en demasía el algoritmo y podamos admirar el trabajo con los operadores en vez de eso.

Ahora a poner a prueba el trabajo que hicimos, y a hacer también porque no algunos experimentos, primero veamos como podemos inicializar dos números fraccionarios a partir de un par de cadenas y sumemos ambos números y mostremos el resultado de la operación también en una cadena, para hacer uso de todos los operadores que hicimos, para eso ponemos un TButton en el formulario y le codificamos esto en el evento OnClick:
procedure TForm2.Button1Click(Sender: TObject);
var
R1,R2 : TFraccionario;
begin
R1 := '2/6';
R2 := '3/6';
Caption := R1 + R2; //Esto devuelve "5/6" en el caption
end;

¡Bastante práctico!, ¿no es cierto?... ¿pero ahora si en vez de que se aplicara la operación entre ambos números fraccionarios lo que nos interesara fuera obtener la concatenación de sus representaciones como cadenas???, bien en ese caso lo que hay que hacer es tratar a ambas variables como cadenas para que el operador que nosotros definimos pierda su efecto y en vez de esto se aplique el operador predeterminado para el trabajo con cadenas, en este caso la concatenación:
Caption := string(R1) + string(R2); //Esto devuelve "2/63/6"

¡Fantástico!, y ¿podríamos hacer combinaciones?? es decir,¿ aplicar el operador con secuencias de operandos de diferentes tipos??... Si, claro y también esto ofrece un gran poder, solo hay que ver esto:
Caption := R1 + R2 + '1/6'; //Esto devuelve "6/6" !!!

¡Estupendo!!...de la misma manera podriamos definir muchos mas operadores para el trabajo con números fraccionarios, por ejemplo:
  • Add +

  • Substract -

  • Multiply *

  • Divide /

  • Negative -

  • Equal =

  • NotEqual <>

  • LessThan <

  • LessThanOrEqual <=

  • GreaterThan >

  • GreaterThanOrEqual >=

Con lo que hemos visto hasta ahora es fácil imaginarse como sería la implementación del resto de los operadores, suerte y a practicar esta nueva funcionalidad.