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, &params);
+  }
+
   
   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, &params);
-  }  
+  }
+
+  /**
+   * @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(&params, 0, sizeof(params));
+    params.resultString = &result;
+    params.connection = connection;
+
+    if (context->InvokeService(context, _OrthancPluginService_GetConnectionRemoteAet, &params) != 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(&params, 0, sizeof(params));
+    params.resultString = &result;
+    params.connection = connection;
+
+    if (context->InvokeService(context, _OrthancPluginService_GetConnectionRemoteIp, &params) != 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(&params, 0, sizeof(params));
+    params.resultString = &result;
+    params.connection = connection;
+
+    if (context->InvokeService(context, _OrthancPluginService_GetConnectionCalledAet, &params) != 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, &params);
+  }
+
+  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, &params);
+  }
+
+  /**
+   * @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, &params);
+  }
 
 #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_);