Repository: pydicom/pydicom

Issue:
Allow to search a list of elements in a `FileSet` while only loading instances once, to drastically improve execution time
**Is your feature request related to a problem? Please describe.**
Currently, `fileset.FileSet.find_values` only allows for elements to be searched for one at a time. When executing this action while setting `load` to `True`, this results in a substantial overhead.

**Describe the solution you'd like**
The following example code allows loading the instances once, and iterating over a list of elements to find:
```python
def find_values_quick(self, elements, instances=None):
    results = {element: [] for element in elements}
    instances = instances or iter(self)
    for instance in instances:
        instance = instance.load()
        for element in elements:
            if element not in instance:
                continue
            val = instance[element].value
            if val not in results[element]:
                results[element].append(val)
    return results
```


Discussion:
Sounds good, do you want to do the PR? Just change `FileSet.find_values` to support an element or iterable of elements.

Patch:
diff --git a/pydicom/fileset.py b/pydicom/fileset.py
--- a/pydicom/fileset.py
+++ b/pydicom/fileset.py
@@ -1486,16 +1486,16 @@ def match(ds, **kwargs):
 
     def find_values(
         self,
-        element: Union[str, int],
+        elements: Union[str, int, List[Union[str, int]]],
         instances: Optional[List[FileInstance]] = None,
         load: bool = False
-    ) -> List[Any]:
-        """Return a list of unique values for a given element.
+    ) -> Union[List[Any], Dict[Union[str, int], List]]:
+        """Return a list of unique values for given element(s).
 
         Parameters
         ----------
-        element : str, int or pydicom.tag.BaseTag
-            The keyword or tag of the element to search for.
+        elements : str, int or pydicom.tag.BaseTag, or list of these
+            The keyword or tag of the element(s) to search for.
         instances : list of pydicom.fileset.FileInstance, optional
             Search within the given instances. If not used then all available
             instances will be searched.
@@ -1508,32 +1508,41 @@ def find_values(
 
         Returns
         -------
-        list of object
-            A list of value(s) for the element available in the instances.
+        list of object(s), or dict of lists of object(s)
+
+            * If single element was queried: A list of value(s) for the element
+              available in the instances.
+            * If list of elements was queried: A dict of element value pairs
+              with lists of value(s) for the elements available in the instances.
         """
-        has_element = False
-        results = []
+        element_list = elements if isinstance(elements, list) else [elements]
+        has_element = {element: False for element in element_list}
+        results: Dict = {element: [] for element in element_list}
         iter_instances = instances or iter(self)
         instance: Union[Dataset, FileInstance]
         for instance in iter_instances:
             if load:
                 instance = instance.load()
-
-            if element not in instance:
-                continue
-
-            has_element = True
-            val = instance[element].value
-            # Not very efficient, but we can't use set
-            if val not in results:
-                results.append(val)
-
-        if not load and not has_element:
+            for element in element_list:
+                if element not in instance:
+                    continue
+
+                has_element[element] = True
+                val = instance[element].value
+                # Not very efficient, but we can't use set
+                if val not in results[element]:
+                    results[element].append(val)
+        missing_elements = [
+            element for element, v in has_element.items() if not v
+        ]
+        if not load and missing_elements:
             warnings.warn(
                 "None of the records in the DICOMDIR dataset contain "
-                "the query element, consider using the 'load' parameter "
+                f"{missing_elements}, consider using the 'load' parameter "
                 "to expand the search to the corresponding SOP instances"
             )
+        if not isinstance(elements, list):
+            return results[element_list[0]]
 
         return results
 


Test patch:
diff --git a/pydicom/tests/test_fileset.py b/pydicom/tests/test_fileset.py
--- a/pydicom/tests/test_fileset.py
+++ b/pydicom/tests/test_fileset.py
@@ -1945,33 +1945,45 @@ def test_find_load(self, private):
     def test_find_values(self, private):
         """Test searching the FileSet for element values."""
         fs = FileSet(private)
-        assert ['77654033', '98890234'] == fs.find_values("PatientID")
-        assert (
-            [
+        expected = {
+            "PatientID": ['77654033', '98890234'],
+            "StudyDescription": [
                 'XR C Spine Comp Min 4 Views',
                 'CT, HEAD/BRAIN WO CONTRAST',
                 '',
                 'Carotids',
                 'Brain',
-                'Brain-MRA'
-            ] == fs.find_values("StudyDescription")
-        )
+                'Brain-MRA',
+            ],
+        }
+        for k, v in expected.items():
+            assert fs.find_values(k) == v
+        assert fs.find_values(list(expected.keys())) == expected
 
     def test_find_values_load(self, private):
         """Test FileSet.find_values(load=True)."""
         fs = FileSet(private)
+        search_element = "PhotometricInterpretation"
         msg = (
             r"None of the records in the DICOMDIR dataset contain "
-            r"the query element, consider using the 'load' parameter "
+            fr"\['{search_element}'\], consider using the 'load' parameter "
             r"to expand the search to the corresponding SOP instances"
         )
         with pytest.warns(UserWarning, match=msg):
-            results = fs.find_values("PhotometricInterpretation", load=False)
+            results = fs.find_values(search_element, load=False)
             assert not results
 
-        assert ['MONOCHROME1', 'MONOCHROME2'] == fs.find_values(
-            "PhotometricInterpretation", load=True
-        )
+        assert fs.find_values(search_element, load=True) == [
+            'MONOCHROME1', 'MONOCHROME2'
+        ]
+
+        with pytest.warns(UserWarning, match=msg):
+            results = fs.find_values([search_element], load=False)
+            assert not results[search_element]
+
+        assert (
+            fs.find_values([search_element], load=True)
+        ) == {search_element: ['MONOCHROME1', 'MONOCHROME2']}
 
     def test_empty_file_id(self, dicomdir):
         """Test loading a record with an empty File ID."""