Para casi todos los proyectos de colaboracion, los procedimientos de ENIGMA se han tomado como el standard a seguir. Asi que vamos a intentar integrar estos procedimientos en el pipeline, sobre todo en el proyecto MRIFACE, que es el mas extenso y dificil de manipular. A partir de aqui deberia ser simple aplicar el procedimeinto al resto de proyectos.
Nota: El procedimiento es bastante básico y tiene dos evaluaciones simples: Good, Fail. Los minor errors pasaran como buenos, los major errors como malos, en general. No obstante, el objetivo es guardar todas las imagenes en XNAT como recurso del experimento, asi que si es necesario basta bajar las imagenes y volver a revisar el QC. Esto es muchisimo mas sencillo y rapido que volver a ejecutar todo el procedimiento para un puñado de MRIs cuyos resultados se quieran compartir a algun proyecto.
El paso previo a ejecutar el QC es obtener los resultados del procesamiento y ordenarlos en un directorio. Para poder evaluar paulatinamente los sujetos, hay que permitir dos cosas,
elegir el directorio donde vamos a guardar los resultados
suministrar una lista de los sujetos a evaluar
Esto se hace tomando estos valores desde la linea de comandos (opcional),
Luego, de la posible lista de sujetos (o del proyecto completo), debo obtener los experimentos correspondientes. Cada experimento procesado debe tener el directorio completo de FS compactado y asociado como recurso. Este lo bajamos y lo descomprimimos en su sitio.
#!/usr/bin/perl# Copyright 2023 O. Sotolongo <asqwerty@gmail.com># This program is free software; you can redistribute it and/or modify# it under the terms of the GNU General Public License as published by# the Free Software Foundation; either version 2 of the License, or# (at your option) any later version.## This program is distributed in the hope that it will be useful,# but WITHOUT ANY WARRANTY; without even the implied warranty of# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the# GNU General Public License for more details.# Main goal here is to get all the FS out put data from XNAT.# Basically, for each target subject, get experiments, download the # gziped tar file with all the data and explode in a separated directory.## And from there you can make your favorite FS QC procedure#use strict;use warnings;use Cwd qw(cwd);use NEURO4 qw(load_project print_help populate check_or_make getLoggingTime);use XNATACE qw(xget_session xget_mri xlist_res xget_res_file xget_subjects);use File::Tempqw(:mktemp tempdir);use File::Find::Rule;use File::Basenameqw(basename);use Data::Dumpqw(dump);my$prj;my$xprj;my$ifile;my$efile;my$wdir= cwd;my$odir;@ARGV=("-h")unless@ARGV;while(@ARGVand$ARGV[0]=~/^-/){$_=shift;lastif/^--$/;if(/^-i/){$ifile=shift;chomp($ifile);}if(/^-o/){$odir=shift;chomp($odir);}if(/^-h/){ print_help $ENV{'PIPEDIR'}.'/doc/reviewqc.hlp';exit;}if(/^-x/){$xprj=shift;chomp($xprj);}#nombre del proyecto en XNATif(/^-p/){$prj=shift;chomp($prj);}#nombre local del proyecto}# Mira, hay que meter el proyecto de XNAT con alguno de los dos switchif($prjandnot$xprj){my%pdata= load_project($prj);$xprj=$pdata{'XNAME'};}# O te vas a tomar por culodie"Should supply XNAT project name or define it at local project config!\n"unless$xprj;# y entonces, el file de input es el que tu digas o los tendras que mirar todos# Estos son los experimentos a los que vamos a revisar o hacer el QC. # Puedes tomar la lista completa y reducirla a lo que quieres o simplemente # tomar la lista as ismy$tmp_dir=$ENV{TMPDIR};my%xconf= xget_session();my@pollos;my%epollos;$efile=$wdir.'/'.$xprj.'_experiment.list';my$selection='*/mri/{orig,orig_nu,aparc+aseg}.mgz */label/*.aparc.annot */surf/*.pial* */surf/*.orig */surf/*.white */mri/transforms/talairach.xfm */stats/*.aparc.stats */stats/aseg.stats';#Añadir logging!!!!!my$odebug=$wdir.'/.debug_'.$xprj.'_data_'.getLoggingTime().'.out';openSTDOUT,">$odebug"ordie"Can't redirect output";openSTDERR,">&STDOUT"ordie"Can't redirect errors";if($ifileand-f $ifile){$efile=$wdir.'/'.$xprj.'_experiment_custom.list';open IDF,"<$ifile";@pollos=<IDF>;chomp@pollos;close IDF;open TDF,">$efile";foreachmy$pollo(@pollos){$epollos{$pollo}=[ xget_mri($xconf{'HOST'},$xconf{'JSESSION'},$xprj,$pollo)];foreachmy$pexp(@{$epollos{$pollo}}){print TDF "$pexp\n";}}close TDF;}unless($odirand-d $odir){# Si me das un directorio de input y este existe entonces he de avisar que existe y salir# Dejar que el luser arregle a su manera el problema $odir=$wdir.'/'.$xprj.'_fsresults';if(-d $odir){# Si el default existe entonces aviso y salgodie"Output directory $odir already exists, move it or give me a new directory output";}mkdir$odir;#y ejecuto la preparacionif($ifileand-f $ifile){# si me has dado una lista de los que quieres procesar# me limito a preparar esosmy$tsdir= tempdir(TEMPLATE =>$tmp_dir.'/tars_data.XXXXX', CLEANUP =>1);foreachmy$pollo(@pollos){foreachmy$pexp(@{$epollos{$pollo}}){my$otfile=$tsdir.'/'.$pexp.'.tar.gz';my%fs_files= xlist_res($xconf{'HOST'},$xconf{'JSESSION'},$xprj,$pexp,'FS');foreachmy$fsfile(sortkeys%fs_files){if($fsfile=~/.*\.tar\.gz$/){
xget_res_file($xconf{'HOST'},$xconf{'JSESSION'},$xprj,$pexp,'FS',$fsfile,$otfile);}}my$otdir=$odir.'/'.$pexp;mkdir$otdir;my$pre='tar xzf '.$otfile.' --strip-components=1 -C '.$otdir.' '.$selection;print"$pre\n";system($pre);unlink$otfile;}}unlink$tsdir;}else{#pero si no hay lista los bajo todosmy$tsdir= tempdir(TEMPLATE =>$tmp_dir.'/tars_data.XXXXX', CLEANUP =>1);my%allchicks= xget_subjects($xconf{'HOST'},$xconf{'JSESSION'},$xprj);foreachmy$pollo(sortkeys%allchicks){$epollos{$pollo}=[ xget_mri($xconf{'HOST'},$xconf{'JSESSION'},$xprj,$pollo)];foreachmy$pexp(@{$epollos{$pollo}}){my$otfile=$tsdir.'/'.$pexp.'.tar.gz';my%fs_files= xlist_res($xconf{'HOST'},$xconf{'JSESSION'},$pexp,'FS');foreachmy$fsfile(sortkeys%fs_files){if($fsfile=~/.*\.tar\.gz$/){
xget_res_file($xconf{'HOST'},$xconf{'JSESSION'},$pexp,'FS',$fsfile,$otfile);}}if(-e $otfile){my$otdir=$odir.'/'.$pexp;mkdir$otdir;my$pre='tar xzf '.$otfile.' --strip-components=1 -C '.$otdir.' '.$selection;print"$pre\n";system($pre);unlink$otfile;}else{print"No FS data for $pexp ($pollo)!\n";}}}unlink$tsdir;}}else{die"This output directory already exists, choose another one!";}
Las opciones del script get_fs_data.pl son,
-p prj : nombre local del proyecto (OBLIGATORIO)
-x xprj : nombre en XNAT del proyecto (Opcional si el proyecto local esta configurado correctamente)
-o output_dir : directorio de output (Opcional)
-i subjects.list : archivo con la lista de sujetos a bajar (Opcional)
Basicamente es usar el script de ENIGMA para sacar SurfAVG y ThicAVG de los resultados individuales y ponerlos en una tabla, pero voy poner esto un poco mas amigable y voy a añadir un par de lineas al inicio del script para poder ejecutarlo desde cualquier sitio y me guarde los CSV en el directorio de trabajo.
[osotolongo@brick03 mri_face]$ R --no-save--slave< outliers.R > outliers.log
que nos da un archivo bastante largo con los vaores que no encajan dentro de una distribucion normal,
[osotolongo@brick03 mri_face]$ head outliers.log
Please check the following subjects closely using the QC methods provided in Step 2 of the cortical protocols.
Subject XNAT05_E00042 has THICKNESS values too HIGH for the following structures: L_cuneus_thickavg
Subject XNAT05_E00042 has THICKNESS values too HIGH for the following structures: R_cuneus_thickavg
Subject XNAT05_E00042 has THICKNESS values too HIGH for the following structures: R_postcentral_thickavg
Subject XNAT05_E00047 has THICKNESS values too LOW for the following structures: L_caudalanteriorcingulate_thickavg
Subject XNAT05_E00047 has THICKNESS values too LOW for the following structures: L_posteriorcingulate_thickavg
Subject XNAT05_E00047 has THICKNESS values too LOW for the following structures: L_rostralanteriorcingulate_thickavg
Subject XNAT05_E00047 has THICKNESS values too LOW for the following structures: R_posteriorcingulate_thickavg
Subject XNAT05_E00050 has THICKNESS values too HIGH for the following structures: L_superiorparietal_thickavg
Subject XNAT05_E00051 has THICKNESS values too LOW for the following structures: R_insula_thickavg
Y estos son los sujetos que tenemos que mirar. Aquellos que estan fuera de esta lista tienen todas las medidas dentro de la norma y no deberian tener problemas con la segmentacion.
So, lo primero que voy a hacer es marcar estos como OK poniendo este archivo como un resource en XNAT,
#!/usr/bin/perl# Copyright 2023 O. Sotolongo <asqwerty@gmail.com># This program is free software; you can redistribute it and/or modify# it under the terms of the GNU General Public License as published by# the Free Software Foundation; either version 2 of the License, or# (at your option) any later version.## This program is distributed in the hope that it will be useful,# but WITHOUT ANY WARRANTY; without even the implied warranty of# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the# GNU General Public License for more details.# Este es un script de apoyo para el QC de FS. Si ya tengo los outliers,# puedo marcar los que no lo son como buenos en XNATuse strict;use warnings;use NEURO4 qw(load_project);use XNATACE qw(xget_session xcreate_res xput_res_data);my$prj;my$xprj;my$idir;my$ifile="outliers.log";@ARGV=("-h")unless@ARGV;while(@ARGVand$ARGV[0]=~/^-/){$_=shift;lastif/^--$/;if(/^-o/){$ifile=shift;chomp($ifile);}#archivo de outliersif(/^-d/){$idir=shift;chomp($idir);}# directorio con los datos de FSif(/^-x/){$xprj=shift;chomp($xprj);}#nombre del proyecto en XNATif(/^-p/){$prj=shift;chomp($prj);}#nombre local del proyecto}if($prjandnot$xprj){my%pdata= load_project($prj);$xprj=$pdata{'XNAME'};}$idir=$prj.'_fsresults'unless$idir;die"Should supply XNAT project name or define it at local project config!\n"unless$xprj;my%xconf= xget_session();my%good_boy=("rating"=>"good","notes"=>"None");# put outliers file into array buffer open IDF,$ifileordie"No such file as $ifile";chomp(my@outliers=<IDF>);close IDF;# put FS dir into arrayopendir IDD,$idirordie"Couldn't open dir $idir";my@experiments=readdir IDD;close IDD;# grep subjects into outliers array foreachmy$experiment(@experiments){unless(grep/$experiment/,@outliers){
xcreate_res($xconf{'HOST'},$xconf{'JSESSION'},$experiment,'fsqc');
xput_res_data($xconf{'HOST'},$xconf{'JSESSION'},$experiment,'fsqc','rating.json',\%good_boy);print"$experiment\n";}}
y podemos subir rapidamente un archivo json compatible con los QC antiguos.
External Surface Method
VM
Ahora vamos a ejecutar el primer metodo de QC para producir las imagenes de la segmentacion cortical. Este metodo necesita ejecutar los visores de Freesurfer (con GLX), que no funcionan por VNC directamente al server. Asi qeu vamos a eejcutarlo en una VM. Asi que la VM debe estar ejecutandose,
[root@brick03 ~]# virsh list
Id Name State
---------------------------1 FSLVm7_64 running
y la redireccion de puertos para el no_vnc activa,
[root@brick03 ~]# ./novnc_ws.sh
WebSocket server settings:
- Listen on :6080
- Web server. Web root: /usr/share/novnc
- SSL/TLS support
- Backgrounding (daemon)
y desde la maquina local ha de hacerse un tunel ssh al 6080,
Nota: Antes de ejecutar el script para preparar las imagenes en la VM, debemos crear el directorio fsqcdir y asegurarnos que el propietario es el user de la VM.
Por alguna razon hay prblemas para salvar algunas imagenes en baja resolucion. Aprovechando que tengo problemas con la visualizacion de los TIF, voy a editar el script original de ENIGMA,
En la pagina web voy a utilizar PNGs y no TIFFs porque no puedo verlos con Firefox
Solo voy a crear las imagenes TIFF de alta resolucion con tksurfer
Voy a usar ImageMagick (implica que lo necesitas instalado) para convertir las imagenes TIFF a formato PNG, tanto en alta como en baja resolucion
Y ahora hemos de acomodar y preparar todo. El paquete de ENIGMA lo dejo en /old_nas/mri_face/ENIGMA_scripts/, y edito el archivo de matlab para reflejar la ubicacion de mis imagenes y archivos.
Ahora ya puedo abrir MATLAB. Aqui selecciono los directorios /old_nas/mri_face/ENIGMA_scripts/ y /old_nas/mri_face/qc_scripts/ y los añado al path con click derecho. Este ultimo directorio es donde tengo el script que voy a correr (cortical_qc.m). Y entonces voy al prompt de matlab y escribo,
>> cortical_qc
y si todo va bien me tengo que esperar un rato largo a que esten todas las imagenes para evaluar el QC.
Al final de este proceso hemos de construir la pagina web para poder mirar los slices de QC,
#!/usr/bin/perl# Copyright 2023 O. Sotolongo <asqwerty@gmail.com># This program is free software; you can redistribute it and/or modify# it under the terms of the GNU General Public License as published by# the Free Software Foundation; either version 2 of the License, or# (at your option) any later version.## This program is distributed in the hope that it will be useful,# but WITHOUT ANY WARRANTY; without even the implied warranty of# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the# GNU General Public License for more details.# Script purpose is to put the resulting images of ENIGMA QC into# XNAT as eperiment resources# We are going to just read the contents of a directory # (two of them really), pack the rigth images and put the gziped# file inside the fsqc resource,use strict;use warnings;use NEURO4 qw(load_project);use XNATACE qw(xget_session xcreate_res xput_res_file);use Cwd qw(cwd);use Data::Dumpqw(dump);use File::Basename;use File::Tempqw(:mktemp tempdir);my$prj;my$xprj;my$edir;my$idir;my$ifile="outliers.list";my$tmp_dir=$ENV{TMPDIR};@ARGV=("-h")unless@ARGV;while(@ARGVand$ARGV[0]=~/^-/){$_=shift;lastif/^--$/;if(/^-e/){$edir=shift;chomp($edir);}# directorio con el QC de FSif(/^-q/){$idir=shift;chomp($idir);}# directorio con el QC de MATLABif(/^-x/){$xprj=shift;chomp($xprj);}#nombre del proyecto en XNATif(/^-p/){$prj=shift;chomp($prj);}#nombre local del proyecto}if($prjandnot$xprj){my%pdata= load_project($prj);$xprj=$pdata{'XNAME'};}die"Should supply XNAT project name or define it at local project config!\n"unless$xprj;$idir= cwd()."/ENIGMA_QC"unless$idir;$edir= cwd()."/fsqcdir"unless$edir;my%xconf= xget_session();# No voy a complicarme mucho, voy a basar esto en el contenido del directorio de matlabmy@experiments=map{ basename($_)}glob($idir.'/XNAT*');foreachmy$experiment(@experiments){my$selection=$idir.'/'.$experiment.' '.$edir.'/'.$experiment.'.*';my$tzf= mktemp($tmp_dir.'/enigmaqc.XXXXX');my$pre='tar czf '.$tzf.' '.$selection;print"$experiment\n";system($pre);
xcreate_res($xconf{'HOST'},$xconf{'JSESSION'},$experiment,'fsqc');
xput_res_file($xconf{'HOST'},$xconf{'JSESSION'},$experiment,'fsqc','enigma_qc_images.tar.gz',$tzf);}
y queda un archivo compactado con todas las imagenes de ambos QC,
The rest of them
Ahora, solo quedan 289 MRI por evaluar, lo que haré es construir una web conjunta para evaluar el QC de estas imagenes. Entonces , leyendo el archivo outliers.list puedo escoger solo las imagenes que me faltan por evaluar,
Por defecto, es solo una lista de los outliers, indicando la MRI como fail y las notas a ecribir como None. Durante la posterior revision ha de editarse,
[osotolongo@brick03 mri_face]$ head fsqc.csv
XNAT05_E00042,good,None
XNAT05_E00043,good,None
XNAT05_E00047,fail,inner fail, too much lessions?
XNAT05_E00049,good,None
XNAT05_E00050,fail,None
XNAT05_E00051,good,None
XNAT05_E00057,good,None
XNAT05_E00059,good,None
XNAT05_E00060,good,None
XNAT05_E00062,good,None
Y una vez revisados todos los outliers y editado el CSV, hay que subir el json correspondiente al recurso del experimento.
#!/usr/bin/perl# Copyright 2023 O. Sotolongo <asqwerty@gmail.com># This program is free software; you can redistribute it and/or modify# it under the terms of the GNU General Public License as published by# the Free Software Foundation; either version 2 of the License, or# (at your option) any later version.## This program is distributed in the hope that it will be useful,# but WITHOUT ANY WARRANTY; without even the implied warranty of# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the# GNU General Public License for more details.# Este es un script de apoyo para el QC de FS. Si ya tengo los outliers,# puedo marcar los que no lo son como buenos en XNATuse strict;use warnings;use NEURO4 qw(load_project);use XNATACE qw(xget_session xcreate_res xput_res_data);my$prj;my$xprj;my$idir;my$ifile="fsqc.csv";@ARGV=("-h")unless@ARGV;while(@ARGVand$ARGV[0]=~/^-/){$_=shift;lastif/^--$/;if(/^-i/){$ifile=shift;chomp($ifile);}#archivo de outliersif(/^-x/){$xprj=shift;chomp($xprj);}#nombre del proyecto en XNATif(/^-p/){$prj=shift;chomp($prj);}#nombre local del proyecto}if($prjandnot$xprj){my%pdata= load_project($prj);$xprj=$pdata{'XNAME'};}die"Should supply XNAT project name or define it at local project config!\n"unless$xprj;my%xconf= xget_session();# read fsqc results fron input fileopen IDF,$ifileordie"No such file as $ifile";while(<IDF>){my($experiment,$rating,$notes)=/^(.*),(.*),(.*)$/;if($experimentand$rating){$notes='None'unless$notes;my%bad_boy=("rating"=>"$rating","notes"=>"$notes");
xcreate_res($xconf{'HOST'},$xconf{'JSESSION'},$experiment,'fsqc');
xput_res_data($xconf{'HOST'},$xconf{'JSESSION'},$experiment,'fsqc','rating.json',\%bad_boy);print"$experiment, $rating : $notes\n";}}close IDF;