Hola Yone,

Imagino que te refieres al ejemplo https://github.com/intersystems-ib/iris-dicom-sample

Los comandos C-FIND son mensajes DICOM que deben tener el formato correcto y transmitirse usando el protocolo DICOM. Por eso, o usas un simulador, o generas ese mensaje desde Ensemble / Health Connect.

En el ejemplo, el comando do ##class(DICOM.BS.QueryService).TestFind() provoca la generación de un mensaje C-FIND dentro de la producción para un paciente concreto.

El simulador dcm4che tiene la utilidad findscu que permite simular la generación de mensajes C-FIND.

Hola Yone,

Imagino que habrá diferencias entre el C-FIND que se envía en el primer caso y el segundo.

En el primer caso, el que retorna más resultados, entiendo que es el caso del ejemplo. Revisa cómo se está generando exactamente ese C-FIND:

https://github.com/intersystems-ib/iris-dicom-sample/blob/7bc3c00dfa1bb…

En principio, se está haciendo un PatientRootQuery (tAffectedSOPClassUID).

No sé si puede servirte para algo, pero en el herramienta findscu creo que hay una opción para especificar el Information Model a utilizar:

https://github.com/dcm4che/dcm4che/blob/master/dcm4che-tool/dcm4che-too…

-M <name>                                specifies Information Model.
                                          Supported names: PatientRoot,
                                          StudyRoot, PatientStudyOnly,
                                          MWL, UPSPull, UPSWatch,
                                          UPSQuery, HangingProtocol or
                                          ColorPalette. If no Information
                                          Model is specified, StudyRoot
                                          will be used.

¡Hola!

Sobre los actores:

  • Servidor autorización - instancia IRIS
  • Servidor de recursos - instancia IRIS
  • Cliente - una aplicación web en el caso de Authorization Code, el propio Postman en caso de Client Credentials

En cuanto a la autenticación:

En el ejemplo puedes echar un ojo a las clases Validate y Authenticate que te permiten añadir tu propio comportamiento a métodos como BeforeAuthenticate, AfterAuthenticate, ValidateUser, ValidateClient

Hola Yone,

Disculpa, ¡no había visto que esta pregunta estaba sin responder!

No puedes exportar un documento DICOM a XML y después tratar de escribirlo directamente en la traza. Los documentos DICOM son complejos y la forma de acceder a sus propiedades es a través de los métodos de la clase EnsLib.DICOM.Document.

Por ejemplo:

pInput.GetValueAt("DataSet.PatientID",,.tSC)

Hola Yone,

Los procesos de negocio para DICOM tienen que controlar y gestionar lo que sucede con las operaciones DICOM que usan debido a la propia naturaleza dúplex del protocolo.

https://docs.intersystems.com/irisforhealthlatest/csp/docbook/DocBook.U…

Por ejemplo, en DICOM.BP.QueryProcess tiene que gestionar llamadas para establecer y liberar las asociaciones en la conexión.

Quizá te resulte más sencillo de implementar utilizar varios BP y BO en la producción, según los diferentes sistemas que tengas que integrar.

Hola,

Necesitas configurar el FHIR Server de IRIS como un OAuth Resource Server.

Ten en cuenta que se requiere que cuando envíes el access token al servidor lo hagas como una cabecera Bearer, no como un parámetro de la URL.

Hola Yone,

Entiendo que estás comparando dos cosas diferentes:

Hay más factores que pueden intervenir: servidor web externo, configuración Web Gateway, configuración seguridad aplicación web.

Yo te recomendaría que lo primero de todo pongas algunas trazas en el código de tu WebService para comprobar si la llamada está llegando a donde crees.

Por ejemplo puedes utilizar una global:

set ^zdebug($i(^zdebug))="["_$zdt($zts,3,,2)_"] "_"before" 
set ackXML = ##class(ITB.HL7.Util.Convert).ER7ToXML(ackER7,.tSC,"2.5")
set ^zdebug($i(^zdebug))="["_$zdt($zts,3,,2)_"] "_"after. tSC="_tSC 

Y luego échale un vistazo:

USER>zw ^zdebug
^zdebug=3
^zdebug(2)="[2022-06-03 08:35:47.75] before"
^zdebug(3)="[2022-06-03 08:35:57.39] after. tSC=1"

Así de entrada estás seguro que está llegando hasta la llamada. Si llega hasta la llamada, luego puedes investigar en particular la conversión para ese mensaje.

Hola Yone,

Si el %Status (tSC) que te devuelve ##class(ITB.HL7.Util.Convert).ER7ToXML es 1 como muestras, entonces el error no lo tienes en la parte de la conversión de ER7 a XML en Healthcare-HL7-XML (ITB).

