changeset 939:0f5f43ac6876

new tests for C-find with private tags and sequences
author Alain Mazy <am@orthanc.team>
date Thu, 02 Jul 2026 16:28:07 +0200
parents 6b5a76308918
children e6666a9679c3 3a440ee47213
files Database/ZeissOCT-cfind-query.dcm Database/ZeissOCT-cfind-query.txt Database/ZeissOCT.dcm GenerateConfigurationForTests.py Tests/Tests.py
diffstat 5 files changed, 134 insertions(+), 0 deletions(-) [+]
line wrap: on
line diff
Binary file Database/ZeissOCT-cfind-query.dcm has changed
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Database/ZeissOCT-cfind-query.txt	Thu Jul 02 16:28:07 2026 +0200
@@ -0,0 +1,116 @@
+# The ZeissOCT-cfind-query.dcm was generated with this code
+
+
+from pydicom.dataset import Dataset
+from pydicom.datadict import add_private_dict_entries
+
+from pynetdicom import AE
+from pynetdicom.sop_class import StudyRootQueryRetrieveInformationModelFind
+
+# ---------------------------------------------------------------------------
+# Custom private dictionary (same as Orthanc's ORTHANC__DICTIONARY / the
+# dummy SCP), so the logged request/response shows resolved private tag
+# names instead of "Private tag data" / "??"
+# ---------------------------------------------------------------------------
+add_private_dict_entries(
+    "99CZM_CapeCod_OctSettings",
+    {
+        0x04051001: ("CS", "1", "PatternType"),
+        0x0405101A: ("FL", "1", "SignalStrength"),
+    },
+)
+
+add_private_dict_entries(
+    "99CZM_CapeCod_OctGeneral",
+    {
+        0x04071001: ("US", "1", "UndefinedPrivateTag1001"),
+        0x04071002: ("US", "1", "UndefinedPrivateTag1002"),
+        0x04071003: ("US", "1", "UndefinedPrivateTag1003"),
+        0x04071005: ("SQ", "1", "OCTCubeSequence2"),
+        0x040710A1: ("SQ", "1", "OCTCubeSequence"),
+    },
+)
+
+add_private_dict_entries(
+    "99CZM_FORUM",
+    {
+        0x23011002: ("CS", "1", "Favorite"),
+    },
+)
+
+def build_query_dataset():
+    """
+    Builds the identifier dataset exactly as captured from the real
+    CIRRUS C-FIND request.
+    """
+    ds = Dataset()
+
+    ds.SpecificCharacterSet = "ISO_IR 192"
+    ds.QueryRetrieveLevel = "IMAGE"
+
+    # Return keys (empty)
+    ds.ImageType = ""
+    ds.InstanceCreationDate = ""
+    ds.InstanceCreationTime = ""
+    ds.SOPClassUID = ""
+    ds.SOPInstanceUID = ""
+    ds.StudyDate = ""
+    ds.SeriesDate = ""
+    ds.AcquisitionDateTime = ""
+    ds.StudyTime = ""
+    ds.SeriesTime = ""
+    ds.AccessionNumber = ""
+    ds.Modality = ""
+    ds.ModalitiesInStudy = ""
+    ds.ReferringPhysicianName = ""
+    ds.StudyDescription = ""
+    ds.SeriesDescription = ""
+    ds.PerformingPhysicianName = ""
+    ds.AdmittingDiagnosesDescription = ""
+    ds.ManufacturerModelName = ""
+
+    # ReferencedInstanceSequence — sequence containing one empty item
+    ds.ReferencedInstanceSequence = [Dataset()]
+
+    # Matching keys (actual values from the captured request)
+    ds.PatientName = ""
+    ds.PatientID = "Zeiss*"
+    ds.IssuerOfPatientID = ""
+    ds.PatientBirthDate = "19790101-20300101"
+    ds.PatientSex = ""
+
+    ds.add_new(0x00101000, "LO", "")  # OtherPatientIDs
+    ds.add_new(0x00102160, "SH", "")  # EthnicGroup
+    ds.PatientComments = ""
+
+    ds.StudyInstanceUID = ""
+    ds.SeriesInstanceUID = ""
+    ds.StudyID = ""
+    ds.SeriesNumber = ""
+    ds.InstanceNumber = ""
+    ds.Laterality = ""
+    ds.ImageLaterality = ""
+
+    ds.PerformedProcedureStepStartDate = ""
+    ds.PerformedProcedureStepStartTime = ""
+
+    # RequestAttributesSequence — sequence containing one empty item
+    ds.RequestAttributesSequence = [Dataset()]
+
+    ds.add_new(0x00420010, "ST", "")  # DocumentTitle
+
+    # --- Private tags: 99CZM_CapeCod_OctSettings block (group 0x0405) ---
+    block_0405 = ds.private_block(0x0405, "99CZM_CapeCod_OctSettings", create=True)
+    block_0405.add_new(0x01, "CS", "")     # (0405,1001) PatternType
+    block_0405.add_new(0x1a, "FL", None)   # (0405,101a) SignalStrength
+
+    # --- Private tags: 99CZM_CapeCod_OctGeneral block (group 0x0407) ---
+    block_0407 = ds.private_block(0x0407, "99CZM_CapeCod_OctGeneral", create=True)
+    seq_tag = block_0407.get_tag(0xa1)
+    ds.add_new(seq_tag, "SQ", [Dataset()])  # (0407,10a1) OCTCubeSequence, 1 empty item
+
+    # --- Private tags: 99CZM_FORUM block (group 0x2301) ---
+    block_2301 = ds.private_block(0x2301, "99CZM_FORUM", create=True)
+    block_2301.add_new(0x02, "CS", "")     # (2301,1002) Favorite
+    ds.save_as('ZeissOCT-cfind-query.dcm', implicit_vr=False)
+    return ds
Binary file Database/ZeissOCT.dcm has changed
--- a/GenerateConfigurationForTests.py	Wed Jul 01 16:42:19 2026 +0200
+++ b/GenerateConfigurationForTests.py	Thu Jul 02 16:28:07 2026 +0200
@@ -180,6 +180,14 @@
     '4321,1012' : [ 'LO', 'RadioButton3', 1, 1, 'RadioLogic' ],     # For issue 140
     '0009,1001' : [ 'DS', 'Abnormality score', 1, 1, 'Lunit' ],     # For issue 168
     '0009,0010' : [ 'LO', 'Private data element', 1, 1, 'Lunit' ],  # For issue 168
