Question
· Oct 11, 2023

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 botella
	set 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:

 

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:

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 storage
Parameter SETTINGS = "OperationDuplexName";
/// This keeps track of the OriginatingMessageID
Property OriginatingMessageID As %Integer;
/// This is the incoming document from the business Sservice
Property DocumentFromService As EnsLib.DICOM.Document;
/// This keeps track of the current state of the process
Property CurrentState As %String [ InitialExpression = "OperationNotConnected" ];
/// This is the name of the operation providing storage
Property OperationDuplexName;
/// Añadimos 11 10 2023 para guardar el listado de estudios
Property 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 service
        If 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 yet
            If ..CurrentState="OperationNotConnected" {
                #; We are in the process of establishing the connection to the operation,
                #; Keep hold of the incoming document
                Set ..DocumentFromService=pInput
                
                Set tSC=..EstablishAssociation(..OperationDuplexName)
                
            } elseif ..CurrentState="OperationConnected" {
                
                
                #; We can forward the document to the operation
                Set tSC=..SendRequestAsync(..OperationDuplexName,..DocumentFromService,0) 
            }
            
        } elseif pSourceConfigName=..OperationDuplexName {
            
            #; We have received a document from the operation
            Set 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) quit
            set study.description = pInput.GetValueAt("DataSet.StudyDescription",,.tSC) if $$$ISERR(tSC) quit
            set study.modality = pInput.GetValueAt("DataSet.ModalitiesInStudy",,.tSC) if $$$ISERR(tSC) quit
            set study.type = pInput.GetValueAt("DataSet.ImageType",,.tSC) if $$$ISERR(tSC) quit
            set study.data = pInput.GetValueAt("DataSet.StudyDate",,.tSC) if $$$ISERR(tSC) quit
            do ..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 = $$$OK
    If pSourceConfigName=..OperationDuplexName {
        
        #; The association with the operation has been completed, operation is now connected
        Set ..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 operation
        Set ..CurrentState="OperationNotConnected"
    ; Añadimos 11 10 2023
    Set 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 association
    If ..CurrentState="OperationConnected" {
        
        #; Form an abort message
        Set tCommandAbort=##class(EnsLib.DICOM.Command.Abort).%New($$$ABORTSOURCESERVICEUSER,$$$ABORTREASONNOTSPECIFIED)
        
        #; Send it to the operation
        Do ..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 operation
			Set 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) quit
		    set study.description = pInput.GetValueAt("DataSet.StudyDescription",,.tSC) if $$$ISERR(tSC) quit
		    set study.modality = pInput.GetValueAt("DataSet.ModalitiesInStudy",,.tSC) if $$$ISERR(tSC) quit
		    set study.type = pInput.GetValueAt("DataSet.ImageType",,.tSC) if $$$ISERR(tSC) quit
		    set study.data = pInput.GetValueAt("DataSet.StudyDate",,.tSC) if $$$ISERR(tSC) quit
		    do ..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:

 

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:

 

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:

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)'

 

 

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/sr...

 

We have also read:

https://docs.intersystems.com/irislatest/csp/docbook/DocBook.UI.Page.cls...

 

Again thank you.

Discussion (14)2
Log in or sign up to continue

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 operation
        Set ..CurrentState="OperationNotConnected"
    
    ; Añadimos 11 10 2023
    
    
    Set 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 operation
		Set ..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
}

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:

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.

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:

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 %Status

And 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 operation
		Set ..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 service
 		If 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 yet
	 		If ..CurrentState="OperationNotConnected" {
		 		#; We are in the process of establishing the connection to the operation,
				#; Keep hold of the incoming document
				Set ..DocumentFromService=pInput
	 			
				Set tSC=..EstablishAssociation(..OperationDuplexName)
				
 			} elseif ..CurrentState="OperationConnected" {
	 			
	 			
	 			#; We can forward the document to the operation
				Set tSC=..SendRequestAsync(..OperationDuplexName,..DocumentFromService,0) 
 			}
 			
 		} elseif pSourceConfigName=..OperationDuplexName {
	 		
	 		#; We have received a document from the operation
			Set 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) quit
		    set study.description = pInput.GetValueAt("DataSet.StudyDescription",,.tSC) if $$$ISERR(tSC) quit
		    set study.modality = pInput.GetValueAt("DataSet.ModalitiesInStudy",,.tSC) if $$$ISERR(tSC) quit
		    set study.type = pInput.GetValueAt("DataSet.ImageType",,.tSC) if $$$ISERR(tSC) quit
		    set study.data = pInput.GetValueAt("DataSet.StudyDate",,.tSC) if $$$ISERR(tSC) quit
		    
		    do ..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
	;Quit tSC
}

We observe at the Visual Trace, just only one response, which is the NULL one at step [17]:

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
 

Thanks @Luis Angel Pérez Ramos .

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:

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.

Thanks @Luis Angel Pérez Ramos for 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:

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 $$$OK

Not 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 service
 		If 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 yet
	 		If ..CurrentState="OperationNotConnected" {
		 		#; We are in the process of establishing the connection to the operation,
				#; Keep hold of the incoming document
				Set ..DocumentFromService=pInput
	 			
				Set tSC=..EstablishAssociation(..OperationDuplexName)
				
 			} elseif ..CurrentState="OperationConnected" {
	 			
	 			
	 			#; We can forward the document to the operation
				Set tSC=..SendRequestAsync(..OperationDuplexName,..DocumentFromService,0) 
 			}
 			
 		} elseif pSourceConfigName=..OperationDuplexName {
	 		
	 		#; We have received a document from the operation
			Set 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) quit
		    set study.description = pInput.GetValueAt("DataSet.StudyDescription",,.tSC) if $$$ISERR(tSC) quit
		    set study.modality = pInput.GetValueAt("DataSet.ModalitiesInStudy",,.tSC) if $$$ISERR(tSC) quit
		    set study.type = pInput.GetValueAt("DataSet.ImageType",,.tSC) if $$$ISERR(tSC) quit
		    set study.data = pInput.GetValueAt("DataSet.StudyDate",,.tSC) if $$$ISERR(tSC) quit
		    
		    do ..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 estudios
	set 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:

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:

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 operation
		Set ..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 estudios
	set 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:

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 = ..respuestaConsultaEstudios
		Quit $$$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:

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 = ..respuestaConsultaEstudios
	Quit $$$OK
}

It will never send back respuestaConsultaStudios because the previous condition, try assigning respuestaConsultaEstudios to callresponse without it.