Tendrás que ir examinando el resto de partes que tengas en tu servicio web:

  • No sé si tienes más código antes o después de esa llamada, comprueba que no haya errores.
  • Comprobar logs del servidor web y del CSP Webgateway. Incluso puedes activar ISCLOG Documentación

Hola Yone,

Los Streams se utilizan para almacenar cadenas de datos sin límite de tamaño. Si en una clase persistente, incluyes propiedades de tipo Stream serán persistentes.

%Stream.GlobalCharacter, %Stream.GlobalBinary: persisten los datos en globals (en la base de datos). %Stream.FileCharacter, %Stream.FileBinary: persisten los datos en ficheros (.stream).

Lo que te recomienda la documentación es que utilices estas clases en lugar de sus versiones antiguas, por ejemplo:

  • Utiliza %Stream.GlobalCharacter en lugar de la antigua %GlobalCharacterStream.
  • Utiliza %Stream.FileCharacter en lugar de la antigua %FileCharacterStream.

La gestión de los mensajes huérfanos es como siempre. Si tienes un árbol de clases persistente que cuelga de un mensaje de interoperabilidad (incluya o no Streams), recuerda siempre implementar el método %OnDelete para borrar su contenido adecuadamente.

Hola Yone,

En los adaptadores de interoperabilidad no hay directamente un adaptador STOW-RS pero puedes intentar implementar la parte de servicios REST en la plataforma y luego enviarlo utilizando los adaptadores DICOM TCP convencionales.

Los servicios y operaciones incluidos que utilizan adaptadores DICOM TCP emplean siempre como mensaje EnsLib.DICOM.Document. Y sí, puedes instanciar un documento DICOM desde un fichero (stream) con CreateFromDicomFileStream.

Comprueba siempre el resultado de CreateFromDicomFileStream por si devuelve algún error. Si todo va bien, debería ser 1 o lo que es lo mismo un $$$OK.

USER>set fileStream = ##class(%Stream.FileBinary).%New()

USER>set fileStream.Filename="/shared/dicom/d1I00001.dcm"                                     

USER>write fileStream.Size
262878
USER>set sc = ##class(EnsLib.DICOM.Document).CreateFromDicomFileStream(fileStream, .dicom)

USER>write sc
1
USER>zwrite dicom
dicom=13@EnsLib.DICOM.Document  ; <OREF>
+----------------- general information ---------------
|      oref value: 13
|      class name: EnsLib.DICOM.Document
| reference count: 2
+----------------- attribute values ------------------
|       %Concurrency = 1  <Set>
|     DestinationAET = ""
|           Modified = 1
|  OriginalSourceAET = ""
|          SourceAET = ""
|    SourceIPAddress = ""
+----------------- swizzled references ---------------
|       i%CommandSet = ""  <Set>
|       r%CommandSet = "7@EnsLib.DICOM.CommandSet"  <Set>
|     i%FixedDataSet = ""
|     r%FixedDataSet = "5@EnsLib.DICOM.FixedDataSet"
|   i%MutableDataSet = ""  <Set>
|   r%MutableDataSet = "4@EnsLib.DICOM.MutableDataSet"  <Set>
+--------------- calculated references ---------------
|        (i%DataSet)   <Get>
|        (r%DataSet)   <Get>
|            HasData   <Get>
+-----------------------------------------------------

USER>

Una vez tengas tu instancia de EnsLib.DICOM.Document ya puedes enviarlo por DICOM TCP convencional. En tu ejemplo, parece que lo has incluido en otro mensaje.

Quizá se haya construido con errores el EnsLib.DICOM.Document, o simplemente sea un tema de representación en el visor de mensajes. Siempre puedes instanciar el mensaje en concreto que quieras en el terminal y examinar su contenido para quedarte seguro.

Hola Luis,

Parece que estás en una versión muy antigua (2012) y utilizando una tecnología (ZEN) también antigua. ZEN se soporta aún por compatibilidad, pero échale un vistazo al InterSystems IRIS Migration Guide en el WRC > Software Distribution > Docs.

Sobre tu cuestión, el error probablemente viene dado de que intentas establecer el value de algo nulo. No consigues referencia el parámetro. Prueba con el ejemplo que te ha pasado Mario, o incluso mejor añade un id al parámetro para que puedas referenciarlo directamente a través del identificador.

<tablePane id="table"
           queryClass="MyApp.Employee"
           queryName="ListEmployees">
    <parameter value="Sales" id="param1"/>
    <parameter value="NEW YORK"/>
</tablePane>

Mira por ejemplo en Query Parameters

Hola Yuri,

Una opción muy sencilla es utilizar $zdatetime, por ejemplo:

write $translate($zdatetime($horolog, 3, 1)," ","T")

