@@ -24,6 +24,7 @@ class URISuffix(enum.Enum):
2424# For DICOM Standard spec validation of UID components in `URI`.
2525_MAX_UID_LENGTH = 64
2626_REGEX_UID = re .compile (r'[0-9]+([.][0-9]+)*' )
27+ _REGEX_PERMISSIVE_UID = re .compile (r'[^/@]+' )
2728
2829
2930class URI :
@@ -60,12 +61,15 @@ def __init__(self,
6061 series_instance_uid : Optional [str ] = None ,
6162 sop_instance_uid : Optional [str ] = None ,
6263 frames : Optional [Sequence [int ]] = None ,
63- suffix : Optional [URISuffix ] = None ):
64+ suffix : Optional [URISuffix ] = None ,
65+ permissive : bool = False ):
6466 """Instantiates an object.
6567
6668 As per the DICOM Standard, the Study, Series, and Instance UIDs must be
6769 a series of numeric components (``0``-``9``) separated by the period
6870 ``.`` character, with a maximum length of 64 characters.
71+ If the ``permissive`` flag is set to ``True``, any alpha-numeric or
72+ special characters (except for ``/`` and ``@``) may be used.
6973
7074 Parameters
7175 ----------
@@ -83,6 +87,14 @@ def __init__(self,
8387 suffix: URISuffix, optional
8488 Suffix attached to the DICOM resource URI. This could refer to a
8589 metadata, rendered, or thumbnail resource.
90+ permissive: bool
91+ If ``True``, relaxes the DICOM Standard validation for UIDs (see
92+ main docstring for details). This option is made available since
93+ users may be occasionally forced to work with DICOMs or services
94+ that may be in violation of the standard. Unless required, use of
95+ this flag is **not** recommended, since non-conformant UIDs may
96+ lead to unexpected errors downstream, e.g., rejection by a DICOMweb
97+ server, etc.
8698
8799 Raises
88100 ------
@@ -112,6 +124,7 @@ def __init__(self,
112124 """
113125 _validate_base_url (base_url )
114126 _validate_resource_identifiers_and_suffix (
127+ permissive ,
115128 study_instance_uid ,
116129 series_instance_uid ,
117130 sop_instance_uid ,
@@ -124,6 +137,7 @@ def __init__(self,
124137 self ._instance_uid = sop_instance_uid
125138 self ._frames = None if frames is None else tuple (frames )
126139 self ._suffix = suffix
140+ self ._permissive = permissive
127141
128142 def __str__ (self ) -> str :
129143 """Returns the object as a DICOMweb URI string."""
@@ -207,6 +221,11 @@ def type(self) -> URIType:
207221 return URIType .INSTANCE
208222 return URIType .FRAME
209223
224+ @property
225+ def permissive (self ) -> bool :
226+ """Returns the ``permissive`` parameter value in the initializer."""
227+ return self ._permissive
228+
210229 def base_uri (self ) -> 'URI' :
211230 """Returns `URI` for the DICOM Service within this object."""
212231 return URI (self .base_url )
@@ -248,7 +267,8 @@ def update(self,
248267 series_instance_uid : Optional [str ] = None ,
249268 sop_instance_uid : Optional [str ] = None ,
250269 frames : Optional [Sequence [int ]] = None ,
251- suffix : Optional [URISuffix ] = None ) -> 'URI' :
270+ suffix : Optional [URISuffix ] = None ,
271+ permissive : Optional [bool ] = False ) -> 'URI' :
252272 """Creates a new `URI` object based on the current one.
253273
254274 Replaces the specified `URI` components in the current `URI` to create
@@ -274,6 +294,9 @@ def update(self,
274294 suffix: URISuffix, optional
275295 Suffix to use in the new `URI` or `None` if the `suffix` from the
276296 current `URI` should be used.
297+ permissive: bool, optional
298+ Set if permissive handling of UIDs (if any) in the updated ``URI``
299+ is required. See the class initializer docstring for details.
277300
278301 Returns
279302 -------
@@ -297,6 +320,7 @@ def update(self,
297320 if sop_instance_uid is not None else self .sop_instance_uid ,
298321 frames if frames is not None else self .frames ,
299322 suffix if suffix is not None else self .suffix ,
323+ permissive if permissive is not None else self .permissive ,
300324 )
301325
302326 @property
@@ -353,7 +377,8 @@ def parent(self) -> 'URI':
353377 @classmethod
354378 def from_string (cls ,
355379 dicomweb_uri : str ,
356- uri_type : Optional [URIType ] = None ) -> 'URI' :
380+ uri_type : Optional [URIType ] = None ,
381+ permissive : bool = False ) -> 'URI' :
357382 """Parses the string to return the URI.
358383
359384 Any valid DICOMweb compatible HTTP[S] URI is permitted, e.g.,
@@ -367,6 +392,9 @@ def from_string(cls,
367392 The expected DICOM resource type referenced by the object. If set,
368393 it validates that the resource-scope of the `dicomweb_uri` matches
369394 the expected type.
395+ permissive: bool
396+ Set if permissive handling of UIDs (if any) in ``dicomweb_uri`` is
397+ required. See the class initializer docstring for details.
370398
371399 Returns
372400 -------
@@ -428,7 +456,7 @@ def from_string(cls,
428456 f'URI: { dicomweb_uri !r} ' )
429457
430458 uri = cls (base_url , study_instance_uid , series_instance_uid ,
431- sop_instance_uid , frames , suffix )
459+ sop_instance_uid , frames , suffix , permissive )
432460 # Validate that the URI is of the specified type, if applicable.
433461 if uri_type is not None and uri .type != uri_type :
434462 raise ValueError (
@@ -450,6 +478,7 @@ def _validate_base_url(url: str) -> None:
450478
451479
452480def _validate_resource_identifiers_and_suffix (
481+ permissive : bool ,
453482 study_instance_uid : Optional [str ],
454483 series_instance_uid : Optional [str ],
455484 sop_instance_uid : Optional [str ],
@@ -473,7 +502,7 @@ def _validate_resource_identifiers_and_suffix(
473502
474503 for uid in (study_instance_uid , series_instance_uid , sop_instance_uid ):
475504 if uid is not None :
476- _validate_uid (uid )
505+ _validate_uid (uid , permissive )
477506
478507 if suffix in (URISuffix .RENDERED , URISuffix .THUMBNAIL ) and (
479508 study_instance_uid is None ):
@@ -487,13 +516,17 @@ def _validate_resource_identifiers_and_suffix(
487516 'resources: Study, Series, or SOP Instance UID' )
488517
489518
490- def _validate_uid (uid : str ) -> None :
519+ def _validate_uid (uid : str , permissive : bool ) -> None :
491520 """Validates a DICOM UID."""
492521 if len (uid ) > _MAX_UID_LENGTH :
493522 raise ValueError ('UID cannot have more than 64 chars. '
494523 f'Actual count in { uid !r} : { len (uid )} ' )
495- if _REGEX_UID .fullmatch (uid ) is None :
496- raise ValueError (f'UID { uid !r} must match regex { _REGEX_UID !r} .' )
524+ if not permissive and _REGEX_UID .fullmatch (uid ) is None :
525+ raise ValueError (f'UID { uid !r} must match regex { _REGEX_UID !r} in '
526+ 'conformance with the DICOM Standard.' )
527+ elif permissive and _REGEX_PERMISSIVE_UID .fullmatch (uid ) is None :
528+ raise ValueError (f'Permissive mode is enabled. UID { uid !r} must match '
529+ f'regex { _REGEX_PERMISSIVE_UID !r} .' )
497530
498531
499532def _validate_frames (frames : Sequence [int ]) -> None :
0 commit comments