+    "0405,1001" : ["CS", "PatternType", 1, 1, "99CZM_CapeCod_OctSettings" ],    # For ZeissOCT related tests
+    "0405,101a" : ["FL", "SignalStrength", 1, 1, "99CZM_CapeCod_OctSettings" ],
+    "0407,1001" : ["US", "UndefinedPrivateTag1001", 1, 1, "99CZM_CapeCod_OctGeneral" ],
+    "0407,1002" : ["US", "UndefinedPrivateTag1002", 1, 1, "99CZM_CapeCod_OctGeneral" ],
+    "0407,1003" : ["US", "UndefinedPrivateTag1003", 1, 1, "99CZM_CapeCod_OctGeneral" ],
+    "0407,1005" : ["SQ", "OCTCubeSequence2", 1, 1, "99CZM_CapeCod_OctGeneral" ], 
+    "0407,10a1" : ["SQ", "OCTCubeSequence", 1, 1, "99CZM_CapeCod_OctGeneral" ], 
+    "2301,1002" : ["CS", "Favorite", 1, 1, "99CZM_FORUM" ]
 }
 
 config['UserMetadata'] = {
--- a/Tests/Tests.py	Wed Jul 01 16:42:19 2026 +0200
+++ b/Tests/Tests.py	Thu Jul 02 16:28:07 2026 +0200
@@ -1956,6 +1956,16 @@
         sequences = re.findall(r'\(0018,1010\)', i)
         self.assertEqual(1, len(sequences))
 
+
+    def test_incoming_findscu_private_tags_and_sequences(self):
+        UploadInstance(_REMOTE, 'ZeissOCT.dcm')
+
+        if IsOrthancVersionAbove(_REMOTE, 1, 12, 12):  # this query requests a private sequence and a private tag that is not present in the data
+            i = CallFindScu([ GetDatabasePath('ZeissOCT-cfind-query.dcm') ])
+            self.assertIn('Find Response: 1', i)
+            sequences = re.findall(r'\(0407,1013\)', i)
+            self.assertEqual(128, len(sequences))
+
         
     def test_incoming_movescu(self):
         UploadInstance(_REMOTE, 'Multiframe.dcm')