|
37 | 37 |
|
38 | 38 | logger = logging.getLogger(__name__) |
39 | 39 |
|
| 40 | +# For DICOM Standard spec validation of UID components in `DICOMwebClient`. |
| 41 | +_REGEX_UID = re.compile(r'[0-9]+([.][0-9]+)*') |
| 42 | +_REGEX_PERMISSIVE_UID = re.compile(r'[^/@]+') |
| 43 | + |
40 | 44 |
|
41 | 45 | def _load_xml_dataset(dataset: Element) -> pydicom.dataset.Dataset: |
42 | 46 | """Load DICOM Data Set in DICOM XML format. |
@@ -200,7 +204,8 @@ def __init__( |
200 | 204 | proxies: Optional[Dict[str, str]] = None, |
201 | 205 | headers: Optional[Dict[str, str]] = None, |
202 | 206 | callback: Optional[Callable] = None, |
203 | | - chunk_size: int = 10**6 |
| 207 | + chunk_size: int = 10**6, |
| 208 | + permissive: bool = False |
204 | 209 | ) -> None: |
205 | 210 | """Instatiate client. |
206 | 211 |
|
@@ -235,6 +240,14 @@ def __init__( |
235 | 240 | when streaming data from the server using chunked transfer encoding |
236 | 241 | (used by ``iter_*()`` methods as well as the ``store_instances()`` |
237 | 242 | method); defaults to ``10**6`` bytes (1MB) |
| 243 | + permissive: bool, optional |
| 244 | + If ``True``, relaxes the DICOM Standard validation for UIDs (see |
| 245 | + main docstring for details). This option is made available since |
| 246 | + users may be occasionally forced to work with DICOMs or services |
| 247 | + that may be in violation of the standard. Unless required, use of |
| 248 | + this flag is **not** recommended, since non-conformant UIDs may |
| 249 | + lead to unexpected errors downstream, e.g., rejection by a DICOMweb |
| 250 | + server, etc. |
238 | 251 |
|
239 | 252 | Warning |
240 | 253 | ------- |
@@ -314,6 +327,7 @@ def __init__( |
314 | 327 | if callback is not None: |
315 | 328 | self._session.hooks = {'response': [callback, ]} |
316 | 329 | self._chunk_size = chunk_size |
| 330 | + self._permissive = permissive |
317 | 331 | self.set_http_retry_params() |
318 | 332 |
|
319 | 333 | def _get_transaction_url(self, transaction: _Transaction) -> str: |
@@ -2176,15 +2190,18 @@ def _assert_uid_format(self, uid: str) -> None: |
2176 | 2190 | TypeError |
2177 | 2191 | When `uid` is not a string |
2178 | 2192 | ValueError |
2179 | | - When `uid` doesn't match the regular expression pattern |
2180 | | - ``"^[.0-9]+$"`` |
| 2193 | + When `uid` doesn't match the regular expression pattern for |
| 2194 | + DICOM UIDs (strict or permissive regex) |
2181 | 2195 |
|
2182 | 2196 | """ |
2183 | 2197 | if not isinstance(uid, str): |
2184 | 2198 | raise TypeError('DICOM UID must be a string.') |
2185 | | - pattern = re.compile('^[.0-9]+$') |
2186 | | - if not pattern.search(uid): |
2187 | | - raise ValueError('DICOM UID has invalid format.') |
| 2199 | + if not self._permissive and _REGEX_UID.fullmatch(uid) is None: |
| 2200 | + raise ValueError(f'UID {uid!r} must match regex {_REGEX_UID!r} in ' |
| 2201 | + 'conformance with the DICOM Standard.') |
| 2202 | + elif self._permissive and _REGEX_PERMISSIVE_UID.fullmatch(uid) is None: |
| 2203 | + raise ValueError(f'Permissive mode is enabled. UID {uid!r} must match ' |
| 2204 | + f'regex {_REGEX_PERMISSIVE_UID!r}.') |
2188 | 2205 |
|
2189 | 2206 | def search_for_series( |
2190 | 2207 | self, |
|
0 commit comments