Mercurial > hg > orthanc-python
changeset 300:086904c6a9d6
wip: new SCP callbacks
| author | Alain Mazy <am@orthanc.team> |
|---|---|
| date | Wed, 26 Nov 2025 06:56:59 +0100 |
| parents | 947e7f4fe34d |
| children | 4210556d96ab |
| files | CodeAnalysis/ClassDocumentation.json CodeAnalysis/CustomFunctions.json NEWS Resources/Orthanc/OrthancPluginCodeModel.json Resources/Orthanc/Sdk-1.12.10/orthanc/OrthancCPlugin.h Sources/DicomScpCallbacks.cpp |
| diffstat | 6 files changed, 808 insertions(+), 30 deletions(-) [+] |
line wrap: on
line diff
--- a/CodeAnalysis/ClassDocumentation.json Mon Oct 13 09:24:40 2025 +0200 +++ b/CodeAnalysis/ClassDocumentation.json Wed Nov 26 06:56:59 2025 +0100 @@ -1,4 +1,5 @@ { + "OrthancPluginDicomConnection" : "DICOM connection managed by the Orthanc core", "OrthancPluginDicomInstance" : "DICOM instance managed by the Orthanc core", "OrthancPluginDicomWebNode" : "Node visited by DICOMweb conversion", "OrthancPluginFindAnswers" : "Answers to a DICOM C-FIND query",
--- a/CodeAnalysis/CustomFunctions.json Mon Oct 13 09:24:40 2025 +0200 +++ b/CodeAnalysis/CustomFunctions.json Wed Nov 26 06:56:59 2025 +0100 @@ -187,6 +187,29 @@ }, { + "comment" : "New in release 7.0", + "short_name" : "RegisterFindCallback2", + "implementation" : "RegisterFindCallback2", + "documentation" : { + "description" : [ "Register a callback to handle C-Find requests (v2)." ], + "args" : { + "callback" : "The callback function." + } + }, + "args" : [ + { + "sdk_name" : "callback", + "sdk_type" : "Callable", + "callable_type" : "FindCallback2", + "callable_protocol_args" : "answers: FindAnswers, query: FindQuery, connection: DicomConnection", + "callable_protocol_return" : "None" + } + ], + "return_sdk_type" : "void", + "sdk_functions" : [ "OrthancPluginRegisterFindCallback" ] + }, + + { "comment" : "New in release 3.2", "short_name" : "RegisterMoveCallback", "implementation" : "RegisterMoveCallback", @@ -210,11 +233,11 @@ }, { - "comment" : "New in release 3.2", + "comment" : "New in release 4.2", "short_name" : "RegisterMoveCallback2", "implementation" : "RegisterMoveCallback2", "documentation" : { - "description" : [ "Register a callback to handle C-Move requests (full version, with multiple suboperations)." ], + "description" : [ "Register a callback to handle C-Move requests (full version, with multiple suboperations. Equivalent to the OrthancPluginRegisterMoveCallback from the C SDK)." ], "args" : { "callback" : "Main callback that creates the C-Move driver.", "get_move_size" : "Callback to read the number of C-Move suboperations.", @@ -256,6 +279,52 @@ }, { + "comment" : "New in release 7.0", + "short_name" : "RegisterMoveCallback3", + "implementation" : "RegisterMoveCallback3", + "documentation" : { + "description" : [ "Register a callback to handle C-Move requests (full version, with multiple suboperations. Equivalent to the OrthancPluginRegisterMoveCallbeck2 from the C SDK)." ], + "args" : { + "callback" : "Main callback that creates the C-Move driver.", + "get_move_size" : "Callback to read the number of C-Move suboperations.", + "apply_move" : "Callback to apply one C-Move suboperation.", + "free_move" : "Callback to free the C-Move driver." + } + }, + "args" : [ + { + "sdk_name" : "callback", + "sdk_type" : "Callable", + "callable_type" : "MoveCallback3", + "callable_protocol_args" : "Connection: DicomConnection, Level: str, PatientID: str, AccessionNumber: str, StudyInstanceUID: str, SeriesInstanceUID: str, SOPInstanceUID: str, TargetAET: str, OriginatorID: int", + "callable_protocol_return" : "object", "comment" : "This is the newly created C-Move driver." + }, + { + "sdk_name" : "get_move_size", + "sdk_type" : "Callable", + "callable_type" : "GetMoveSizeCallback", + "callable_protocol_args" : "driver: object", + "callable_protocol_return" : "int" + }, + { + "sdk_name" : "apply_move", + "sdk_type" : "Callable", + "callable_type" : "ApplyMoveCallback", + "callable_protocol_args" : "driver: object", + "callable_protocol_return" : "None" + }, + { + "sdk_name" : "free_move", + "sdk_type" : "Callable", + "callable_type" : "FreeMoveCallback", + "callable_protocol_args" : "driver: object", + "callable_protocol_return" : "None" + } + ], + "return_sdk_type" : "void" + }, + + { "comment" : "New in release 3.2", "short_name" : "RegisterWorklistCallback", "implementation" : "RegisterWorklistCallback",
--- a/NEWS Mon Oct 13 09:24:40 2025 +0200 +++ b/NEWS Wed Nov 26 06:56:59 2025 +0100 @@ -4,7 +4,11 @@ TODO before release: update to SDK 1.12.10 * The "orthanc.pyi" stub is now excluded from the "install" step during the build - +* Added new SCP callbacks: + - RegisterFindCallback2 + - RegisterMoveCallback3 + - TODO: worklist + storage commitment + Version 6.0 (2025-08-12) ========================
--- a/Resources/Orthanc/OrthancPluginCodeModel.json Mon Oct 13 09:24:40 2025 +0200 +++ b/Resources/Orthanc/OrthancPluginCodeModel.json Wed Nov 26 06:56:59 2025 +0100 @@ -1,6 +1,68 @@ { "classes": [ { + "methods": [ + { + "args": [], + "c_function": "OrthancPluginGetConnectionRemoteAet", + "const": true, + "documentation": { + "args": {}, + "description": [ + "This function returns the Application Entity Title (AET) of the DICOM modality from which a DICOM connection originates." + ], + "return": "The pointer to the AET, NULL in case of error.", + "summary": "Get the remote AET of a DICOM connection." + }, + "return_sdk_type": "const char *", + "since_sdk": [ + 1, + 12, + 10 + ] + }, + { + "args": [], + "c_function": "OrthancPluginGetConnectionRemoteIp", + "const": true, + "documentation": { + "args": {}, + "description": [ + "This function returns the IP of the DICOM modality from which a DICOM connection originates." + ], + "return": "The pointer to the IP, NULL in case of error.", + "summary": "Get the remote IP of a DICOM connection." + }, + "return_sdk_type": "const char *", + "since_sdk": [ + 1, + 12, + 10 + ] + }, + { + "args": [], + "c_function": "OrthancPluginGetConnectionCalledAet", + "const": true, + "documentation": { + "args": {}, + "description": [ + "This function returns the Orthanc called AET that a DICOM modality has used in a DICOM connection." + ], + "return": "The pointer to the called AET, NULL in case of error.", + "summary": "Get the called AET of a DICOM connection." + }, + "return_sdk_type": "const char *", + "since_sdk": [ + 1, + 12, + 10 + ] + } + ], + "name": "OrthancPluginDicomConnection" + }, + { "destructor": "OrthancPluginFreeDicomInstance", "methods": [ { @@ -5504,11 +5566,11 @@ "documentation": { "args": { "queueId": "A unique identifier identifying both the plugin and the queue.", - "valueId": "An opaque id obtained from OrthancPluginReserveQueueValue" + "valueId": "The opaque identifier for the value provided by OrthancPluginReserveQueueValue()." }, "description": [], "return": "0 if success, other value if error.", - "summary": "Acknowledge that a queue value has been consumed and definitely removes it from the queue." + "summary": "Acknowledge that a queue value has been properly consumed by the plugin and can be permanently removed from the queue." }, "return_sdk_enumeration": "OrthancPluginErrorCode", "return_sdk_type": "enumeration", @@ -5529,6 +5591,7 @@ "OrthancPluginGetImageBuffer", "OrthancPluginRestApiGet2", "OrthancPluginRegisterWorklistCallback", + "OrthancPluginRegisterWorklistCallback2", "OrthancPluginRegisterDecodeImageCallback", "OrthancPluginCreateImageAccessor", "OrthancPluginLookupDictionary", @@ -5570,6 +5633,9 @@ "OrthancPluginSetStableStatus", "OrthancPluginRegisterHttpAuthentication", "OrthancPluginRegisterAuditLogHandler", - "OrthancPluginReserveQueueValue" + "OrthancPluginReserveQueueValue", + "OrthancPluginRegisterFindCallback2", + "OrthancPluginRegisterMoveCallback2", + "OrthancPluginRegisterStorageCommitmentScpCallback2" ] } \ No newline at end of file
--- a/Resources/Orthanc/Sdk-1.12.10/orthanc/OrthancCPlugin.h Mon Oct 13 09:24:40 2025 +0200 +++ b/Resources/Orthanc/Sdk-1.12.10/orthanc/OrthancCPlugin.h Wed Nov 26 06:56:59 2025 +0100 @@ -19,14 +19,18 @@ * - Possibly register a custom storage area using ::OrthancPluginRegisterStorageArea3(). * - Possibly register a custom database back-end area using OrthancPluginRegisterDatabaseBackendV4(). * - Possibly register a handler for C-Find SCP using OrthancPluginRegisterFindCallback(). + * - Possibly register a handler for C-Find SCP using OrthancPluginRegisterFindCallback2(). * - Possibly register a handler for C-Find SCP against DICOM worklists using OrthancPluginRegisterWorklistCallback(). + * - Possibly register a handler for C-Find SCP against DICOM worklists using OrthancPluginRegisterWorklistCallback2(). * - Possibly register a handler for C-Move SCP using OrthancPluginRegisterMoveCallback(). + * - Possibly register a handler for C-Move SCP using OrthancPluginRegisterMoveCallback2(). * - Possibly register a custom decoder for DICOM images using OrthancPluginRegisterDecodeImageCallback(). * - Possibly register a callback to filter incoming HTTP requests using OrthancPluginRegisterIncomingHttpRequestFilter2(). * - Possibly register a callback to unserialize jobs using OrthancPluginRegisterJobsUnserializer(). * - Possibly register a callback to refresh its metrics using OrthancPluginRegisterRefreshMetricsCallback(). * - Possibly register a callback to answer chunked HTTP transfers using ::OrthancPluginRegisterChunkedRestCallback(). * - Possibly register a callback for Storage Commitment SCP using ::OrthancPluginRegisterStorageCommitmentScpCallback(). + * - Possibly register a callback for Storage Commitment SCP using ::OrthancPluginRegisterStorageCommitmentScpCallback2(). * - Possibly register a callback to keep/discard/modify incoming DICOM instances using OrthancPluginRegisterReceivedInstanceCallback(). * - Possibly register a custom transcoder for DICOM images using OrthancPluginRegisterTranscoderCallback(). * - Possibly register a callback to discard instances received through DICOM C-STORE using OrthancPluginRegisterIncomingCStoreInstanceFilter(). @@ -72,6 +76,9 @@ * * @defgroup DicomInstance DicomInstance * @brief Functions to access DICOM images that are managed by the Orthanc core. + * + * @defgroup DicomConnection DicomConnection + * @brief Functions to access DICOM connection parameters that are managed by the Orthanc core. **/ @@ -546,6 +553,10 @@ _OrthancPluginService_RegisterStorageArea3 = 1020, /* New in Orthanc 1.12.8 */ _OrthancPluginService_RegisterHttpAuthentication = 1021, /* New in Orthanc 1.12.9 */ _OrthancPluginService_RegisterAuditLogHandler = 1022, /* New in Orthanc 1.12.9 */ + _OrthancPluginService_RegisterFindCallback2 = 1023, /* New in Orthanc 1.12.10 */ + _OrthancPluginService_RegisterMoveCallback2 = 1024, /* New in Orthanc 1.12.10 */ + _OrthancPluginService_RegisterWorklistCallback2 = 1025, /* New in Orthanc 1.12.10 */ + _OrthancPluginService_RegisterStorageCommitmentScpCallback2 = 1026, /* New in Orthanc 1.12.0 */ /* Sending answers to REST calls */ _OrthancPluginService_AnswerBuffer = 2000, @@ -664,6 +675,11 @@ _OrthancPluginService_SubmitJob = 9002, _OrthancPluginService_RegisterJobsUnserializer = 9003, _OrthancPluginService_CreateJob2 = 9004, /* New in SDK 1.11.3 */ + + /* Access to DICOM connection */ + _OrthancPluginService_GetConnectionRemoteAet = 10000, /* New in SDK 1.12.10 */ + _OrthancPluginService_GetConnectionRemoteIp = 10001, /* New in SDK 1.12.10 */ + _OrthancPluginService_GetConnectionCalledAet = 10002, /* New in SDK 1.12.10 */ _OrthancPluginService_INTERNAL = 0x7fffffff } _OrthancPluginService; @@ -1384,6 +1400,23 @@ _OrthancPluginDicomWebNode_t OrthancPluginDicomWebNode; + /** + * @brief Opaque structure that represents DICOM connection + * parameters. + * @ingroup DicomConnection + **/ + ORTHANC_PLUGIN_SINCE_SDK("1.12.10") + typedef struct + _OrthancPluginDicomConnection_t OrthancPluginDicomConnection; + + + ORTHANC_PLUGIN_SINCE_SDK("1.12.10") + typedef struct + { + const OrthancPluginDicomConnection* connection; + const char** resultString; + } _OrthancPluginAccessDicomConnection; + /** * @brief Signature of a callback function that answers to a REST request. @@ -1652,6 +1685,24 @@ const char* calledAet); + /** + * @brief Callback to handle the C-Find SCP requests for worklists (v2). + * + * Signature of a callback function that is triggered when Orthanc + * receives a C-Find SCP request against modality worklists. + * + * @param answers The target structure where answers must be stored. + * @param query The worklist query. + * @param connection The DICOM connection from which the request originates. + * @return 0 if success, other value if error. + * @ingroup DicomCallbacks + **/ + ORTHANC_PLUGIN_SINCE_SDK("1.12.10") + typedef OrthancPluginErrorCode (*OrthancPluginWorklistCallback2) ( + OrthancPluginWorklistAnswers* answers, + const OrthancPluginWorklistQuery* query, + const OrthancPluginDicomConnection* connection); + /** * @brief Callback to filter incoming HTTP requests received by Orthanc. @@ -1749,6 +1800,25 @@ const char* calledAet); + /** + * @brief Callback to handle incoming C-Find SCP requests (v2). + * + * Signature of a callback function that is triggered whenever + * Orthanc receives a C-Find SCP request not concerning modality + * worklists. + * + * @param answers The target structure where answers must be stored. + * @param query The worklist query. + * @param connection The DICOM connection from which the request originates. + * @return 0 if success, other value if error. + * @ingroup DicomCallbacks + **/ + ORTHANC_PLUGIN_SINCE_SDK("1.12.10") + typedef OrthancPluginErrorCode (*OrthancPluginFindCallback2) ( + OrthancPluginFindAnswers* answers, + const OrthancPluginFindQuery* query, + const OrthancPluginDicomConnection* connection); + /** * @brief Callback to handle incoming C-Move SCP requests. @@ -1796,7 +1866,52 @@ const char* sourceAet, const char* targetAet, uint16_t originatorId); + + /** + * @brief Callback to handle incoming C-Move SCP requests (v2). + * + * Signature of a callback function that is triggered whenever + * Orthanc receives a C-Move SCP request. The callback receives the + * type of the resource of interest (study, series, instance...) + * together with the DICOM tags containing its identifiers. In turn, + * the plugin must create a driver object that will be responsible + * for driving the successive move suboperations. + * + * @param resourceType The type of the resource of interest. Note + * that this might be set to ResourceType_None if the + * QueryRetrieveLevel (0008,0052) tag was not provided by the + * issuer (i.e. the originator modality). + * @param patientId Content of the PatientID (0x0010, 0x0020) tag of the resource of interest. Might be NULL. + * @param accessionNumber Content of the AccessionNumber (0x0008, 0x0050) tag. Might be NULL. + * @param studyInstanceUid Content of the StudyInstanceUID (0x0020, 0x000d) tag. Might be NULL. + * @param seriesInstanceUid Content of the SeriesInstanceUID (0x0020, 0x000e) tag. Might be NULL. + * @param sopInstanceUid Content of the SOPInstanceUID (0x0008, 0x0018) tag. Might be NULL. + * @param connection The DICOM connection from which the request originates. + * @param targetAet The Application Entity Title (AET) of the + * modality that should receive the DICOM files. + * @param originatorId The Message ID issued by the originator modality, + * as found in tag (0000,0110) of the DICOM query emitted by the issuer. + * + * @return The NULL value if the plugin cannot deal with this query, + * or a pointer to the driver object that is responsible for + * handling the successive move suboperations. + * + * @note If targetAet equals sourceAet, this is actually a query/retrieve operation. + * @ingroup DicomCallbacks + **/ + ORTHANC_PLUGIN_SINCE_SDK("1.12.10") + typedef void* (*OrthancPluginMoveCallback2) ( + OrthancPluginResourceType resourceType, + const char* patientId, + const char* accessionNumber, + const char* studyInstanceUid, + const char* seriesInstanceUid, + const char* sopInstanceUid, + const OrthancPluginDicomConnection* connection, + const char* targetAet, + uint16_t originatorId); + /** * @brief Callback to read the size of a C-Move driver. @@ -1804,7 +1919,7 @@ * Signature of a callback function that returns the number of * C-Move suboperations that are to be achieved by the given C-Move * driver. This driver is the return value of a previous call to the - * OrthancPluginMoveCallback() callback. + * OrthancPluginMoveCallback() or OrthancPluginMoveCallback2() callback. * * @param moveDriver The C-Move driver of interest. * @return The number of suboperations. @@ -1819,7 +1934,7 @@ * Signature of a callback function that applies the next C-Move * suboperation that os to be achieved by the given C-Move * driver. This driver is the return value of a previous call to the - * OrthancPluginMoveCallback() callback. + * OrthancPluginMoveCallback() or OrthancPluginMoveCallback2() callback. * * @param moveDriver The C-Move driver of interest. * @return 0 if success, or the error code if failure. @@ -1834,7 +1949,7 @@ * Signature of a callback function that releases the resources * allocated by the given C-Move driver. This driver is the return * value of a previous call to the OrthancPluginMoveCallback() - * callback. + * or OrthancPluginMoveCallback2() callback. * * @param moveDriver The C-Move driver of interest. * @ingroup DicomCallbacks @@ -2180,7 +2295,7 @@ **/ ORTHANC_PLUGIN_SINCE_SDK("1.4.0") ORTHANC_PLUGIN_INLINE int32_t OrthancPluginCheckVersionAdvanced( - OrthancPluginContext* context, + const OrthancPluginContext* context, int32_t expectedMajor, int32_t expectedMinor, int32_t expectedRevision) @@ -2295,7 +2410,7 @@ * @ingroup Callbacks **/ ORTHANC_PLUGIN_INLINE int32_t OrthancPluginCheckVersion( - OrthancPluginContext* context) + const OrthancPluginContext* context) { return OrthancPluginCheckVersionAdvanced( context, @@ -5599,6 +5714,34 @@ } + ORTHANC_PLUGIN_SINCE_SDK("1.12.10") + typedef struct + { + OrthancPluginWorklistCallback2 callback; + } _OrthancPluginWorklistCallback2; + + /** + * @brief Register a callback to handle modality worklists requests (v2). + * + * This function registers a callback to handle C-Find SCP requests + * on modality worklists. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param callback The callback. + * @return 0 if success, other value if error. + * @ingroup DicomCallbacks + **/ + ORTHANC_PLUGIN_SINCE_SDK("1.12.10") + ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginRegisterWorklistCallback2( + OrthancPluginContext* context, + OrthancPluginWorklistCallback2 callback) + { + _OrthancPluginWorklistCallback2 params; + params.callback = callback; + + return context->InvokeService(context, _OrthancPluginService_RegisterWorklistCallback2, ¶ms); + } + typedef struct { @@ -8234,7 +8377,44 @@ const char* remoteAet, const char* calledAet); - + + /** + * @brief Callback executed by the storage commitment SCP (v2). + * + * Signature of a factory function that creates an object to handle + * one incoming storage commitment request. + * + * @remark The factory receives the list of the SOP class/instance + * UIDs of interest to the remote storage commitment SCU. This gives + * the factory the possibility to start some prefetch process + * upfront in the background, before the handler object is actually + * queried about the status of these DICOM instances. + * + * @param handler Output variable where the factory puts the handler object it created. + * @param jobId ID of the Orthanc job that is responsible for handling + * the storage commitment request. This job will successively look for the + * status of all the individual queried DICOM instances. + * @param transactionUid UID of the storage commitment transaction + * provided by the storage commitment SCU. It contains the value of the + * (0008,1195) DICOM tag. + * @param sopClassUids Array of the SOP class UIDs (0008,0016) that are queried by the SCU. + * @param sopInstanceUids Array of the SOP instance UIDs (0008,0018) that are queried by the SCU. + * @param countInstances Number of DICOM instances that are queried. This is the size + * of the `sopClassUids` and `sopInstanceUids` arrays. + * @param connection The DICOM connection from which the request originates. + * @return 0 if success, other value if error. + * @ingroup DicomCallbacks + **/ + ORTHANC_PLUGIN_SINCE_SDK("1.12.10") + typedef OrthancPluginErrorCode (*OrthancPluginStorageCommitmentFactory2) ( + void** handler /* out */, + const char* jobId, + const char* transactionUid, + const char* const* sopClassUids, + const char* const* sopInstanceUids, + uint32_t countInstances, + const OrthancPluginDicomConnection* connection); + /** * @brief Callback to free one storage commitment SCP handler. * @@ -8281,6 +8461,16 @@ OrthancPluginStorageCommitmentLookup lookup; } _OrthancPluginRegisterStorageCommitmentScpCallback; + + ORTHANC_PLUGIN_SINCE_SDK("1.12.10") + typedef struct + { + OrthancPluginStorageCommitmentFactory2 factory; + OrthancPluginStorageCommitmentDestructor destructor; + OrthancPluginStorageCommitmentLookup lookup; + } _OrthancPluginRegisterStorageCommitmentScpCallback2; + + /** * @brief Register a callback to handle incoming requests to the storage commitment SCP. * @@ -10414,9 +10604,10 @@ * @param queueId A unique identifier identifying both the plugin and the queue. * @param origin The position from where the value is dequeued (back for LIFO, front for FIFO). * @return 0 if success, other value if error. - * @deprecated This function should not be used anymore because two consumers may consume the same - * value. Use OrthancPluginReserveQueueValue and OrthancPluginAcknowledgeQueueValue instead. - **/ + * @deprecated This function should not be used anymore because there is a risk of loosing + * a value if the consumer plugin crashes before it has processed the value. Use + * OrthancPluginReserveQueueValue() and OrthancPluginAcknowledgeQueueValue() if possible. + **/ ORTHANC_PLUGIN_SINCE_SDK("1.12.8") ORTHANC_PLUGIN_DEPRECATED ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginDequeueValue( OrthancPluginContext* context, @@ -10442,7 +10633,7 @@ } _OrthancPluginGetQueueSize; /** - * @brief Get the number of elements in a queue. + * @brief Get the number of elements that are currently stored in a queue. * * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). * @param queueId A unique identifier identifying both the plugin and the queue. @@ -10721,11 +10912,11 @@ } _OrthancPluginReserveQueueValue; /** - * @brief Reserve a value from a queue which means the message is not - * available for other consumers. + * @brief Reserve a value from a queue, which means that the message is not + * available to other consumers. * The value is either: - * - removed from the queue when one calls OrthancPluginAcknowledgeQueueValue - * - or made available again for consumers after the releaseTimeout has expired + * - removed from the queue when one calls OrthancPluginAcknowledgeQueueValue() + * - or made available again for consumers after the releaseTimeout has expired. * * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). * @param found Pointer to a Boolean that is set to "true" iff. a value has been dequeued. @@ -10733,10 +10924,12 @@ * It must be freed with OrthancPluginFreeMemoryBuffer(). * @param queueId A unique identifier identifying both the plugin and the queue. * @param origin The position from where the value is dequeued (back for LIFO, front for FIFO). - * @param releaseTimeout Timeout in seconds. If the value is not acknowledge within this - * timeout, the value is automatically released and made available for further - * calls to OrthancPluginDequeueValue or OrthancPluginReserveQueueValue - * @param valueId An opaque id to use in OrthancPluginAcknowledgeQueueValue + * @param releaseTimeout Timeout in seconds. If the value is not acknowledged before this delay expires, + * the value is automatically released and made available for further calls to + * OrthancPluginDequeueValue() or OrthancPluginReserveQueueValue(). + * This value cannot be equal to 0 second. + * @param valueId An opaque identifier for this value, to be subsequently provided to + * OrthancPluginAcknowledgeQueueValue(). * @return 0 if success, other value if error. **/ ORTHANC_PLUGIN_SINCE_SDK("1.12.10") @@ -10767,11 +10960,12 @@ } _OrthancPluginAcknowledgeQueueValue; /** - * @brief Acknowledge that a queue value has been consumed and definitely removes it from the queue. + * @brief Acknowledge that a queue value has been properly consumed by the plugin + * and can be permanently removed from the queue. * * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). * @param queueId A unique identifier identifying both the plugin and the queue. - * @param valueId An opaque id obtained from OrthancPluginReserveQueueValue + * @param valueId The opaque identifier for the value provided by OrthancPluginReserveQueueValue(). * @return 0 if success, other value if error. **/ ORTHANC_PLUGIN_SINCE_SDK("1.12.10") @@ -10785,7 +10979,206 @@ params.valueId = valueId; return context->InvokeService(context, _OrthancPluginService_AcknowledgeQueueValue, ¶ms); - } + } + + /** + * @brief Get the remote AET of a DICOM connection. + * + * This function returns the Application Entity Title (AET) of the + * DICOM modality from which a DICOM connection originates. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param connection The connection of interest. + * @return The pointer to the AET, NULL in case of error. + * @ingroup DicomConnection + **/ + ORTHANC_PLUGIN_SINCE_SDK("1.12.10") + ORTHANC_PLUGIN_INLINE const char* OrthancPluginGetConnectionRemoteAet( + OrthancPluginContext* context, + const OrthancPluginDicomConnection* connection) + { + const char* result; + + _OrthancPluginAccessDicomConnection params; + memset(¶ms, 0, sizeof(params)); + params.resultString = &result; + params.connection = connection; + + if (context->InvokeService(context, _OrthancPluginService_GetConnectionRemoteAet, ¶ms) != OrthancPluginErrorCode_Success) + { + /* Error */ + return NULL; + } + else + { + return result; + } + } + + + /** + * @brief Get the remote IP of a DICOM connection. + * + * This function returns the IP of the + * DICOM modality from which a DICOM connection originates. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param connection The connection of interest. + * @return The pointer to the IP, NULL in case of error. + * @ingroup DicomConnection + **/ + ORTHANC_PLUGIN_SINCE_SDK("1.12.10") + ORTHANC_PLUGIN_INLINE const char* OrthancPluginGetConnectionRemoteIp( + OrthancPluginContext* context, + const OrthancPluginDicomConnection* connection) + { + const char* result; + + _OrthancPluginAccessDicomConnection params; + memset(¶ms, 0, sizeof(params)); + params.resultString = &result; + params.connection = connection; + + if (context->InvokeService(context, _OrthancPluginService_GetConnectionRemoteIp, ¶ms) != OrthancPluginErrorCode_Success) + { + /* Error */ + return NULL; + } + else + { + return result; + } + } + + + /** + * @brief Get the called AET of a DICOM connection. + * + * This function returns the Orthanc called AET that a + * DICOM modality has used in a DICOM connection. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param connection The connection of interest. + * @return The pointer to the called AET, NULL in case of error. + * @ingroup DicomConnection + **/ + ORTHANC_PLUGIN_SINCE_SDK("1.12.10") + ORTHANC_PLUGIN_INLINE const char* OrthancPluginGetConnectionCalledAet( + OrthancPluginContext* context, + const OrthancPluginDicomConnection* connection) + { + const char* result; + + _OrthancPluginAccessDicomConnection params; + memset(¶ms, 0, sizeof(params)); + params.resultString = &result; + params.connection = connection; + + if (context->InvokeService(context, _OrthancPluginService_GetConnectionCalledAet, ¶ms) != OrthancPluginErrorCode_Success) + { + /* Error */ + return NULL; + } + else + { + return result; + } + } + + + ORTHANC_PLUGIN_SINCE_SDK("1.12.10") + typedef struct + { + OrthancPluginFindCallback2 callback; + } _OrthancPluginFindCallback2; + + /** + * @brief Register a callback to handle C-Find requests (v2). + * + * This function registers a callback to handle C-Find SCP requests + * that are not related to modality worklists. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param callback The callback. + * @return 0 if success, other value if error. + * @ingroup DicomCallbacks + **/ + ORTHANC_PLUGIN_SINCE_SDK("1.12.10") + ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginRegisterFindCallback2( + OrthancPluginContext* context, + OrthancPluginFindCallback2 callback) + { + _OrthancPluginFindCallback2 params; + params.callback = callback; + + return context->InvokeService(context, _OrthancPluginService_RegisterFindCallback2, ¶ms); + } + + ORTHANC_PLUGIN_SINCE_SDK("1.12.10") + typedef struct + { + OrthancPluginMoveCallback2 callback; + OrthancPluginGetMoveSize getMoveSize; + OrthancPluginApplyMove applyMove; + OrthancPluginFreeMove freeMove; + } _OrthancPluginMoveCallback2; + + /** + * @brief Register a callback to handle C-Move requests (v2). + * + * This function registers a callback to handle C-Move SCP requests. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param callback The main callback. + * @param getMoveSize Callback to read the number of C-Move suboperations. + * @param applyMove Callback to apply one C-Move suboperation. + * @param freeMove Callback to free the C-Move driver. + * @return 0 if success, other value if error. + * @ingroup DicomCallbacks + **/ + ORTHANC_PLUGIN_SINCE_SDK("1.12.10") + ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginRegisterMoveCallback2( + OrthancPluginContext* context, + OrthancPluginMoveCallback2 callback, + OrthancPluginGetMoveSize getMoveSize, + OrthancPluginApplyMove applyMove, + OrthancPluginFreeMove freeMove) + { + _OrthancPluginMoveCallback2 params; + params.callback = callback; + params.getMoveSize = getMoveSize; + params.applyMove = applyMove; + params.freeMove = freeMove; + + return context->InvokeService(context, _OrthancPluginService_RegisterMoveCallback2, ¶ms); + } + + /** + * @brief Register a callback to handle incoming requests to the storage commitment SCP (v2). + * + * This function registers a callback to handle storage commitment SCP requests. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param factory Factory function that creates the handler object + * for incoming storage commitment requests. + * @param destructor Destructor function to destroy the handler object. + * @param lookup Callback function to get the status of one DICOM instance. + * @return 0 if success, other value if error. + * @ingroup DicomCallbacks + **/ + ORTHANC_PLUGIN_SINCE_SDK("1.12.10") + ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginRegisterStorageCommitmentScpCallback2( + OrthancPluginContext* context, + OrthancPluginStorageCommitmentFactory2 factory, + OrthancPluginStorageCommitmentDestructor destructor, + OrthancPluginStorageCommitmentLookup lookup) + { + _OrthancPluginRegisterStorageCommitmentScpCallback2 params; + params.factory = factory; + params.destructor = destructor; + params.lookup = lookup; + return context->InvokeService(context, _OrthancPluginService_RegisterStorageCommitmentScpCallback2, ¶ms); + } #ifdef __cplusplus }
--- a/Sources/DicomScpCallbacks.cpp Mon Oct 13 09:24:40 2025 +0200 +++ b/Sources/DicomScpCallbacks.cpp Wed Nov 26 06:56:59 2025 +0100 @@ -36,6 +36,7 @@ static PyObject* findScpCallback_ = NULL; +static PyObject* findScpCallback2_ = NULL; static PyObject* moveScpCallback_ = NULL; static PyObject* worklistScpCallback_ = NULL; @@ -45,6 +46,9 @@ static PyObject* applyMoveCallback_ = NULL; static PyObject* freeMoveCallback_ = NULL; +// version 3 of Python Move callbacks (version 2 in the C SDK) +static PyObject* createMoveScpDriverCallback2_ = NULL; + static PyObject *GetFindQueryTag(sdk_OrthancPluginFindQuery_Object* self, PyObject *args, @@ -143,6 +147,55 @@ } +static OrthancPluginErrorCode FindCallback2(OrthancPluginFindAnswers *answers, + const OrthancPluginFindQuery *query, + const OrthancPluginDicomConnection* connection) +{ + try + { + PythonLock lock; + + PyObject *pAnswers, *pQuery, *pConnection; + + { + PythonObject args(lock, PyTuple_New(2)); + PyTuple_SetItem(args.GetPyObject(), 0, PyLong_FromSsize_t((intptr_t) answers)); + PyTuple_SetItem(args.GetPyObject(), 1, PyBool_FromLong(true /* borrowed, don't destruct */)); + pAnswers = PyObject_CallObject((PyObject *) GetOrthancPluginFindAnswersType(), args.GetPyObject()); + } + + { + PythonObject args(lock, PyTuple_New(2)); + PyTuple_SetItem(args.GetPyObject(), 0, PyLong_FromSsize_t((intptr_t) query)); + PyTuple_SetItem(args.GetPyObject(), 1, PyBool_FromLong(true /* borrowed, don't destruct */)); + pQuery = PyObject_CallObject((PyObject *) GetOrthancPluginFindQueryType(), args.GetPyObject()); + } + + { + PythonObject argsConnection(lock, PyTuple_New(2)); + PyTuple_SetItem(argsConnection.GetPyObject(), 0, PyLong_FromSsize_t((intptr_t) connection)); + PyTuple_SetItem(argsConnection.GetPyObject(), 1, PyBool_FromLong(true /* borrowed, don't destruct */)); + pConnection = PyObject_CallObject((PyObject*) GetOrthancPluginDicomConnectionType(), argsConnection.GetPyObject()); + } + + { + PythonObject args(lock, PyTuple_New(3)); + PyTuple_SetItem(args.GetPyObject(), 0, pAnswers); + PyTuple_SetItem(args.GetPyObject(), 1, pQuery); + PyTuple_SetItem(args.GetPyObject(), 2, pConnection); + + assert(findScpCallback2_ != NULL); + PythonObject result(lock, PyObject_CallObject(findScpCallback2_, args.GetPyObject())); + } + + return lock.CheckCallbackSuccess("Python C-FIND SCP callback (v2)"); + } + catch (OrthancPlugins::PluginException& e) + { + return e.GetErrorCode(); + } +} + class IMoveDriver : public boost::noncopyable { @@ -320,7 +373,7 @@ assert(moveScpCallback_ != NULL); PythonObject result(lock, PyObject_Call(moveScpCallback_, args.GetPyObject(), kw.GetPyObject())); - OrthancPluginErrorCode code = lock.CheckCallbackSuccess("Python C-MOVE SCP callback"); + OrthancPluginErrorCode code = lock.CheckCallbackSuccess("Python C-MOVE SCP callback (v1)"); if (code != OrthancPluginErrorCode_Success) { throw OrthancPlugins::PluginException(code); @@ -512,7 +565,157 @@ // to delete the object at the end of the move. PyObject* result = PyObject_Call(createMoveScpDriverCallback_, args.GetPyObject(), kw.GetPyObject()); - OrthancPluginErrorCode code = lock.CheckCallbackSuccess("Python C-MOVE SCP callback (Create)"); + OrthancPluginErrorCode code = lock.CheckCallbackSuccess("Python C-MOVE SCP callback (Create v2)"); + if (code != OrthancPluginErrorCode_Success) + { + throw OrthancPlugins::PluginException(code); + } + + return result; + } + catch (OrthancPlugins::PluginException& e) + { + return NULL; + } +} + + +static void* CreateMoveCallback3(OrthancPluginResourceType resourceType, + const char *patientId, + const char *accessionNumber, + const char *studyInstanceUid, + const char *seriesInstanceUid, + const char *sopInstanceUid, + const OrthancPluginDicomConnection* connection, + const char *targetAet, + uint16_t originatorId) +{ + assert(createMoveScpDriverCallback2_ != NULL); + + try + { + std::string _patientId, _accessionNumber, _studyInstanceUid, _seriesInstanceUid, _sopInstanceUid, _targetAet; + if (patientId != NULL) + { + _patientId.assign(patientId); + } + + if (accessionNumber != NULL) + { + _accessionNumber.assign(accessionNumber); + } + + if (studyInstanceUid != NULL) + { + _studyInstanceUid.assign(studyInstanceUid); + } + + if (seriesInstanceUid != NULL) + { + _seriesInstanceUid.assign(seriesInstanceUid); + } + + if (sopInstanceUid != NULL) + { + _sopInstanceUid.assign(sopInstanceUid); + } + + if (targetAet != NULL) + { + _targetAet.assign(targetAet); + } + + PythonLock lock; + + PythonObject kw(lock, PyDict_New()); + + std::string level; + switch (resourceType) + { + case OrthancPluginResourceType_Patient: + level = "PATIENT"; + break; + + case OrthancPluginResourceType_Study: + level = "STUDY"; + break; + + case OrthancPluginResourceType_Series: + level = "SERIES"; + break; + + case OrthancPluginResourceType_Instance: + level = "INSTANCE"; + break; + + default: + throw OrthancPlugins::PluginException(OrthancPluginErrorCode_ParameterOutOfRange); + } + + { + PythonString tmp(lock, level); + PyDict_SetItemString(kw.GetPyObject(), "Level", tmp.GetPyObject()); + } + + { + PythonString tmp(lock, _patientId); + PyDict_SetItemString(kw.GetPyObject(), "PatientID", tmp.GetPyObject()); + } + + { + PythonString tmp(lock, _accessionNumber); + PyDict_SetItemString(kw.GetPyObject(), "AccessionNumber", tmp.GetPyObject()); + } + + { + PythonString tmp(lock, _studyInstanceUid); + PyDict_SetItemString(kw.GetPyObject(), "StudyInstanceUID", tmp.GetPyObject()); + } + + { + PythonString tmp(lock, _seriesInstanceUid); + PyDict_SetItemString(kw.GetPyObject(), "SeriesInstanceUID", tmp.GetPyObject()); + } + + { + PythonString tmp(lock, _sopInstanceUid); + PyDict_SetItemString(kw.GetPyObject(), "SOPInstanceUID", tmp.GetPyObject()); + } + + { + PythonString tmp(lock, _targetAet); + PyDict_SetItemString(kw.GetPyObject(), "TargetAET", tmp.GetPyObject()); + } + + { + PythonObject tmp(lock, PyLong_FromUnsignedLong(originatorId)); + PyDict_SetItemString(kw.GetPyObject(), "OriginatorID", tmp.GetPyObject()); + } + + /** + * Construct an instance object of the "orthanc.DicomConnection" + * class. This is done by calling the constructor function + * "sdk_OrthancPluginDicomConnection_Type". + **/ + PythonObject argsConnection(lock, PyTuple_New(2)); + PyTuple_SetItem(argsConnection.GetPyObject(), 0, PyLong_FromSsize_t((intptr_t) connection)); + PyTuple_SetItem(argsConnection.GetPyObject(), 1, PyBool_FromLong(true /* borrowed, don't destruct */)); + PyObject *pConnection = PyObject_CallObject((PyObject*) GetOrthancPluginDicomConnectionType(), argsConnection.GetPyObject()); + + /** + * Construct the arguments tuple (instance) + **/ + PythonObject args(lock, PyTuple_New(1)); + PyTuple_SetItem(args.GetPyObject(), 0, pConnection); + + + // Note: the result is not attached to the PythonLock because we want it to survive after this call since + // the result is the python move driver that will be passed as first argument to GetMoveSize, Apply and Free. + // After the PyObject_Call, result's ref count is 1 -> no need to add a reference but we need to decref explicitely + // to delete the object at the end of the move. + PyObject* result = PyObject_Call(createMoveScpDriverCallback2_, args.GetPyObject(), kw.GetPyObject()); + + OrthancPluginErrorCode code = lock.CheckCallbackSuccess("Python C-MOVE SCP callback (Create v3)"); if (code != OrthancPluginErrorCode_Success) { throw OrthancPlugins::PluginException(code); @@ -659,6 +862,25 @@ } +PyObject* RegisterFindCallback2(PyObject* module, PyObject* args) +{ + // The GIL is locked at this point (no need to create "PythonLock") + + class Registration : public ICallbackRegistration + { + public: + virtual void Register() ORTHANC_OVERRIDE + { + OrthancPluginRegisterFindCallback2(OrthancPlugins::GetGlobalContext(), FindCallback2); + } + }; + + Registration registration; + return ICallbackRegistration::Apply( + registration, args, findScpCallback2_, "Python C-FIND SCP callback (v2)"); +} + + PyObject* RegisterMoveCallback(PyObject* module, PyObject* args) { // The GIL is locked at this point (no need to create "PythonLock") @@ -695,9 +917,30 @@ Registration registration; return ICallbackRegistration::Apply4( - registration, args, createMoveScpDriverCallback_, getMoveSizeCallback_, applyMoveCallback_, freeMoveCallback_, "Python C-MOVE SCP callback (2)"); + registration, args, createMoveScpDriverCallback_, getMoveSizeCallback_, applyMoveCallback_, freeMoveCallback_, "Python C-MOVE SCP callback (v2)"); } + +PyObject* RegisterMoveCallback3(PyObject* module, PyObject* args) +{ + // The GIL is locked at this point (no need to create "PythonLock") + + class Registration : public ICallbackRegistration + { + public: + virtual void Register() ORTHANC_OVERRIDE + { + OrthancPluginRegisterMoveCallback2( + OrthancPlugins::GetGlobalContext(), CreateMoveCallback3, GetMoveSize2, ApplyMove2, FreeMove2); + } + }; + + Registration registration; + return ICallbackRegistration::Apply4( + registration, args, createMoveScpDriverCallback2_, getMoveSizeCallback_, applyMoveCallback_, freeMoveCallback_, "Python C-MOVE SCP callback (v3)"); +} + + PyObject* RegisterWorklistCallback(PyObject* module, PyObject* args) { // The GIL is locked at this point (no need to create "PythonLock") @@ -720,10 +963,12 @@ void FinalizeDicomScpCallbacks() { ICallbackRegistration::Unregister(findScpCallback_); + ICallbackRegistration::Unregister(findScpCallback2_); ICallbackRegistration::Unregister(moveScpCallback_); ICallbackRegistration::Unregister(worklistScpCallback_); ICallbackRegistration::Unregister(createMoveScpDriverCallback_); + ICallbackRegistration::Unregister(createMoveScpDriverCallback2_); ICallbackRegistration::Unregister(getMoveSizeCallback_); ICallbackRegistration::Unregister(applyMoveCallback_); ICallbackRegistration::Unregister(freeMoveCallback_);
