This shows you the differences between two versions of the page.
Both sides previous revision Previous revision Next revision | Previous revision | ||
neuroimagen:xnat_pipelines_registro [2020/12/11 12:05] daniel [Detalle de la implementación] |
neuroimagen:xnat_pipelines_registro [2020/12/11 16:25] daniel [RegisterPETwithMRImatch: llamadas a los pipelets] |
||
---|---|---|---|
Line 6: | Line 6: | ||
- preprocesamiento de los experimentos (emparejamiento de imágenes PET con las MRI más próximas del mismo sujeto y conversión de ambas a NIFTI) | - preprocesamiento de los experimentos (emparejamiento de imágenes PET con las MRI más próximas del mismo sujeto y conversión de ambas a NIFTI) | ||
- ejecución del pipeline de registro y cálculo | - ejecución del pipeline de registro y cálculo | ||
- | - generación de una imagen de previsualización del registro y un proceso /user friendly/ de validación del resultado | + | - generación de una imagen de previsualización del registro y un proceso |
===== Ejemplo de uso ===== | ===== Ejemplo de uso ===== | ||
Line 60: | Line 60: | ||
{{ : | {{ : | ||
+ | |||
+ | |||
===== Generación del informe ===== | ===== Generación del informe ===== | ||
Line 79: | Line 81: | ||
**Nota:** Cuando un estudio PET no ha podido emparejarse con uno MRI, el usuario recibe un mensaje de error. Entonces, en el archivo CSV la columna de REGISTRATION_QA aparece vacía (y en MRI_EXPERIMENT_ID un valor que no corresponde a ningún experimento). | **Nota:** Cuando un estudio PET no ha podido emparejarse con uno MRI, el usuario recibe un mensaje de error. Entonces, en el archivo CSV la columna de REGISTRATION_QA aparece vacía (y en MRI_EXPERIMENT_ID un valor que no corresponde a ningún experimento). | ||
+ | |||
+ | ---- | ||
===== Detalle de la implementación ===== | ===== Detalle de la implementación ===== | ||
Line 102: | Line 106: | ||
</ | </ | ||
- | **Nota:** La forma de declarar un pipelet no está correctamente documentada en el [[https:// | + | **Nota:** La forma de declarar un pipelet no está correctamente documentada en el [[https:// |
El parámetro '' | El parámetro '' | ||
**Nota:** '' | **Nota:** '' | ||
+ | |||
+ | El resultado del pipeline anterior es un archivo JSON (que también es cargado como recurso de la sesión PET en Xnat) con una la estructura similar a las respuestas de los servicios de Xnat, para poder hacer uso después de este mecanismo de consulta; p.ej. | ||
+ | |||
+ | <code javascript> | ||
+ | { " | ||
+ | " | ||
+ | { " | ||
+ | " | ||
+ | }] | ||
+ | } | ||
+ | } | ||
+ | </ | ||
La conversión de DICOM a NIFTI se lleva a cabo ejecutando el pipeline '' | La conversión de DICOM a NIFTI se lleva a cabo ejecutando el pipeline '' | ||
Line 124: | Line 140: | ||
</ | </ | ||
</ | </ | ||
+ | |||
+ | **Nota:** Los pipelines de Xnat pueden usar llamadas a funciones Java del /pipeline engine/ para evaluar variables y expresiones y asignar valores a parámetros. En particular, '' | ||
+ | < | ||
+ | | ||
+ | | ||
+ | </ | ||
+ | |||
+ | Más detalles en el código fuente de [[https:// | ||
**Nota:** La diferencia entre '' | **Nota:** La diferencia entre '' | ||
Line 129: | Line 153: | ||
El último pipeline llamado es '' | El último pipeline llamado es '' | ||
+ | ==== MatchPETwithMRI: | ||
+ | |||
+ | El pipeline usa el script '' | ||
+ | |||
+ | Primero, hace la consulta a Xnat sobre los experimentos de modalidad (xsiType) '' | ||
+ | |||
+ | Después, ejecuta el script '' | ||
+ | |||
+ | <code Bash> | ||
+ | matchPETwithMRI.sh --PETdate $DATE --MRIrecords mriSessions.csv --outputMatch mriSessionMatch.json | ||
+ | </ | ||
+ | |||
+ | donde '' | ||
+ | |||
+ | ++++El código de este script se puede ver a continuación. | | ||
+ | <file Bash matchPETwithMRI.sh> | ||
+ | #!/bin/bash -l | ||
+ | |||
+ | ARGS=( " | ||
+ | |||
+ | #parse options | ||
+ | HOST="" | ||
+ | USER="" | ||
+ | PASSWORD="" | ||
+ | PROJECT="" | ||
+ | SUBJECT="" | ||
+ | PET_EXPERIMENT="" | ||
+ | MATCH_OUT="" | ||
+ | for ((n=0; n< | ||
+ | case " | ||
+ | --project) | ||
+ | let n=n+1 | ||
+ | PROJECT=" | ||
+ | ;; | ||
+ | | ||
+ | let n=n+1 | ||
+ | SUBJECT=" | ||
+ | ;; | ||
+ | --PETdate) | ||
+ | let n=n+1 | ||
+ | PET_DATE=" | ||
+ | ;; | ||
+ | --MRIrecords) | ||
+ | let n=n+1 | ||
+ | MRI_RECORDS=" | ||
+ | ;; | ||
+ | --outputMatch) | ||
+ | let n=n+1 | ||
+ | MATCH_OUT=" | ||
+ | ;; | ||
+ | --version) | ||
+ | echo " | ||
+ | echo " | ||
+ | exit 0 | ||
+ | ;; | ||
+ | --help) | ||
+ | echo " | ||
+ | echo " | ||
+ | echo " | ||
+ | echo " | ||
+ | echo " | ||
+ | exit 0 | ||
+ | ;; | ||
+ | esac | ||
+ | done | ||
+ | |||
+ | if [ -z " | ||
+ | echo " | ||
+ | exit 1 | ||
+ | fi | ||
+ | |||
+ | #helper function to read CSV values | ||
+ | function getCSVfield() { | ||
+ | echo " | ||
+ | n=1 | ||
+ | while read l ; do | ||
+ | if [ $n == " | ||
+ | let n=n+1 | ||
+ | done | ||
+ | ) | ||
+ | } | ||
+ | |||
+ | #PET date in seconds | ||
+ | PET_UDATE=$( date -d " | ||
+ | |||
+ | #list of MRI experiments | ||
+ | cat " | ||
+ | |||
+ | #choose closest MRI image in time | ||
+ | MRI_EXPER="" | ||
+ | DT_MIN=0 | ||
+ | while read MRI_RECORD ; do | ||
+ | MRI_DATE=$( getCSVfield " | ||
+ | MRI_UDATE=$( date -d " | ||
+ | DT=$(echo "$(( " | ||
+ | if [ -z " | ||
+ | MRI_EXPER=$( getCSVfield " | ||
+ | DT_MIN=$DT | ||
+ | fi | ||
+ | done | ||
+ | |||
+ | if [ $DT_MIN -gt $((3600*24*30*6)) ] ; then | ||
+ | MRI_EXPER="" | ||
+ | fi | ||
+ | |||
+ | #write MRI experiment name to file | ||
+ | if ! [ -z " | ||
+ | ( | ||
+ | echo ' | ||
+ | printf '" | ||
+ | echo '" | ||
+ | ) > " | ||
+ | fi | ||
+ | ) | ||
+ | </ | ||
+ | ++++ | ||
+ | |||
+ | El archivo JSON generado, '' | ||
+ | |||
+ | [[http:// | ||
+ | |||
+ | ==== DicomToNifti_Y: | ||
+ | |||
+ | Este pipeline es una adaptación del '' | ||
+ | |||
+ | * usar un formato de salida en los nombres de los ficheros NII distinto del formato por defecto (en particular, se usa: '' | ||
+ | * usar el argumento /ignore derived/ ('' | ||
+ | * recibir como parámetro el directorio de trabajo | ||
+ | |||
+ | **Nota:** previamente, | ||
+ | |||
+ | ==== RegisterPETwithMRI: | ||
+ | |||
+ | Si este pipeline no recibe el parámetro '' | ||
+ | |||
+ | El pipeline ejecuta tres scripts: '' | ||
+ | |||
+ | === linkNIIwithTagValue.sh === | ||
+ | |||
+ | Este script busca dentro del directorio de trabajo los archivos JSON asociados a archivos NIFTI que contienen un valor particular de un tag DICOM (es decir, un par clave-valor en el JSON). | ||
+ | |||
+ | ++++El código del script se puede ver a continuación. | | ||
+ | <file bash linkNIIwithTagValue.sh> | ||
+ | #!/bin/bash | ||
+ | |||
+ | ARGS=( " | ||
+ | |||
+ | #parse options | ||
+ | DCMTAG="" | ||
+ | DCMVALUE="" | ||
+ | LINKBASE="" | ||
+ | for ((n=0; n< | ||
+ | case " | ||
+ | --tag) | ||
+ | let n=n+1 | ||
+ | DCMTAG=" | ||
+ | ;; | ||
+ | | ||
+ | let n=n+1 | ||
+ | DCMVALUE=" | ||
+ | ;; | ||
+ | --link) | ||
+ | let n=n+1 | ||
+ | LINKBASE=" | ||
+ | ;; | ||
+ | --version) | ||
+ | echo " | ||
+ | echo " | ||
+ | exit 0 | ||
+ | ;; | ||
+ | --help) | ||
+ | echo " | ||
+ | echo " | ||
+ | echo " | ||
+ | echo " | ||
+ | echo " | ||
+ | exit 0 | ||
+ | ;; | ||
+ | esac | ||
+ | done | ||
+ | |||
+ | if [ -z " | ||
+ | echo " | ||
+ | exit 1 | ||
+ | fi | ||
+ | |||
+ | find . -name ' | ||
+ | fnj=" | ||
+ | if grep " | ||
+ | rm -f " | ||
+ | |||
+ | EXT=$(echo " | ||
+ | ln -s " | ||
+ | ln -s " | ||
+ | |||
+ | fi | ||
+ | done | ||
+ | |||
+ | if ! [ -e " | ||
+ | exit -1 | ||
+ | fi | ||
+ | </ | ||
+ | ++++ | ||
+ | |||
+ | El script se llamaría desde la línea de comandos de esta forma: | ||
+ | |||
+ | <code Bash> | ||
+ | linkNIIwithTagValue.sh --tag SequenceName --value _tfl3d1_16ns --link sub-XNAT5_S00037_T1w | ||
+ | </ | ||
+ | |||
+ | El argumento '' | ||
+ | |||
+ | === registerPETwithMRI.sh === | ||
+ | |||
+ | Este script usa las herramientas de la //FSL// y de //ANTS// para registrar las imágenes cuyos enlaces se crearon en el script anterior. Después, calcula dos valores estadísticos (usando la utilidad '' | ||
+ | |||
+ | ++++ El código del script se puede ver a continuación. | | ||
+ | <file bash registerPETwithMRI.sh> | ||
+ | #!/bin/bash -l | ||
+ | |||
+ | ARGS=( " | ||
+ | |||
+ | if [ -z " | ||
+ | export PIPEDIR=/ | ||
+ | fi | ||
+ | |||
+ | if [ -z " | ||
+ | export FSLDIR="/ | ||
+ | source ${FSLDIR}/ | ||
+ | fi | ||
+ | if [ -z " | ||
+ | export ANTS_PATH="/ | ||
+ | export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/ | ||
+ | fi | ||
+ | |||
+ | |||
+ | #parse options | ||
+ | STUDY="" | ||
+ | SID="" | ||
+ | WDIR="" | ||
+ | PET="" | ||
+ | MRI="" | ||
+ | for ((n=0; n< | ||
+ | case " | ||
+ | --study) | ||
+ | let n=n+1 | ||
+ | STUDY=" | ||
+ | ;; | ||
+ | | ||
+ | let n=n+1 | ||
+ | SID=" | ||
+ | ;; | ||
+ | --wdir) | ||
+ | let n=n+1 | ||
+ | WDIR=" | ||
+ | ;; | ||
+ | --pet) | ||
+ | let n=n+1 | ||
+ | PET=" | ||
+ | ;; | ||
+ | --mri) | ||
+ | let n=n+1 | ||
+ | MRI=" | ||
+ | ;; | ||
+ | --out) | ||
+ | let n=n+1 | ||
+ | OUT=" | ||
+ | ;; | ||
+ | --version) | ||
+ | echo " | ||
+ | echo " | ||
+ | exit 0 | ||
+ | ;; | ||
+ | --help) | ||
+ | echo " | ||
+ | echo " | ||
+ | echo " | ||
+ | echo " | ||
+ | echo " | ||
+ | echo " | ||
+ | echo " | ||
+ | echo " | ||
+ | exit 0 | ||
+ | ;; | ||
+ | esac | ||
+ | done | ||
+ | |||
+ | if [ -z " | ||
+ | echo " | ||
+ | exit 1 | ||
+ | fi | ||
+ | |||
+ | |||
+ | ${FSLDIR}/ | ||
+ | |||
+ | ${ANTS_PATH}/ | ||
+ | |||
+ | ${ANTS_PATH}/ | ||
+ | |||
+ | ${ANTS_PATH}/ | ||
+ | |||
+ | ${ANTS_PATH}/ | ||
+ | |||
+ | CTX=$( ${FSLDIR}/ | ||
+ | NORM=$( ${FSLDIR}/ | ||
+ | |||
+ | SURV=$(bc -l <<< | ||
+ | CL=$(bc -l <<< | ||
+ | if [ -s " | ||
+ | jq -c " | ||
+ | mv ${OUT}~ | ||
+ | else | ||
+ | ( | ||
+ | cat << | ||
+ | {" | ||
+ | .EOF | ||
+ | ) > " | ||
+ | fi | ||
+ | </ | ||
+ | ++++ | ||
+ | |||
+ | El script se llamaría desde la línea de comandos como sigue: | ||
+ | |||
+ | <code bash> | ||
+ | |||
+ | |||
+ | **Nota:** Para que se pueda ejecutar el script, además de ser visibles las bibliotecas de Freesurfer y de ANT, debe estar instalado en todos los nodos del cluster el programa '' | ||
+ | |||
+ | **Nota:** para que el script pueda acceder a las imágenes de referencia en ''/ | ||
+ | |||
+ | === mkSlicesQAhtm.sh === | ||
+ | |||
+ | Este script genera una página HTML interactiva con la que hacer la comprobación visual del registro. Utiliza la herramienta '' | ||
+ | |||
+ | <code javascript> | ||
+ | //update registration status | ||
+ | regStatus.ResultSet.Result[0].qa=( $(" | ||
+ | fetch(" | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | ).then( ... ); | ||
+ | </ | ||
+ | |||
+ | **Nota:** Para interactuar con Xnat a través de una página web, se debe obtener un //token// CSRF (// | ||
+ | |||
+ | ++++El código completo del script se puede ver a continuación. | | ||
+ | <file bash mkSlicesQAhtm.sh> | ||
+ | #!/bin/bash -l | ||
+ | |||
+ | ARGS=( " | ||
+ | |||
+ | if [ -z " | ||
+ | export FSLDIR="/ | ||
+ | source ${FSLDIR}/ | ||
+ | fi | ||
+ | |||
+ | |||
+ | #parse options | ||
+ | SUBJECT_ID="" | ||
+ | DIRBASE="" | ||
+ | XNATBASE="" | ||
+ | for ((n=0; n< | ||
+ | case " | ||
+ | --sid) | ||
+ | let n=n+1 | ||
+ | SUBJECT_ID=" | ||
+ | ;; | ||
+ | --wdir) | ||
+ | let n=n+1 | ||
+ | DIRBASE=" | ||
+ | ;; | ||
+ | --version) | ||
+ | echo " | ||
+ | echo " | ||
+ | exit 0 | ||
+ | ;; | ||
+ | --help) | ||
+ | echo " | ||
+ | echo " | ||
+ | echo " | ||
+ | echo " | ||
+ | exit 0 | ||
+ | ;; | ||
+ | esac | ||
+ | done | ||
+ | |||
+ | if [ -z " | ||
+ | echo " | ||
+ | exit 1 | ||
+ | fi | ||
+ | |||
+ | #QA with slices | ||
+ | qaGIF=${SUBJECT_ID}_fbb_mni.gif | ||
+ | ${FSLDIR}/ | ||
+ | |||
+ | ( | ||
+ | cat << | ||
+ | < | ||
+ | < | ||
+ | < | ||
+ | <meta charset=" | ||
+ | < | ||
+ | < | ||
+ | window.onload=function () { | ||
+ | function \$(q) { return document.querySelector(q); | ||
+ | function \$\$(q) { return document.querySelectorAll(q); | ||
+ | |||
+ | //download registration status and parse it | ||
+ | var csrfToken=null; | ||
+ | var regStatus=null; | ||
+ | //hack to get Xnat csrfToken | ||
+ | fetch("/" | ||
+ | function(response) { | ||
+ | if( response.ok ) | ||
+ | return response.text(); | ||
+ | throw " | ||
+ | } | ||
+ | ).then( | ||
+ | function(text) { | ||
+ | var a=text.match(/ | ||
+ | if( a ) { | ||
+ | csrfToken=a[0].replace(/ | ||
+ | } | ||
+ | else | ||
+ | throw " | ||
+ | } | ||
+ | ); | ||
+ | |||
+ | //get MRI session registration info | ||
+ | fetch(" | ||
+ | function(response) { | ||
+ | if( response.ok ) | ||
+ | return response.json(); | ||
+ | throw " | ||
+ | } | ||
+ | ).then( | ||
+ | function(jresp) { | ||
+ | //set input# | ||
+ | if( jresp.ResultSet.Result[0].qa ) | ||
+ | \$(" | ||
+ | else { | ||
+ | jresp.ResultSet.Result[0].qa=0; | ||
+ | \$(" | ||
+ | } | ||
+ | regStatus=jresp; | ||
+ | \$(" | ||
+ | } | ||
+ | ); | ||
+ | |||
+ | //get images from Xnat server (served as binary streams?) | ||
+ | var imgs=\$\$(" | ||
+ | for(var i=0; i < imgs.length; | ||
+ | (function (img) { | ||
+ | fetch(img.getAttribute(' | ||
+ | ).then( | ||
+ | function(response) { | ||
+ | if( response.ok ) | ||
+ | return response.blob(); | ||
+ | throw " | ||
+ | } | ||
+ | ).then( | ||
+ | function(blob) { | ||
+ | img.src=URL.createObjectURL(blob); | ||
+ | } | ||
+ | ).catch( | ||
+ | function(e) { | ||
+ | alert(e); | ||
+ | } | ||
+ | ); | ||
+ | })(imgs[i]); | ||
+ | } | ||
+ | |||
+ | \$(" | ||
+ | if( confirm(" | ||
+ | //update registration status | ||
+ | regStatus.ResultSet.Result[0].qa=( \$(" | ||
+ | fetch(" | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | ).then( | ||
+ | function(response) { | ||
+ | if( ! response.ok ) | ||
+ | throw " | ||
+ | } | ||
+ | ).catch( | ||
+ | function(e) { | ||
+ | alert(e); | ||
+ | } | ||
+ | ); | ||
+ | } | ||
+ | }; | ||
+ | } | ||
+ | </ | ||
+ | </ | ||
+ | < | ||
+ | .EOF | ||
+ | |||
+ | cat << | ||
+ | <div id=" | ||
+ | <br /> | ||
+ | .EOF | ||
+ | |||
+ | cat << | ||
+ | <hr /> | ||
+ | <form id=" | ||
+ | <input type=" | ||
+ | | ||
+ | <input type=" | ||
+ | | ||
+ | <input type=" | ||
+ | </ | ||
+ | </ | ||
+ | </ | ||
+ | .EOF | ||
+ | |||
+ | ) >>" | ||
+ | </ | ||
+ | ++++ | ||
+ | |||
+ | La llamada al script desde la línea de comandos se haría como sigue: | ||
+ | |||
+ | <code bash> | ||
+ | mkSlicesQAhtm.sh --sid XNAT5_S00037 --wdir ./reg/ | ||
+ | </ | ||
+ | |||
+ | Como se ve, no necesita más información que el '' | ||
+ | |||
+ | ==== Script para generación de informe final ==== | ||
+ | |||
+ | Para facilitar el acceso a la información, | ||
+ | |||
+ | ++++ El código completo de este script (que se debe copiar o bien en el directorio share/ | ||
+ | <file bash get_registration_report.sh> | ||
+ | #!/bin/bash | ||
+ | |||
+ | ARGS=( " | ||
+ | HELP=" | ||
+ | |||
+ | #global variables | ||
+ | [ -s " | ||
+ | [ -s " | ||
+ | |||
+ | #local variables | ||
+ | PROJ_ID="" | ||
+ | OUTPUT="" | ||
+ | |||
+ | #parse arguments | ||
+ | for ((n=0; n< | ||
+ | case " | ||
+ | --help-short) | ||
+ | echo " | ||
+ | exit 0 | ||
+ | ;; | ||
+ | --help) | ||
+ | echo " | ||
+ | cat << | ||
+ | | ||
+ | | ||
+ | | ||
+ | | ||
+ | .EOF | ||
+ | exit 0 | ||
+ | ;; | ||
+ | --version) | ||
+ | echo " | ||
+ | echo " | ||
+ | exit 0; | ||
+ | ;; | ||
+ | --project_id) | ||
+ | let n=n+1 | ||
+ | PROJECT_ID=" | ||
+ | ;; | ||
+ | --output) | ||
+ | let n=n+1 | ||
+ | EXPERIMENT_ID=" | ||
+ | ;; | ||
+ | -*) | ||
+ | echo " | ||
+ | ;; | ||
+ | esac | ||
+ | done | ||
+ | |||
+ | #experiment route | ||
+ | [ -z " | ||
+ | URIpath="/ | ||
+ | |||
+ | if [ -z " | ||
+ | echo " | ||
+ | else | ||
+ | echo " | ||
+ | fi | ||
+ | |||
+ | curl -f -X GET -u " | ||
+ | grep ' | ||
+ | cut -d, -f 2 |\ | ||
+ | while read PET_EXPERIMENT_ID ; do | ||
+ | URIpath="/ | ||
+ | DCM_SUBJECT_ID="" | ||
+ | SUBJECT_ID="" | ||
+ | MRI_EXPERIMENT_ID="" | ||
+ | REGISTRATION_QA="" | ||
+ | STATISTICS_SURV="" | ||
+ | STATISTICS_CL="" | ||
+ | |||
+ | curl -f -X GET -u $USER: | ||
+ | sed ' | ||
+ | while read kv ; do | ||
+ | k=" | ||
+ | v=" | ||
+ | #echo "K=$k, V=$v" | ||
+ | case $k in | ||
+ | dcmPatientId) DCM_SUBJECT_ID=" | ||
+ | subject_ID) | ||
+ | esac | ||
+ | done | ||
+ | printf '" | ||
+ | ) | ||
+ | |||
+ | curl -f -X GET -u $USER: | ||
+ | sed ' | ||
+ | while read kv ; do | ||
+ | k=" | ||
+ | v=" | ||
+ | #echo "K=$k, V=$v" | ||
+ | case $k in | ||
+ | MRIsession) MRI_EXPERIMENT_ID=" | ||
+ | qa) | ||
+ | surv) | ||
+ | cl) | ||
+ | esac | ||
+ | done | ||
+ | printf '" | ||
+ | ) | ||
+ | done | if [ -z " | ||
+ | cat | ||
+ | else | ||
+ | cat >> " | ||
+ | fi | ||
+ | </ | ||
+ | ++++ | ||
+ | |||
+ | La llamada y el formato del resultado se pueden ver más arriba. | ||