En la documentación puedes ver distintos formatos disponibles para fecha y hora con los que puedes jugar con $zdatetime.

Hola Yone,

Podrían ser errores de red, o de timeouts durante la transferencia.

Echa un vistazo a Tasks for DICOM Productions, en particular a Configuring a DICOM Duplex Business Host. Tienes varios configuraciones que quizá te ayuden como TraceVerbosity, ARTIM, TXTIM para mostrar más o menos información y modificar ciertos timeouts.

También te puede servir lo que te dice el otro lado de la comunicación (lo que parece que es el simulador).

De todas formas, ten en cuenta que parece que estás trabajando con un simulador y a veces pueden tener ciertas limitaciones.

Hola Yone,

En IRIS y Health Connect se soportan adaptadores para recibir, enviar y tratar mensajes DICOM con dispositivos que soporten el protocolo. En la documentación del producto encuentras información relativa a estos puntos que se soportan.

Pero el estándar DICOM en sí tiene su propia definición y documentación (como ocurre co HL7, FHIR, etc.). Échale un vistazo por ejemplo a https://www.dicomstandard.org/current. Encontrarás los diferentes capítulos del estándar y su especificación.

Hola de nuevo, Yone:

Puedes probar a subir los timeous por ejemplo a 60s.

Además, puedes activar las trazas detalladas de interoperabilidad. Quizá te ponga así más información. Mira en: https://docs.intersystems.com/irisforhealth20221/csp/docbook/Doc.View.cls?KEY=EMONITOR_tracing

set ^Ens.Debug("TraceCat","queue")=1

En otro caso, quizá habría que ya verlo a nivel de red (e.g. captura con WireShark y demás), o si lo quieres ver muy en detalle abrir un caso WRC.

Hola Yone,

Para descartar que el simulador esté manipulando los campos de datos del DICOM en el envío, puedes probar lo siguiente:

  • Comprueba con el simulador los datos del fichero DICOM con dcmdump.
  • En una producción de interoperablidad, añade un Business Service que lea ficheros DICOM directamente (EnsLib.DICOM.Service.File)
  • Configura el Business Service para que envíe el DICOM como mensaje a cualquier Business Operation que tengas (incluso desactivado), sólo para probar.
  • Lee el fichero DICOM de prueba que has generado directamente con el Business Service y comprueba los campos.

Hola Yone,

Por lo que pones, parece que si lees el fichero DICOM en Health Connect directamente tienes los mismos valores que el dump usando el simulador o incluso el visor de DICOM que tienes.

Por tanto, es probable que el servicio de envío de DICOM por TCP que estés montando con el simulador quizá esté alterando algo la información antes de enviarla, o al menos la filtre.

Creo que estás haciendo un C-FIND request, y quizá en la respuesta (dependiendo de parámetros del C-FIND request, o del propio motor del simulador que hace de repositorio) se filtre la información. En ese caso, sería un tema ya específico del propio simulador.

Hola Yone,

En el ejemplo está implementado utilizando un C-MOVE. Básicamente una vez elegidas las imágenes, se pide al PACS que te las envíe con un C-MOVE, y él te las hará llegar con un C-STORE.

Otra opción sería hacerlo con un C-GET. Y pedir la imagen directamente (creo que en el simulador dcm4chee también tienes opción para simular un C-GET). Y sí claro, podrías implementarlo utilizando un C-GET ya que es un mensaje DICOM y podrías enviarlo con Health Connect de la misma forma que se ha hecho con los otros.

jaja! es verdad: mejor, peor o poca diferencia? 

Bueno, en este caso yo creo que depende de cómo te sea más fácil mantener el código de tu producción.

Dependiendo de cuál es tu caso de uso de la integración, quizá te interese mantener por separado lo que tenga que ver con la búsqueda C-FIND y por otro servicio independiente el obtener el studio con un C-GET.

De esa forma podrías tener puntualmente deshabilitado uno y habilitado el otro por ejemplo.

Hola Yone,

Entiendo que quieres montar lo siguiente:

  • Cliente externo (e.g. Postman) que solicite token a servidor de autorización.
  • Servidor de autorización en PREPRODUCCIÓN.
  • Servicio REST en INTEGRACIÓN que actúe como servidor de recursos, que reciba y valide el token.

En principio estás en un escenario como el de Client Credentials del workshop-iris-oauth2.

