@@ -48,7 +48,7 @@ class DICOMSeriesSelectorOperator(Operator):
4848 3. each condition uses the implicit equal operator; in addition, the following are supported:
4949 - regex, relational, and range matching for float and int types
5050 - regex matching for str type
51- - union matching for set type
51+ - inclusion and exclusion matching for set type
5252 - image orientation check for the ImageOrientationPatient tag
5353 4. DICOM attribute keywords are used, and only for those defined as DICOMStudy and DICOMSeries properties
5454
@@ -88,6 +88,14 @@ class DICOMSeriesSelectorOperator(Operator):
8888 "ImageOrientationPatient": "Axial",
8989 "SliceThickness": [2, ">"]
9090 }
91+ },
92+ {
93+ "name": "CT Series 5",
94+ "conditions": {
95+ "StudyDescription": "(.*?)",
96+ "Modality": "(?i)CT",
97+ "ImageType": ["PRIMARY", "!SECONDARY"]
98+ }
9199 }
92100 ]
93101 }
@@ -163,7 +171,7 @@ def filter(
163171 Supported matching logic:
164172 Float + Int: exact matching, relational matching, range matching, and regex matching
165173 String: matches case insensitive, if fails then tries RegEx search
166- String array (set): matches as subset , case insensitive
174+ String array (set): inclusive and exclusive (via !) matching as subsets , case insensitive
167175 ImageOrientationPatient tag: image orientation (Axial, Coronal, Sagittal) matching
168176
169177 Args:
@@ -326,9 +334,19 @@ def _select_series(
326334 meta_data_list = str (attr_value ).lower ()
327335 if isinstance (value_to_match , list ):
328336 value_set = {str (element ).lower () for element in value_to_match }
329- matched = all (val in meta_data_list for val in value_set )
337+ # split inclusion and exclusion matches using ! indicator
338+ include_terms = {v for v in value_set if not v .startswith ("!" )}
339+ exclude_terms = {v [1 :] for v in value_set if v .startswith ("!" )}
340+ matched = all (term in meta_data_list for term in include_terms ) and all (
341+ term not in meta_data_list for term in exclude_terms
342+ )
330343 elif isinstance (value_to_match , (str , numbers .Number )):
331- matched = str (value_to_match ).lower () in meta_data_list
344+ v = str (value_to_match ).lower ()
345+ # ! indicates exclusion match
346+ if v .startswith ("!" ):
347+ matched = v [1 :] not in meta_data_list
348+ else :
349+ matched = v in meta_data_list
332350 else :
333351 raise NotImplementedError (
334352 f"No support for matching condition { value_to_match } (type: { type (value_to_match )} )"
@@ -594,6 +612,14 @@ def test():
594612 "ImageOrientationPatient": "Axial",
595613 "SliceThickness": [2, ">"]
596614 }
615+ },
616+ {
617+ "name": "CT Series 5",
618+ "conditions": {
619+ "StudyDescription": "(.*?)",
620+ "Modality": "(?i)CT",
621+ "ImageType": ["PRIMARY", "!SECONDARY"]
622+ }
597623 }
598624 ]
599625}
0 commit comments