Troubleshooting Null Response in DICOM Process Integration
Hello,
First of all; thanks for your time reading this post, and thanks for your answers and help. I am really grateful.
We have the following need: Send a DICOM Find Document to get the complete list of a patient's studies from an Outbound System, which we simulate with the tool called "dcm4che" specifically with these two commands:
1º.Initialize a DICOM database in simulator. We will use this database to run queries using DICOM C-FIND commands:
./dcmdir -c ./shared/DICOMDIR --fs-id SAMPLEDICOMS --fs-desc ./shared/dicom/descriptor ./shared/dicom
2ºStart simulated DICOM archive that uses the previous initialized DICOM database for query/retrieve. This will receive C-FIND and C-MOVE commands:
./dcmqrscp --ae-config ./shared/ae.properties -b VNAPRE:11223 --dicomdir ./shared/DICOMDIR
We create a DICOM Document with dummy data to simulate the FIND command as follows:
/// Class Procesos.DICOM.EnrutadorConsultarEstudiosVNAv01r00.Code Extends Ens.BP.Context
{
...
ClassMethod responseTarjetaC002ToFindDicom(responseTarjetaC002 As Mensajes.Response.GestionPacientes.TSI.consultaDatosPacienteResponse) As EnsLib.DICOM.Document
{
set findDicom = ##class(EnsLib.DICOM.Document).%New()
set tSC = findDicom.SetValueAt("76", "CommandSet.CommandGroupLength")
set tSC = findDicom.SetValueAt("1.2.840.10008.5.1.4.1.2.2.1", "CommandSet.AffectedSOPClassUID")
set tSC = findDicom.SetValueAt("32", "CommandSet.CommandField")
set tSC = findDicom.SetValueAt("1", "CommandSet.MessageID")
set tSC = findDicom.SetValueAt("0", "CommandSet.Priority")
set tSC = findDicom.SetValueAt("65278", "CommandSet.CommandDataSetType")
set tSC = findDicom.SetValueAt("STUDY", "DataSet.QueryRetrieveLevel")
;set tSC = findDicom.SetValueAt("732831", "DataSet.PatientID")// Paciente Prueba Negrin con 2 imagenes dadas por Siemes de una botellaset tSC = findDicom.SetValueAt("988333", "DataSet.PatientID")
set tSC = findDicom.SetValueAt("", "DataSet.AccessionNumber")
set tSC = findDicom.SetValueAt("", "DataSet.StudyDescription")
set tSC = findDicom.SetValueAt("", "DataSet.ModalitiesInStudy")
set tSC = findDicom.SetValueAt("", "DataSet.ImageType")
set tSC = findDicom.SetValueAt("", "DataSet.StudyDate")
quit findDicom
}
...
Storage Default
{
<Data name="CodeDefaultData">
<Subscript>"Code"</Subscript>
<Value name="1">
<Value>todosEstudios</Value>
</Value>
</Data>
<DefaultData>CodeDefaultData</DefaultData>
<Type>%Storage.Persistent</Type>
}
}
We have included this Parent Class as the Bussiness' Process context superclass:
.png)
We call this function inside a <code> block, get the response (which is the dummy DICOM Find document), and then we <call> the DICOM Bussiness Process, being visually as:
.png)
Being as .cls language as:
<scope xpos='335' ypos='1600' xend='335' yend='2150' >
<code name='C002 a Consulta DICOM' xpos='335' ypos='1700' >
<![CDATA[
set context.consultaDICOM = context.responseTarjetaC002ToFindDicom(context.responseTarjetaC002)]]>
</code>
<call name='ConsultarEstudiosFind' target='Procesos.DICOM.ConsultarEstudiosMedianteFind' async='0' xpos='335' ypos='1800' >
<request type='Ens.Request' >
<assign property="callrequest" value="context.consultaDICOM" action="set" />
</request>
<response type='Ens.Response' >
<assign property="context.respuestaDICOM" value="callresponse" action="set" />
</response>
</call>
<code name='LOG' xpos='335' ypos='1900' >
<![CDATA[
$$$LOGWARNING("Despues del call ConsultarEstudiosFind")]]>
</code>
<faulthandlers>
<catchall xpos='335' ypos='2000' xend='200' yend='350' >
<code name='LOG ERROR' xpos='200' ypos='250' >
<![CDATA[
$$$LOGERROR($System.Status.GetErrorText(context.%LastError))]]>
</code>
</catchall>
</faulthandlers>
</scope>
<code name='Informar Demograficos' xpos='335' ypos='2250' >
<![CDATA[
set response = context.informarDemograficosPaciente(context.responseTarjetaC001, context.respuestaDICOM)]]>
</code>
</case>
The DICOM Process which is being invoked, is very basic:
Class Procesos.DICOM.ConsultarEstudiosMedianteFind Extends EnsLib.DICOM.Process [ ClassType = persistent, ProcedureBlock ]
{
/// This parameter names the operation used to provide storageParameter SETTINGS = "OperationDuplexName";/// This keeps track of the OriginatingMessageIDProperty OriginatingMessageID As%Integer;/// This is the incoming document from the business SserviceProperty DocumentFromService As EnsLib.DICOM.Document;/// This keeps track of the current state of the processProperty CurrentState As%String [ InitialExpression = "OperationNotConnected" ];/// This is the name of the operation providing storageProperty OperationDuplexName;/// Añadimos 11 10 2023 para guardar el listado de estudiosProperty respuestaConsultaEstudios As Mensajes.Response.DICOM.consultarEstudiosDatosPacienteVNAResponse;/// Messages received here are instances of EnsLib.DICOM.Document sent to this/// process by the service or operation config items. In this demo, the process is ever/// in one of two states, the Operation is connected or not.
Method OnMessage(pSourceConfigName As%String, pInput As%Library.Persistent) As%Status
{
#dim tSC As%Status = $$$OK#dim tMsgType As%String#dim tFindRequest As EnsLib.DICOM.Document
do {
#; If its a document sent from the serviceIf pSourceConfigName'=..OperationDuplexName {
set..respuestaConsultaEstudios = ##class(Mensajes.Response.DICOM.consultarEstudiosDatosPacienteVNAResponse).%New()
set..respuestaConsultaEstudios.patients = ##class(EsquemasDatos.DICOM.Patient).%New()
#; If the operation has not been connected yetIf..CurrentState="OperationNotConnected" {
#; We are in the process of establishing the connection to the operation,
#; Keep hold of the incoming documentSet..DocumentFromService=pInput
Set tSC=..EstablishAssociation(..OperationDuplexName)
} elseif..CurrentState="OperationConnected" {
#; We can forward the document to the operationSet tSC=..SendRequestAsync(..OperationDuplexName,..DocumentFromService,0)
}
} elseif pSourceConfigName=..OperationDuplexName {
#; We have received a document from the operationSet tMsgType=pInput.GetValueAt("CommandSet.CommandField",,.tSC)
If$$$ISERR(tSC) Quit
#; Should only EVER get a C-FIND-RSP$$$ASSERT(tMsgType="C-FIND-RSP")
#; TODO: Do Something With the Find Response(s)if pInput.GetValueAt("CommandSet.Status",,.tSC)=0
{
Set tSC=..ReleaseAssociation(..OperationDuplexName)
quit
}
else
{
; Escribimos 11 / 10 / 2023 para responder con los datos de cada estudio DICOM remitido por Sistema Destino set study = ##class(EsquemasDatos.DICOM.Study).%New()
set study.accessionNumber = pInput.GetValueAt("DataSet.AccessionNumber",,.tSC) if$$$ISERR(tSC) quitset study.description = pInput.GetValueAt("DataSet.StudyDescription",,.tSC) if$$$ISERR(tSC) quitset study.modality = pInput.GetValueAt("DataSet.ModalitiesInStudy",,.tSC) if$$$ISERR(tSC) quitset study.type = pInput.GetValueAt("DataSet.ImageType",,.tSC) if$$$ISERR(tSC) quitset study.data = pInput.GetValueAt("DataSet.StudyDate",,.tSC) if$$$ISERR(tSC) quitdo..respuestaConsultaEstudios.patients.studies.Insert(study)
; Añadimos 11 / 10 / 2023 para enviar la respuesta DICOM al Enturador Padre;Set tSC=..SendRequestAsync("EnrutadorConsultarEstudiosVNAv01r00",pInput,0);Set tSC=..SendRequestAsync("EnrutadorConsultarEstudiosVNAv01r00",study,0);Set tSC=..SendRequestAsync("EnrutadorConsultarEstudiosVNAv01r00",QueryRsp,0);Set tSC=..SendRequestSync("EnrutadorConsultarEstudiosVNAv01r00",QueryRsp,0)
}
}
} while (0)
Quit tSC
}
/// This method is called by the framework on successful establishment of an association
Method OnAssociationEstablished(pSourceConfigName As%String, pInput As EnsLib.DICOM.Notify.Established) As%Status
{
#dim tSC As%Status = $$$OKIf pSourceConfigName=..OperationDuplexName {
#; The association with the operation has been completed, operation is now connectedSet..CurrentState="OperationConnected"Set tSC=..OnMessage(..ServiceDuplexName,"")
} else {
#; Need to establish an associaton with the operation (we will be called back here at
#; OnAssociationEstablished()Set tSC=..EstablishAssociation(..OperationDuplexName)
}
Quit tSC
}
Method OnAssociationReleased(pSourceConfigName As%String, pInput As EnsLib.DICOM.Notify.Released) As%Status
{
#dim tSC As%Status = $$$OK
#; The association between this process and the operation has been released, so we are now
#; not connected to the operationSet..CurrentState="OperationNotConnected"; Añadimos 11 10 2023Set tSC=..SendRequestAsync("EnrutadorConsultarEstudiosVNAv01r00",..respuestaConsultaEstudios,0)
Quit tSC
}
/// This callback is called by the framework when an Association is rejected
Method OnAssociationRejected(pSourceConfigName As%String, pInput As EnsLib.DICOM.Notify.Rejected) As%Status [ Final ]
{
Quit$$$ERROR($$$EnsDICOMPeerRejectedAssociation)
}
/// This callback is called by the framework when an association encounters an error
Method OnAssociationErrored(pSourceConfigName As%String, pInput As EnsLib.DICOM.Notify.Errored) As%Status [ Final ]
{
Quit pInput.Status
}
/// This callback is called by the framework when an association is aborted
Method OnAssociationAborted(pSourceConfigName As%String, pInput As EnsLib.DICOM.Notify.Aborted) As%Status [ Final ]
{
Quit$$$ERROR($$$EnsDICOMPeerRequestedAbort)
}
/// This method is called when any error occurs. Returning the same error will cause the BusinessProcess to set its/// status to error and close down
Method OnError(request As%Library.Persistent, ByRef response As%Library.Persistent, callrequest As%Library.Persistent, pErrorStatus As%Status, pCompletionKey As%String) As%Status
{
#; If we are in conversation with the operation, we neet to tell the operation to ABORT its associationIf..CurrentState="OperationConnected" {
#; Form an abort messageSet tCommandAbort=##class(EnsLib.DICOM.Command.Abort).%New($$$ABORTSOURCESERVICEUSER,$$$ABORTREASONNOTSPECIFIED)
#; Send it to the operationDo..AbortAssociation(..OperationDuplexName,tCommandAbort)
}
Quit pErrorStatus
}
Storage Default
{
<Data name="QueryDefaultData">
<Subscript>"Query"</Subscript>
<Value name="1">
<Value>OriginatingMessageID</Value>
</Value>
<Value name="2">
<Value>DocumentFromService</Value>
</Value>
<Value name="3">
<Value>CurrentState</Value>
</Value>
<Value name="4">
<Value>OperationDuplexName</Value>
</Value>
<Value name="5">
<Value>listadoEstudios</Value>
</Value>
<Value name="6">
<Value>response</Value>
</Value>
<Value name="7">
<Value>respuestaConsultaEstudos</Value>
</Value>
<Value name="8">
<Value>respuestaConsultaEstudios</Value>
</Value>
</Data>
<DefaultData>QueryDefaultData</DefaultData>
<Type>%Storage.Persistent</Type>
}
}
Here comes what we do not understand:
We do process each study inside the following part:
} elseif pSourceConfigName=..OperationDuplexName {
#; We have received a document from the operationSet tMsgType=pInput.GetValueAt("CommandSet.CommandField",,.tSC)
If$$$ISERR(tSC) Quit
#; Should only EVER get a C-FIND-RSP$$$ASSERT(tMsgType="C-FIND-RSP")
#; TODO: Do Something With the Find Response(s)if pInput.GetValueAt("CommandSet.Status",,.tSC)=0
{
Set tSC=..ReleaseAssociation(..OperationDuplexName)
quit
}
else
{
; Escribimos 11 / 10 / 2023 para responder con los datos de cada estudio DICOM remitido por Sistema Destino set study = ##class(EsquemasDatos.DICOM.Study).%New()
set study.accessionNumber = pInput.GetValueAt("DataSet.AccessionNumber",,.tSC) if$$$ISERR(tSC) quitset study.description = pInput.GetValueAt("DataSet.StudyDescription",,.tSC) if$$$ISERR(tSC) quitset study.modality = pInput.GetValueAt("DataSet.ModalitiesInStudy",,.tSC) if$$$ISERR(tSC) quitset study.type = pInput.GetValueAt("DataSet.ImageType",,.tSC) if$$$ISERR(tSC) quitset study.data = pInput.GetValueAt("DataSet.StudyDate",,.tSC) if$$$ISERR(tSC) quitdo..respuestaConsultaEstudios.patients.studies.Insert(study)
; Añadimos 11 / 10 / 2023 para enviar la respuesta DICOM al Enturador Padre;Set tSC=..SendRequestAsync("EnrutadorConsultarEstudiosVNAv01r00",pInput,0);Set tSC=..SendRequestAsync("EnrutadorConsultarEstudiosVNAv01r00",study,0);Set tSC=..SendRequestAsync("EnrutadorConsultarEstudiosVNAv01r00",QueryRsp,0);Set tSC=..SendRequestSync("EnrutadorConsultarEstudiosVNAv01r00",QueryRsp,0)
}
}
And then, we send it back to the original BPL visual process, via the following line written inside the method "OnAssociationReleased"
Set tSC=..SendRequestAsync("EnrutadorConsultarEstudiosVNAv01r00",..respuestaConsultaEstudios,0)
We do get that response, in the <call>, as you can see:
.png)
However here comes the issue:
⚫️😮 We do not know why there is a "ghost" NULL additions response being returned from the DICOM Process to the Visual BPL:
.png)
.png)
It is important for us to prevent that NULL response, because the undersired effect, is that is overwrittes the [29] response which is the correct one.
To be more precise, adter that <call>, we do a <code> to join a previous response (which is not important for this topic), and the [29] response:
.png)
Being the code:
ClassMethod informarDemograficosPaciente(responseTarjetaC001 As Mensajes.Response.GestionPacientes.TSI.IdentificacionResponse, respuestaDICOM As Mensajes.Response.DICOM.consultarEstudiosDatosPacienteVNAResponse) As Mensajes.Response.DICOM.consultarEstudiosDatosPacienteVNAResponse
{
$$$LOGINFO("respuestaDICOM: "_respuestaDICOM)
$$$LOGINFO("respuestaDICOM.patients: "_respuestaDICOM.patients)
$$$LOGINFO("respuestaDICOM.patients.studies: "_respuestaDICOM.patients.studies)
$$$LOGINFO("respuestaDICOM.patients.studies.Size: "_respuestaDICOM.patients.studies.Size)
set response = ##class(Mensajes.Response.DICOM.consultarEstudiosDatosPacienteVNAResponse).%New()
set response.responseStatus = "OK"set response.patients = ##class(EsquemasDatos.DICOM.Patient).%New()
set response.patients.firstName = responseTarjetaC001.nombre
set response.patients.middleName = responseTarjetaC001.apellido1
set response.patients.lastName = responseTarjetaC001.apellido2
set response.patients.gender = responseTarjetaC001.sexo
set response.patients.birthDate = responseTarjetaC001.fechaNacimiento
for i = 1:1:respuestaDICOM.patients.studies.Size {
do response.patients.studies.Insert(respuestaDICOM.patients.studies.GetAt(i))
}
quit response
}
However, the variable "respuestaDICOM" holds the response [30] which is NULL and not the [29], so it causes an Exception, at the following line:
$$$LOGINFO("respuestaDICOM.patients: "_respuestaDICOM.patients)
Being on the visual trace:
| ERROR <Ens>ErrException: <PROPERTY DOES NOT EXIST>zinformarDemograficosPaciente+2^Procesos.DICOM.EnrutadorConsultarEstudiosVNAv01r00.Code.1 *patients,%Collection.ListOfObj -- - registrado como '-' número - @' Do ##class(Ens.Util.Log).LogInfo($classname(),"informarDemograficosPaciente","respuestaDICOM.patients: "_respuestaDICOM.patients)' |
.png)
Why does the DICOM Process generate a NULL response at step [30]?
How could we prevent (stop) it, to make that NULL response not being forwarded from the DICOM Process to the Basic BPL?
How would we just get the [29] response as needed, without having the [30] overwritting it?
Could you help us please?
Thanks.
The DICOM Process code is 99% from:
https://github.com/intersystems-ib/iris-dicom-sample/blob/master/iris/s…
We have also read:
https://docs.intersystems.com/irislatest/csp/docbook/DocBook.UI.Page.cl…
.png)
.png)
.png)
Again thank you.
Comments
Try adding a $$$TRACE in this method:
Method OnAssociationReleased(pSourceConfigName As%String, pInput As EnsLib.DICOM.Notify.Released) As%Status
{
#dim tSC As%Status = $$$OK
#; The association between this process and the operation has been released, so we are now
#; not connected to the operationSet..CurrentState="OperationNotConnected"; Añadimos 11 10 2023Set tSC=..SendRequestAsync("EnrutadorConsultarEstudiosVNAv01r00",..respuestaConsultaEstudios,0)
Quit tSC
}Maybe you are calling the BP from here with a null response.
Thanks @Luis Angel Pérez Ramos for your reply and your help, thanks.
We have added it as:
Method OnAssociationReleased(pSourceConfigName As%String, pInput As EnsLib.DICOM.Notify.Released) As%Status
{
#dim tSC As%Status = $$$OK
#; The association between this process and the operation has been released, so we are now
#; not connected to the operationSet..CurrentState="OperationNotConnected"; Añadido el LOGINFO 13 10 2023 para depurar la llamada NULL espúrea que realiza este BP al Proceso Padre$$$LOGINFO("OnAssociationReleased antes de llamar al BP Padre, ..respuestaConsultaEstudios: "_..respuestaConsultaEstudios)
; Añadimos 11 10 2023Set tSC=..SendRequestAsync("EnrutadorConsultarEstudiosVNAv01r00",..respuestaConsultaEstudios,0)
Quit tSC
}
However it does only prints it once, and we do not understand why there is a NULL response being replied from this DICOM Process to the Parent Process:
.png)
Please; Could you help us with this issue?
Again, thank you for your time, help, replies, and for being supporting us.
In addittion, a sentence which is quite interesting:
"Collaboration is not just a buzzword; it's the very heart of what makes programming great. Helping others with their technical questions is not only the best way to learn but also a profound way to help ourselves." - Grace Hopper
This sentences shows us both points:
Learning through helping: The statement asserts that when you help others with their technical queries, you, in turn, gain knowledge and experience. Teaching or explaining something to someone else often requires a deeper understanding of the subject, which can enhance your own learning.
Helping is self-beneficial: Lastly, the sentence suggests that helping others is not just an altruistic act but also a means of self-improvement. By aiding others, you not only contribute to the community but also grow as a programmer and problem solver. It's a win-win situation where both parties benefit.
I suspect that the problem is that you are sending a SendRequestAsync to EnrutadorConsultarEstudiosVNAv01r00 that is the business process that called to Procesos.DICOM.ConsultarEstudiosMedianteFind, and after that you are doing the Quit, returning to EnrutadorConsultarEstudiosVNAv01r00 again.
And the Quit is what is generating the null response. You have to avoid the async call and return the result using an output variable.
Thanks @Luis Angel Pérez Ramos for your tiem and help.
First we tried to declare the return type instead of being %Status, being Ens.Response; however it does not allow it:
.png)
Compilando clase Procesos.DICOM.ConsultarEstudiosMedianteFind
ERROR #5478: Error de firma de palabra clave en Procesos.DICOM.ConsultarEstudiosMedianteFind:Method:OnMessage, 'ReturnType' debe ser '%Library.Status' o su subclase
> ERROR #5030: Se produjo un error mientras se compilaba la clase Procesos.DICOM.ConsultarEstudiosMedianteFind
On the other hand, if we keep the return type as %Status, and we just declare an Output vairable pOutput as Ens.Response:
Method OnMessage(pSourceConfigName As%String, pInput As%Library.Persistent, Output pOutput As Ens.Response) As%StatusAnd we disable the line inside OnAssociationReleased() which makes the ..SendRequestAsync
Method OnAssociationReleased(pSourceConfigName As%String, pInput As EnsLib.DICOM.Notify.Released) As%Status
{
#dim tSC As%Status = $$$OK
#; The association between this process and the operation has been released, so we are now
#; not connected to the operationSet..CurrentState="OperationNotConnected"; Añadido el LOGINFO 13 10 2023 para depurar la llamada NULL espúrea que realiza este BP al Proceso Padre$$$LOGINFO("OnAssociationReleased antes de llamar al BP Padre, ..respuestaConsultaEstudios: "_..respuestaConsultaEstudios)
; Añadimos 11 10 2023;Set tSC=..SendRequestAsync("EnrutadorConsultarEstudiosVNAv01r00",..respuestaConsultaEstudios,0)Quit tSC
}And, we comment the Quit tSC inside the OnMessage() method, and we put a Quit pOutput as follows:
/// Messages received here are instances of EnsLib.DICOM.Document sent to this/// process by the service or operation config items. In this demo, the process is ever/// in one of two states, the Operation is connected or not.
Method OnMessage(pSourceConfigName As%String, pInput As%Library.Persistent, Output pOutput As Ens.Response) As%Status
{
#dim tSC As%Status = $$$OK#dim tMsgType As%String#dim tFindRequest As EnsLib.DICOM.Document
do {
#; If its a document sent from the serviceIf pSourceConfigName'=..OperationDuplexName {
set..respuestaConsultaEstudios = ##class(Mensajes.Response.DICOM.consultarEstudiosDatosPacienteVNAResponse).%New()
set..respuestaConsultaEstudios.patients = ##class(EsquemasDatos.DICOM.Patient).%New()
#; If the operation has not been connected yetIf..CurrentState="OperationNotConnected" {
#; We are in the process of establishing the connection to the operation,
#; Keep hold of the incoming documentSet..DocumentFromService=pInput
Set tSC=..EstablishAssociation(..OperationDuplexName)
} elseif..CurrentState="OperationConnected" {
#; We can forward the document to the operationSet tSC=..SendRequestAsync(..OperationDuplexName,..DocumentFromService,0)
}
} elseif pSourceConfigName=..OperationDuplexName {
#; We have received a document from the operationSet tMsgType=pInput.GetValueAt("CommandSet.CommandField",,.tSC)
If$$$ISERR(tSC) Quit
#; Should only EVER get a C-FIND-RSP$$$ASSERT(tMsgType="C-FIND-RSP")
#; TODO: Do Something With the Find Response(s)if pInput.GetValueAt("CommandSet.Status",,.tSC)=0
{
Set tSC=..ReleaseAssociation(..OperationDuplexName)
quit
}
else
{
; Escribimos 11 / 10 / 2023 para responder con los datos de cada estudio DICOM remitido por Sistema Destino set study = ##class(EsquemasDatos.DICOM.Study).%New()
set study.accessionNumber = pInput.GetValueAt("DataSet.AccessionNumber",,.tSC) if$$$ISERR(tSC) quitset study.description = pInput.GetValueAt("DataSet.StudyDescription",,.tSC) if$$$ISERR(tSC) quitset study.modality = pInput.GetValueAt("DataSet.ModalitiesInStudy",,.tSC) if$$$ISERR(tSC) quitset study.type = pInput.GetValueAt("DataSet.ImageType",,.tSC) if$$$ISERR(tSC) quitset study.data = pInput.GetValueAt("DataSet.StudyDate",,.tSC) if$$$ISERR(tSC) quitdo..respuestaConsultaEstudios.patients.studies.Insert(study)
; Añadimos 11 / 10 / 2023 para enviar la respuesta DICOM al Enturador Padre;Set tSC=..SendRequestAsync("EnrutadorConsultarEstudiosVNAv01r00",pInput,0);Set tSC=..SendRequestAsync("EnrutadorConsultarEstudiosVNAv01r00",study,0);Set tSC=..SendRequestAsync("EnrutadorConsultarEstudiosVNAv01r00",QueryRsp,0);Set tSC=..SendRequestSync("EnrutadorConsultarEstudiosVNAv01r00",QueryRsp,0)
}
}
} while (0)
set pOutput = ..respuestaConsultaEstudiosQuit pOutput
;Quit tSC
}
We observe at the Visual Trace, just only one response, which is the NULL one at step [17]:.png)
I still not knowing why it happens, why could it happen?
Could you help us please?
Thanks for your time and for your help, and even more I am really grateful for your explanations about this issue. Thank you.
"Collaboration and helping others with their programming questions isn't just an act of kindness; it's the most powerful way to learn and to help ourselves grow in this ever-evolving field." - Ada Lovelace
For the Quit you have to return the status, is not necessary to return the output variable. You have to update the call to Procesos.DICOM.ConsultarEstudiosMedianteFind adding the output variable as reference.
Thanks @LuisAngel.PérezRamos.
Now we have changed the method's signature to add the pOutput; as:
Method OnMessage(pSourceConfigName As %String, pInput As %Library.Persistent, Output pOutput As Ens.Response) As %Status
Then, we have commented (disable) the SendRequestAsync inside OnAssociationReleased(), as:
;Set tSC=..SendRequestAsync("EnrutadorConsultarEstudiosVNAv01r00",..respuestaConsultaEstudios,0)
Finally, we have set our Property which holds the studies' list as follows, inside the OnMessage(), at the ending part of this method:
set pOutput = ..respuestaConsultaEstudios
Then, when we test it, it just shows us the "NULL", response without the one we do want:
.png)
Why if we disable the ..SendRequestAsync , our child DICOM Process does NOT reply to the Parent visual Process, with the desired Ens.Response?
Why it justs responds with a "NULL" Ens.Response?
Thanks again for your time, help, and for reading and writting to us.
Did you update the call from EnrutadorConsultarEstudiosVNAv01r00 to ConsultarEstudiosMedianteFind adding the variable by reference?
Thanks @LuisAngel.PérezRamosfor your help.
How could we change our Parent Bussiness Process EnrutadorConsultarEstudiosVNAv01r00 when it calls its Child DICOM Process ConsultarEstudiosMedianteFind to add the variable by reference?
We currently <call> it as follows:
<scope xpos='335' ypos='1600' xend='335' yend='2150' >
<code name='C002 a Consulta DICOM' xpos='335' ypos='1700' >
<![CDATA[
set context.consultaDICOM = context.responseTarjetaC002ToFindDicom(context.responseTarjetaC002)]]>
</code>
<call name='ConsultarEstudiosFind' target='Procesos.DICOM.ConsultarEstudiosMedianteFind' async='0' xpos='335' ypos='1800' >
<request type='Ens.Request' >
<assign property="callrequest" value="context.consultaDICOM" action="set" />
</request>
<response type='Ens.Response' >
<assign property="context.respuestaDICOM" value="callresponse" action="set" />
</response>
</call>
<code name='LOG' xpos='335' ypos='1900' >
<![CDATA[
$$$LOGWARNING("Despues del call ConsultarEstudiosFind")
$$$LOGINFO(context.respuestaDICOM)
$$$LOGINFO(context.respuestaDICOM.patients.studies.GetAt(1).description)]]>
</code>
<faulthandlers>
<catchall xpos='335' ypos='2000' xend='200' yend='350' >
<code name='LOG ERROR' xpos='200' ypos='250' >
<![CDATA[
$$$LOGERROR($System.Status.GetErrorText(context.%LastError))]]>
</code>
</catchall>
</faulthandlers>
</scope>
Which visually is:
.png)
In addition our Child DICOM Process, has its method's signature as follows, where we have added the Output pOutput variable:
Method OnMessage(pSourceConfigName As %String, pInput As %Library.Persistent, Output pOutput As Ens.Response) As %Status
Thanks for your time, help, and replies.
Oh! forget what I said, I was thinking that you were calling a Business Operation all the time...remove the pOutput from the OnMessage method .
I see what is the problem. Your BP is using the method OnMessage to do the C-FIND operation, but the OnResponse method is not receiving the response.
Remove the pOutput references from your code and try with the following code:
do ..%responseSet(..respuestaConsultaEstudios)
Quit$$$OKNot sure if that would work.
Thank you @Luis Angel Pérez Ramos , for your time, and deep replies.
We have removed the pOutput in the Child DICOM Process.
In addition, as suggested, we have added inside OnMessage's ending the following:
; 16 10 2023 responder con el listado de estudios
set tSC = ..%responseSet(..respuestaConsultaEstudios)
$$$LOGALERT("tSC: "_tSC)
$$$LOGALERT($System.Status.GetErrorText(tSC))
Quit $$$OK
Being the complete Bussines Process ConsultarEstudiosMedianteFind's OnMessage() method as we can read here:
/// Messages received here are instances of EnsLib.DICOM.Document sent to this/// process by the service or operation config items. In this demo, the process is ever/// in one of two states, the Operation is connected or not.
Method OnMessage(pSourceConfigName As%String, pInput As%Library.Persistent) As%Status
{
#dim tSC As%Status = $$$OK#dim tMsgType As%String#dim tFindRequest As EnsLib.DICOM.Document
do {
#; If its a document sent from the serviceIf pSourceConfigName'=..OperationDuplexName {
set..respuestaConsultaEstudios = ##class(Mensajes.Response.DICOM.consultarEstudiosDatosPacienteVNAResponse).%New()
set..respuestaConsultaEstudios.patients = ##class(EsquemasDatos.DICOM.Patient).%New()
#; If the operation has not been connected yetIf..CurrentState="OperationNotConnected" {
#; We are in the process of establishing the connection to the operation,
#; Keep hold of the incoming documentSet..DocumentFromService=pInput
Set tSC=..EstablishAssociation(..OperationDuplexName)
} elseif..CurrentState="OperationConnected" {
#; We can forward the document to the operationSet tSC=..SendRequestAsync(..OperationDuplexName,..DocumentFromService,0)
}
} elseif pSourceConfigName=..OperationDuplexName {
#; We have received a document from the operationSet tMsgType=pInput.GetValueAt("CommandSet.CommandField",,.tSC)
If$$$ISERR(tSC) Quit
#; Should only EVER get a C-FIND-RSP$$$ASSERT(tMsgType="C-FIND-RSP")
#; TODO: Do Something With the Find Response(s)if pInput.GetValueAt("CommandSet.Status",,.tSC)=0
{
Set tSC=..ReleaseAssociation(..OperationDuplexName)
$$$LOGINFO("Antes quit Release")
quit$$$LOGINFO("Después quit Release")
}
else
{
; Escribimos 11 / 10 / 2023 para responder con los datos de cada estudio DICOM remitido por Sistema Destino set study = ##class(EsquemasDatos.DICOM.Study).%New()
set study.accessionNumber = pInput.GetValueAt("DataSet.AccessionNumber",,.tSC) if$$$ISERR(tSC) quitset study.description = pInput.GetValueAt("DataSet.StudyDescription",,.tSC) if$$$ISERR(tSC) quitset study.modality = pInput.GetValueAt("DataSet.ModalitiesInStudy",,.tSC) if$$$ISERR(tSC) quitset study.type = pInput.GetValueAt("DataSet.ImageType",,.tSC) if$$$ISERR(tSC) quitset study.data = pInput.GetValueAt("DataSet.StudyDate",,.tSC) if$$$ISERR(tSC) quitdo..respuestaConsultaEstudios.patients.studies.Insert(study)
; Añadimos 11 / 10 / 2023 para enviar la respuesta DICOM al Enturador Padre;Set tSC=..SendRequestAsync("EnrutadorConsultarEstudiosVNAv01r00",pInput,0);Set tSC=..SendRequestAsync("EnrutadorConsultarEstudiosVNAv01r00",study,0);Set tSC=..SendRequestAsync("EnrutadorConsultarEstudiosVNAv01r00",QueryRsp,0);Set tSC=..SendRequestSync("EnrutadorConsultarEstudiosVNAv01r00",QueryRsp,0)
}
}
} while (0)
;set pOutput = ..respuestaConsultaEstudios;Quit pOutput; 16 10 2023 responder con el listado de estudiosset tSC = ..%responseSet(..respuestaConsultaEstudios)
$$$LOGALERT("tSC: "_tSC)
$$$LOGALERT($System.Status.GetErrorText(tSC))
Quit$$$OK;$$$LOGINFO("Antes Quit tSC")Quit tSC
;$$$LOGINFO("Despues Quit tSC")
}After compiling, we observe the Visual Trace, where both yellow LOGALERTS are being printed various times:
.png)
Being the interesting part, before the NULL reply is being forwarded from the Child to the Parent, it continues happening, we do observe the NULL response:
.png)
Besides, we have desactivated the lines mentioned inside OnMessage()'s method, and we have added them inside OnAssociationReleased() as we can see below:
Method OnAssociationReleased(pSourceConfigName As%String, pInput As EnsLib.DICOM.Notify.Released) As%Status
{
#dim tSC As%Status = $$$OK
#; The association between this process and the operation has been released, so we are now
#; not connected to the operationSet..CurrentState="OperationNotConnected"; Añadido el LOGINFO 13 10 2023 para depurar la llamada NULL espúrea que realiza este BP al Proceso Padre$$$LOGINFO("OnAssociationReleased antes de llamar al BP Padre, ..respuestaConsultaEstudios: "_..respuestaConsultaEstudios)
; 16 10 2023 responder con el listado de estudiosset tSC = ..%responseSet(..respuestaConsultaEstudios)
$$$LOGALERT("tSC: "_tSC)
$$$LOGALERT($System.Status.GetErrorText(tSC))
Quit$$$OK; Añadimos 11 10 2023;Set tSC=..SendRequestAsync("EnrutadorConsultarEstudiosVNAv01r00",..respuestaConsultaEstudios,0)Quit tSC
}
After that change, we have tried again, and we see this new Visual Trace, where the important part, is that now, the LOGALERTS are only being displayed BEFORE the NULL response is being replied:
.png)
To summarize, even if we add the following lines to OnMessage() or to OnAssociationReleased():
; 16 10 2023 responder con el listado de estudios
set tSC = ..%responseSet(..respuestaConsultaEstudios)
$$$LOGALERT("tSC: "_tSC)
$$$LOGALERT($System.Status.GetErrorText(tSC))
Quit $$$OK
we still having that strange NULL response that we do not understand.
How could we further understand / debug this issue?
Thanks for your help and time and replies.
Maybe you can do a workaround for this problem creating a new Business Process to send respuestaConsultaEstudios and manage it from there, ignoring from EnrutadorConsultarEstudiosVNAv01r00 the null response that you receive from ConsultarEstudiosMedianteFind
Another option could be define OnResponse method on ConsultarEstudiosMedianteFind and define the response properly.
Thanks for your help @Luis Angel Pérez Ramos
We have faced the second option, to implement the OnResponse() method in the CHild DICOM Process.
We have written:
Method OnResponse(request As%Library.Persistent, ByRef response As%Library.Persistent, callrequest As%Library.Persistent, callresponse As%Library.Persistent, pCompletionKey As%String) As%Status
{
$$$LOGWARNING("OnResponse")
$$$LOGWARNING("request.%ClassName(): "_request.%ClassName())
;$$$LOGWARNING("response.%ClassName(): "_response.%ClassName())$$$LOGWARNING("callrequest.%ClassName(): "_callrequest.%ClassName())
$$$LOGWARNING("callresponse.%ClassName(): "_callresponse.%ClassName())
if (callresponse.%ClassName() = "consultarEstudiosDatosPacienteVNAResponse"){
set callresponse = ..respuestaConsultaEstudiosQuit$$$OK
}
Quit '$$$OK
}It outputs:
request.%ClassName(): Document
| callrequest.%ClassName(): Start |
| callresponse.%ClassName(): Ack |
It is being called after PrivateSession.Message.Ack.
However, we do not know how to handle it properly, to make it continue execution, untils it reaches the final Ens.Response and then just only send it if it is not NULL.
Currently the Visual Trace:
.png)
It shows:
[11] DICOM.Document the C-FIND request
[12] PrivateSession.Message.Start
[13] PrivateSession.Message.Ack
[14--17] The LOGS
[19]
| ERROR <Ens>ErrBPTerminated: Finalizando BP Procesos.DICOM.ConsultarEstudiosMedianteFind # debido a un error: ERROR #00: (sin descripción de error) > ERROR #00: (sin descripción de error) |
We do not know how to implement the OnResponse() properly because it forces us to reply with a %Status, and either if we respond $$$OK or '$$$OK , it halts execution, and replies with a NULL error, or with a NULL.
Furthermore, there are not examples of OnResponse() being implemented in the parent internal class: EnsLib.DICOM.Process
How could we continue beyond this point?
Thanks for your help.
If you are getting:
callresponse.%ClassName(): Ack
And your code is:
if (callresponse.%ClassName() = "consultarEstudiosDatosPacienteVNAResponse"){
set callresponse = ..respuestaConsultaEstudiosQuit$$$OK
}It will never send back respuestaConsultaStudios because the previous condition, try assigning respuestaConsultaEstudios to callresponse without it.