@@ -25,6 +25,7 @@ class URISuffix(enum.Enum):
2525# For DICOM Standard spec validation of UID components in `URI`.
2626_MAX_UID_LENGTH = 64
2727_REGEX_UID = re .compile (r'[0-9]+([.][0-9]+)*' )
28+ _REGEX_PERMISSIVE_UID = re .compile (r'[^/@]+' )
2829# Used for Project ID and Location validation in `GoogleCloudHealthcareURL`.
2930_REGEX_ID_1 = r'[\w-]+'
3031_ATTR_VALIDATOR_ID_1 = attr .validators .matches_re (_REGEX_ID_1 )
@@ -70,12 +71,15 @@ def __init__(self,
7071 series_instance_uid : Optional [str ] = None ,
7172 sop_instance_uid : Optional [str ] = None ,
7273 frames : Optional [Sequence [int ]] = None ,
73- suffix : Optional [URISuffix ] = None ):
74+ suffix : Optional [URISuffix ] = None ,
75+ permissive : bool = False ):
7476 """Instantiates an object.
7577
7678 As per the DICOM Standard, the Study, Series, and Instance UIDs must be
7779 a series of numeric components (``0``-``9``) separated by the period
7880 ``.`` character, with a maximum length of 64 characters.
81+ If the ``permissive`` flag is set to ``True``, any alpha-numeric or
82+ special characters (except for ``/`` and ``@``) may be used.
7983
8084 Parameters
8185 ----------
@@ -93,6 +97,14 @@ def __init__(self,
9397 suffix: URISuffix, optional
9498 Suffix attached to the DICOM resource URI. This could refer to a
9599 metadata, rendered, or thumbnail resource.
100+ permissive: bool
101+ If ``True``, relaxes the DICOM Standard validation for UIDs (see
102+ main docstring for details). This option is made available since
103+ users may be occasionally forced to work with DICOMs or services
104+ that may be in violation of the standard. Unless required, use of
105+ this flag is **not** recommended, since non-conformant UIDs may
106+ lead to unexpected errors downstream, e.g., rejection by a DICOMweb
107+ server, etc.
96108
97109 Raises
98110 ------
@@ -122,6 +134,7 @@ def __init__(self,
122134 """
123135 _validate_base_url (base_url )
124136 _validate_resource_identifiers_and_suffix (
137+ permissive ,
125138 study_instance_uid ,
126139 series_instance_uid ,
127140 sop_instance_uid ,
@@ -134,6 +147,7 @@ def __init__(self,
134147 self ._instance_uid = sop_instance_uid
135148 self ._frames = None if frames is None else tuple (frames )
136149 self ._suffix = suffix
150+ self ._permissive = permissive
137151
138152 def __str__ (self ) -> str :
139153 """Returns the object as a DICOMweb URI string."""
@@ -217,6 +231,11 @@ def type(self) -> URIType:
217231 return URIType .INSTANCE
218232 return URIType .FRAME
219233
234+ @property
235+ def permissive (self ) -> bool :
236+ """Returns the ``permissive`` parameter value in the initializer."""
237+ return self ._permissive
238+
220239 def base_uri (self ) -> 'URI' :
221240 """Returns `URI` for the DICOM Service within this object."""
222241 return URI (self .base_url )
@@ -258,7 +277,8 @@ def update(self,
258277 series_instance_uid : Optional [str ] = None ,
259278 sop_instance_uid : Optional [str ] = None ,
260279 frames : Optional [Sequence [int ]] = None ,
261- suffix : Optional [URISuffix ] = None ) -> 'URI' :
280+ suffix : Optional [URISuffix ] = None ,
281+ permissive : Optional [bool ] = False ) -> 'URI' :
262282 """Creates a new `URI` object based on the current one.
263283
264284 Replaces the specified `URI` components in the current `URI` to create
@@ -284,6 +304,9 @@ def update(self,
284304 suffix: URISuffix, optional
285305 Suffix to use in the new `URI` or `None` if the `suffix` from the
286306 current `URI` should be used.
307+ permissive: bool, optional
308+ Set if permissive handling of UIDs (if any) in the updated ``URI``
309+ is required. See the class initializer docstring for details.
287310
288311 Returns
289312 -------
@@ -307,6 +330,7 @@ def update(self,
307330 if sop_instance_uid is not None else self .sop_instance_uid ,
308331 frames if frames is not None else self .frames ,
309332 suffix if suffix is not None else self .suffix ,
333+ permissive if permissive is not None else self .permissive ,
310334 )
311335
312336 @property
@@ -363,7 +387,8 @@ def parent(self) -> 'URI':
363387 @classmethod
364388 def from_string (cls ,
365389 dicomweb_uri : str ,
366- uri_type : Optional [URIType ] = None ) -> 'URI' :
390+ uri_type : Optional [URIType ] = None ,
391+ permissive : bool = False ) -> 'URI' :
367392 """Parses the string to return the URI.
368393
369394 Any valid DICOMweb compatible HTTP[S] URI is permitted, e.g.,
@@ -377,6 +402,9 @@ def from_string(cls,
377402 The expected DICOM resource type referenced by the object. If set,
378403 it validates that the resource-scope of the `dicomweb_uri` matches
379404 the expected type.
405+ permissive: bool
406+ Set if permissive handling of UIDs (if any) in ``dicomweb_uri`` is
407+ required. See the class initializer docstring for details.
380408
381409 Returns
382410 -------
@@ -438,7 +466,7 @@ def from_string(cls,
438466 f'URI: { dicomweb_uri !r} ' )
439467
440468 uri = cls (base_url , study_instance_uid , series_instance_uid ,
441- sop_instance_uid , frames , suffix )
469+ sop_instance_uid , frames , suffix , permissive )
442470 # Validate that the URI is of the specified type, if applicable.
443471 if uri_type is not None and uri .type != uri_type :
444472 raise ValueError (
@@ -540,6 +568,7 @@ def _validate_base_url(url: str) -> None:
540568
541569
542570def _validate_resource_identifiers_and_suffix (
571+ permissive : bool ,
543572 study_instance_uid : Optional [str ],
544573 series_instance_uid : Optional [str ],
545574 sop_instance_uid : Optional [str ],
@@ -563,7 +592,7 @@ def _validate_resource_identifiers_and_suffix(
563592
564593 for uid in (study_instance_uid , series_instance_uid , sop_instance_uid ):
565594 if uid is not None :
566- _validate_uid (uid )
595+ _validate_uid (uid , permissive )
567596
568597 if suffix in (URISuffix .RENDERED , URISuffix .THUMBNAIL ) and (
569598 study_instance_uid is None ):
@@ -577,13 +606,17 @@ def _validate_resource_identifiers_and_suffix(
577606 'resources: Study, Series, or SOP Instance UID' )
578607
579608
580- def _validate_uid (uid : str ) -> None :
609+ def _validate_uid (uid : str , permissive : bool ) -> None :
581610 """Validates a DICOM UID."""
582611 if len (uid ) > _MAX_UID_LENGTH :
583612 raise ValueError ('UID cannot have more than 64 chars. '
584613 f'Actual count in { uid !r} : { len (uid )} ' )
585- if _REGEX_UID .fullmatch (uid ) is None :
586- raise ValueError (f'UID { uid !r} must match regex { _REGEX_UID !r} .' )
614+ if not permissive and _REGEX_UID .fullmatch (uid ) is None :
615+ raise ValueError (f'UID { uid !r} must match regex { _REGEX_UID !r} in '
616+ 'conformance with the DICOM Standard.' )
617+ elif permissive and _REGEX_PERMISSIVE_UID .fullmatch (uid ) is None :
618+ raise ValueError (f'Permissive mode is enabled. UID { uid !r} must match '
619+ f'regex { _REGEX_PERMISSIVE_UID !r} .' )
587620
588621
589622def _validate_frames (frames : Sequence [int ]) -> None :
0 commit comments