En resumen, tendrías que configurar lo siguiente:

  • Definir servidor autorización OAuth en PREPRODUCCIÓN.
  • Registrar cliente en servidor de autorización en PREPRODUCCIÓN: tendrás ClientID y ClientSecret. Lo necesitarás para solicitar token.
  • Definir servidor de recursos en INTEGRACIÓN como se hacía en este paso.
    • Cuando defines el servidor de recursos, tienes que indicar cuál es el endpoint del servidor de autorización.
    • Tienes que asignarle un nombre, e.g. "resserver"
    • En el código del servidor de recursos, en INTEGRACIÓN, en el método ValidateJWT sólo necesitas referirte al nombre que le hayas puesto al servidor de recursos (e.g. "resserver").
    • El método ValidateJWT ya se encarga de utilizar tu definición de servidor de recursos "resserver", donde tenías el endpoint del servidor de autorización, y validar el token que le pases.

Actualización:

Este módulo ahora está disponible en PyPI:

Instalar utilizando PyPI

pip3 install iris_pex_embedded_python

Importa las clases ObjectScript, abrir una sesión de Embedded Python y ejecutar:

from grongier.pex import Utils
Utils.setup()

Atención Si el módulo no está actualizado, asegúrate de borrar la versión antigua:

pip3 uninstall iris_pex_embedded_python

o borra manualmente el directorio grongier en <iris_installation>/lib/python/ o fuerza la instalación con pip:

pip3 install --upgrade iris_pex_embedded_python --target <iris_installation>/lib/python/

Hola José Manuel,

Creo que quizá la imagen que tiene el ejemplo tiene una versión de IRIS Community que ya ha caducado.

En el directorio donde lo hayas descargado, modifica el fichero Dockerfile (al comienzo) y prueba a utilizar por ejemplo esta versión:

ARG IMAGE=intersystemsdc/irishealth-ml-community:2022.2.0.368.0-zpm

Hola Yone,

Le he echado un primer vistazo.

Las peticiones que se hacen a /oauth2/token se gestionan en OAuth2.Server.Token:Process -> OAuth2.Server.Token:ProcessClientCredentials -> OAuth2.Server.Token:Authorize. Ese último método es el que retorna el error.

En principio no veo un sitio donde puedas personalizar ese error rápidamente porque son clases internas.

¿Podrías por favor crear un caso de soporte WRC para que se pueda analizar en detalle allí?

Gracias,

Mmm exactamente lo mismo no lo sé, pero en el pasado hemos utilizado `EnsLib.PubSub` para alguna funcionalidad que necesitábamos pero terminábamos desarrollando nuestras propias clases para gestionar la complejidad que nos hacía falta. 

De todas maneras, aún estamos experimentando con esta utilidad :)

Hola Yone,

El error que te devuelve el servicio quizá esté relacionado con que recibe un elemento que no espera.

Te diría que intentes aislarlo lo máximo posible:

  • Tener un método en Health Connect que te permita lanzar una prueba controlada y puedas investigar. Así puedes lanzar mensaje al cliente de WebService que te está dando el error siempre de la misma forma y probar tus cambios sólo desde Health Connect.
  • Activar el log de SOAP para tus pruebas.
  • Intentar reducir esas mínimas diferencias. Eliminando campos o cambiando configuraciones. Si tienes alguna duda en concreto sobre cómo intentar influir en cómo se proyectan los campos en el XML, puedes crear un caso a soporte WRC con la duda concreta!

Hola Yone,

En este caso entiendo que tienes un recurso FHIR en forma de JSON y quieres incluir o no cierta parte en función de si tienes un objeto vacío o no.

Puedes probar algo así: primero creas el recurso con las propiedades que seguro vas a incluir, y después a continuación incluyes la lógica para las propiedades que incluirás según ciertas condiciones:

	set resource = {
            "resourceType": "Observation",
	    "id": (..Id),
            "status": "final",
            "code": {
                "coding": [
                ]
            },
            "subject": {
                "reference": ("Patient/"_..Patient.Id)
            },
            "effectiveDateTime": ($tr(..TimeStamp, " ", "T")),
            "valueQuantity": {
            }
    }

	if ..Code="BodyTemp" {
		set resource.code.coding."0" = { "system": "http://loinc.org", "code": "8310-5", "display": "Body temperature" }
		set resource.valueQuantity.unit = "C"
		set resource.valueQuantity.system = "http://unitsofmeasure.org"
		set resource.valueQuantity.code = "Cel"
		do resource.valueQuantity.%Set("value", ..ValueNM, "number")
	}

Hola Kurro,

Me parece que en las reglas del enrutador no se puede.

Una posible alternativa sería:

  • Desde el enrutador, enviar el mensaje a un Business Process que se encargue de enviar dinámicamente lo que necesites
  • Creas el Business Process anterior, y extraes el valor que necesites del HL7 (e.g. usando .GetValueAt()) y después desde el Business Process sí que puedes hacer envío dinámico utilizando justo esa sintaxis que has puesto (@destino) en la llamada.