o
    i                    @   s  d Z ddlmZmZmZ ddlZddlZddlmZ ddl	Z	ddl
Z
ddlmZ ddlmZmZmZmZ ddlZddlmZ ddlmZmZ dd	lmZ dd
lmZmZmZ ddlmZm Z  ddl!m"Z" ddl#m$Z$m%Z%m&Z& ddl'm(Z( ddl)m*Z*m+Z+ ddl,m-Z. ddl,m/Z/m0Z0m1Z1m2Z2m3Z3 e	4dZ5i ddddddddddddddd d!d"d#d$d%d&d'd(d)d*d+d,d-d.d/d0d1d2d3d4d5d6d7d8d9d:d;d<d=d>d?d@dAdBdCZ6dDZ7dEZ8dFZ9dGZ:	IddJe;dKe<dLe=dMee; fdNdOZ>dPedMe=fdQdRZ?G dSdT dTedT Z@G dUdV dVe@ZAG dWdX dXZBee;B ejCB ZDG dYdZ dZZEd[ed\eFe; dMdfd]d^ZGd[edMefd_d`ZHd[edMefdadbZId[edMefdcddZJd[edMefdedfZKd[edMefdgdhZLd[edMefdidjZMd[edMefdkdlZNd[edMefdmdnZOd[edMefdodpZPd[edMefdqdrZQd[edMefdsdtZRd[edMefdudvZSd[edMefdwdxZTd[edMefdydzZUd[edMefd{d|ZVd[edMefd}d~ZWd[edMefddZXd[edMefddZYd[edMefddZZd[edMefddZ[d[edMefddZ\d[edMefddZ]d[edMefddZ^d[edMefddZ_i deHdeIdeJdeKdeLdeMdeNd eOd"ePd$e]d&eQd(eRd*eSd,e]d.e^d0e^d2eTeUe^e_eVeWeXeYe_e^e^eZe^e[e\dZ`	 e.jad2e.jbde.jcde.jdde.jediZfi e.jgde.jhde.jid e.jjd e.jkd e.jld e.jmd"e.jnd"e.jod"e.jpd"e.jqd"e.jrd"e.jsd$e.jtd$e.jud$e.jvd$e.jwd$i e.jxd$e.jyd$e.jzd$e.j{d$e.j|d$e.j}d$e.j~d$e.jd$e.jd$e.jd$e.jd&e.jd&e.jd&e.jd&e.jd&e.jd&e.jd&i e.jd&e.jd&e.jd&e.jd&e.jd&e.jd&e.jd&e.jd&e.jd&e.jd&e.jd&e.jd&e.jd(e.jd*e.jd,e.jd.e.jd.e.jd0e.jde.jde.jde.jde.jde.jde.jde.jde.jde.jde.jde.jde.jde.jdiZd[edMe;fddZd[edMe;fddZdS )zDICOM File-set handling.    )IteratorIterableCallableN)Path)TemporaryDirectory)OptionalUnionAnycast)default_encoding)tag_for_keyworddictionary_description)DataElement)DatasetFileMetaDatasetFileDataset)DicomBytesIODicomFileLike)dcmread)write_datasetwrite_data_elementwrite_file_meta_info)warn_and_log)TagBaseTag)generate_uidUIDExplicitVRLittleEndianImplicitVRLittleEndianMediaStorageDirectoryStoragez^[A-Z0-9_]*$PATIENTPTSTUDYSTSERIESSEIMAGEIMzRT DOSERDzRT STRUCTURE SETRSRT PLANRPzRT TREAT RECORDRXPRESENTATIONPRWAVEFORMWVzSR DOCUMENTSRzKEY OBJECT DOCKYSPECTROSCOPYSPzRAW DATARWREGISTRATIONRGFIDUCIALFDzHANGING PROTOCOLHGEDVMSXPAIPIAIGPLMXSFSSTRASRTP)	ENCAP DOC	VALUE MAPSTEREOMETRICPALETTEIMPLANTIMPLANT ASSYIMPLANT GROUPPLANMEASUREMENTSURFACESURFACE SCANTRACT
ASSESSMENTRADIOTHERAPYPRIVATE7OffsetOfTheFirstDirectoryRecordOfTheRootDirectoryEntityOffsetOfTheNextDirectoryRecord+OffsetOfReferencedLowerLevelDirectoryEntity6OffsetOfTheLastDirectoryRecordOfTheRootDirectoryEntity Fprefixstartalphanumericreturnc           	      c   s    t | dkrtdd}|s|dd }|}t |}dt |  }||| k rW|}d}|r;||||  7 }|| }|s-|  |ddd d	| V  |d
7 }||| k s'dS dS )a  Yield File IDs for a File-set.

    Maximum number of File IDs is:

    * Numeric: (10 ** (8 - `prefix`)) - `start`
    * Alphanumeric: (35 ** (8 - `prefix`)) - `start`

    .. versionchanged:: 3.0

       The characters used when `alphanumeric` is ``True`` have been reduced to
       [0-9][A-I,K-Z]

    Parameters
    ----------
    prefix : str, optional
        The prefix to use for all filenames, default (``""``).
    start : int, optional
        The starting index to use for the suffixes, (default ``0``).
        i.e. if you want to start at ``'00010'`` then `start` should be ``10``.
    alphanumeric : bool, optional
        If ``False`` (default) then only generate suffixes using the characters
        [0-9], otherwise use [0-9][A-I,K-Z].

    Yields
    ------
    str
        A unique filename with 8 characters, with each incremented by 1 from
        the previous one (i.e. ``'00000000'``, ``'00000001'``, ``'00000002'``,
        and so on).
       z0The 'prefix' must be less than 8 characters long#0123456789ABCDEFGHIKLMNOPQRSTUVWXYZN
      r]   >0   )len
ValueError)	r^   r_   r`   charsidxblengthnsuffix rq   E/mnt/sdb/aimis/docanh/lib/python3.10/site-packages/pydicom/fileset.pygenerate_filenameL   s&   ! rs   pathc                 C   sr   | j }tdd |D rdS t|dkrdS d|}z	|jddd W n
 ty.   Y dS w tt|r7d	S dS )
a{  Return ``True`` if `path` is a conformant File ID.

    **Conformance**

    * :dcm:`No more than 8 components<part03/sect_F.3.2.2.html>` (parts) in
      the path
    * :dcm:`No more than 8 characters per component<part03/sect_F.3.2.2.html>`
    * :dcm:`Characters in a component must be ASCII<part10/sect_8.2.html>`
    * :dcm:`Valid characters in a component are 0-9, A-Z and _
      <part10/sect_8.5.html>`

    Parameters
    ----------
    path : pathlib.Path
        The path to check, relative to the File-set root directory.

    Returns
    -------
    bool
        ``True`` if `path` is conformant, ``False`` otherwise.
    c                 S   s   g | ]}t |d kqS )re   )ri   ).0pprq   rq   rr   
<listcomp>       z)is_conformant_file_id.<locals>.<listcomp>Fre   r]   asciistrict)encodingerrorsT)	partsanyri   joinencodeUnicodeEncodeErrorrematch_RE_FILE_ID)rt   r}   rk   rq   rq   rr   is_conformant_file_id   s   
r   c                   @   s  e Zd ZdZdEdedB ddfddZdFdd	Zeded  fd
dZ	ede
fddZdee
d f defddZdee
d f ddfddZedefddZdGdedefddZededB fddZedHddZdee
d f dd fddZedefd d!Zedefd"d#Zedefd$d%Zded  fd&d'Zede
fd(d)Zeded  fd*d+ZedId,d-Zej dJd/d-ZdKd1e
dee
 fd2d3Z!eded  fd4d5Z"d6eddfd7d8Z#ede
fd9d:Z$dJd;d<Z%de&d  fd=d>Z'edId?d@Z(de
fdAdBZ)dLdCdDZ*dS )M
RecordNodeaS  Representation of a DICOMDIR's directory record.

    Attributes
    ----------
    children : list of RecordNode
        The current node's child nodes (if any)
    instance : FileInstance or None
        If the current node is a leaf node, a
        :class:`~pydicom.fileset.FileInstance` for the corresponding SOP
        Instance.
    Nrecordra   c                 C   s:   g | _ d| _d| _|  |r| | d| _d| _d| _dS )zCreate a new ``RecordNode``.

        Parameters
        ----------
        record : pydicom.dataset.Dataset, optional
            A *Directory Record Sequence's* directory record.
        Nr   )childreninstance_parent_set_record_offset_offset_next_offset_lower)selfr   rq   rq   rr   __init__   s   

zRecordNode.__init__leafc                 C   sV   |j }|| u r|jd }| j }||v r&|jr&|| }|jd }||v r&|js||_dS )a  Add a leaf to the tree.

        Parameters
        ----------
        leaf : pydicom.fileset.RecordNode
            A leaf node (i.e. one with a
            :class:`~pydicom.fileset.FileInstance`) to be added to the tree
            (if not already present).
        r   N)rootr   parent)r   r   nodecurrentrq   rq   rr   add   s   


zRecordNode.addc                    s    fdd   D S )zaReturn a list of the current node's ancestors, ordered from nearest
        to furthest.
        c                    s   g | ]}| ur|qS rq   rq   )ru   nnr   rq   rr   rw      rx   z(RecordNode.ancestors.<locals>.<listcomp>)reverser   rq   r   rr   	ancestors   s   zRecordNode.ancestorsc                 C   s   | j rtdt| j }| jdkr| | j }d}| jjs$|dd }d}| j}t|}|r=||||  7 }|| }|s/|ddd dd	t|  }| | S )
z@Return a File ID component as :class:`str` for the current node.z4The root node doesn't contribute a File ID componentrX   rc   Nrd   r]   rf   rg   re   )	is_rootrj   	_PREFIXESrecord_typedepthfile_set_use_alphanumericindexri   )r   r^   rk   rp   ro   rm   rl   rq   rq   rr   	component   s"   

 zRecordNode.componentkeyc                 C   s$   t |tr|j}|dd | jD v S )z?Return ``True`` if the current node has a child matching `key`.c                 S   s   g | ]}|j qS rq   r   )ru   childrq   rq   rr   rw         z+RecordNode.__contains__.<locals>.<listcomp>)
isinstancer   r   r   r   r   rq   rq   rr   __contains__  s   
zRecordNode.__contains__c                    sV   t  tr j  | vrt  fdd| jD | _| js'| js)| j| = dS dS dS )zRemove one of the current node's children and if the current node
        becomes childless recurse upwards and delete it from its parent.
        c                    s   g | ]	}|j  kr|qS rq   r   ru   iir   rq   rr   rw         z*RecordNode.__delitem__.<locals>.<listcomp>N)r   r   r   KeyErrorr   r   r   r   rq   r   rr   __delitem__  s   
zRecordNode.__delitem__c                 C   s   t t|  d S )z;Return the number of nodes to the level below the tree rootrh   )ri   listr   r   rq   rq   rr   r   $  s   zRecordNode.depthFforce_implicitc                 C   s   t  }d|_||_| jdt}t| j D ]-}|jdkr$|j	dkr$q|dkr0|
 d | _n|dkr;|
 d | _t|| j| | qt| S )a  Encode the node's directory record.

        * Encodes the record as explicit VR little endian
        * Sets the ``RecordNode._offset_next`` and ``RecordNode._offset_lower``
          attributes to the position of the start of the values of the *Offset
          of the Next Directory Record* and *Offset of Referenced Lower Level
          Directory Entity* elements. Note that the offsets are relative to
          the start of the current directory record.

        The values for the *Offset Of The Next Directory Record* and *Offset
        of Referenced Lower Level Directory Entity* elements are not guaranteed
        to be correct.

        Parameters
        ----------
        force_implicit : bool, optional
            ``True`` to force using implicit VR encoding, which is
            non-conformant. Default ``False``.

        Returns
        -------
        int
            The length of the encoded directory record.

        See Also
        --------
        :meth:`~pydicom.fileset.RecordNode._update_record_offsets`
        TSpecificCharacterSetr      i  re   i  )r   is_little_endianis_implicit_VR_recordgetr   sortedkeyselementgrouptellr   r   r   ri   getvalue)r   r   fpr{   tagrq   rq   rr   _encode_record)  s   zRecordNode._encode_recordc                 C   s\   d| j v r*| j d }|jdkrttt| j jS |jdkr(tttt | j j S dS td)a  Return the *Referenced File ID* as a :class:`~pathlib.Path`.

        Returns
        -------
        pathlib.Path or None
            The *Referenced File ID* from the directory record as a
            :class:`pathlib.Path` or ``None`` if the element value is null.
        ReferencedFileIDrh   Nz/No 'Referenced File ID' in the directory record)r   r<   r   r
   strr   r   AttributeError)r   elemrq   rq   rr   _file_id]  s   




zRecordNode._file_idFileSetc                 C      | j jS z4Return the tree's :class:`~pydicom.fileset.FileSet`.)r   r   r   rq   rq   rr   r   r  s   zRecordNode.file_setc                 C   s6   t |tr|j}| jD ]}||jkr|  S qt|)zcReturn the current node's child using it's
        :attr:`~pydicom.fileset.RecordNode.key`
        )r   r   r   r   r   )r   r   r   rq   rq   rr   __getitem__w  s   


zRecordNode.__getitem__c                 C   s
   | j duS )z?Return ``True`` if the current node corresponds to an instance.N)r   r   rq   rq   rr   has_instance  s   
zRecordNode.has_instancec                 C   s   | j sdS | j j| S )z:Return the index of the current node amongst its siblings.r   )r   r   r   r   rq   rq   rr   r     s   zRecordNode.indexc                 C      dS )<Return ``True`` if the current node is the tree's root node.Frq   r   rq   rq   rr   r        zRecordNode.is_rootc                 c   s(    | j s| V  | jD ]}|E dH  q
dS )zCYield this node (unless it's the root node) and all nodes below it.N)r   r   )r   r   rq   rq   rr   __iter__  s   
zRecordNode.__iter__c              
   C   s   | j }|dkrtt| jjS |dkr%d| jv rtt| jjS tt| jjS |dkr0tt| jjS |dkr;tt| jj	S ztt| jjW S  t
yX } z	t
d| d|d}~ww )	z:Return a unique key for the node's record as :class:`str`.r    r"   StudyInstanceUIDr$   rX   z	Invalid 'zI' record - missing required element 'Referenced SOP Instance UID in File'N)r   r
   r   r   	PatientIDr   r   ReferencedSOPInstanceUIDInFileSeriesInstanceUIDPrivateRecordUIDr   )r   rtypeexcrq   rq   rr   r     s*   

zRecordNode.keyc                 C   s4   | j sdS z
| j j| jd  W S  ty   Y dS w )z<Return the node after the current one (if any), or ``None``.Nrh   )r   r   r   
IndexErrorr   rq   rq   rr   next  s   zRecordNode.nextc                 C   s   t d| jS )z1Return the current node's parent (if it has one).r   )r
   r   r   rq   rq   rr   r        zRecordNode.parentr   c                 C   s0   || _ |dur| |jvr|j|  dS dS dS )z#Set the parent of the current node.N)r   r   appendr   r   rq   rq   rr   r     s     indent_charc                 C   s   dddt dtt  fdd}g }| D ]T}||j }|jr8|| |  |jD ]}|jr6||||  nq'q|jdkre|jrett|j	|_	| |j
 d}|j	jrX|d	7 }n|j	jr`|d
7 }|| q|S )a  Return the tree structure as a list of pretty strings, starting at
        the current node (unless the current node is the root node).

        Parameters
        ----------
        indent_char : str, optional
            The characters to use to indent each level of the tree.
        r   r   r   ra   c                    sN  g }| j s|| j }dd | jD }dd |D }t|D ]  fdd|D }tdd |D }tdd |D }t|| }	t|| }
g }|sL|rX|	dkrX||	 d	 |rl|d
kr`dnd}|| d|  |r|d
krtdnd}|| d|  |   d|
 d|
d
krdnd }|r|dd| d7 }|| q|S )z*Summarize the leaves at the current level.c                 S   s   g | ]}|j r|qS rq   )r   r   rq   rq   rr   rw         z=RecordNode.prettify.<locals>.leaf_summary.<locals>.<listcomp>c                 S      h | ]}|j qS rq   r   r   rq   rq   rr   	<setcomp>  r   z<RecordNode.prettify.<locals>.leaf_summary.<locals>.<setcomp>c                       g | ]	}|j  kr|qS rq   r   r   r   rq   rr   rw     r   c                 S      g | ]}t t|jjr|qS rq   )r
   FileInstancer   for_additionr   rq   rq   rr   rw     s    c                 S   r   rq   )r
   r   r   for_removalr   rq   rq   rr   rw     s    r   z initialrh   sr]   	 addition removal: z SOP Instancez (, ))r   r   r   r   ri   r   r   )r   r   outindentsibsrtypesnrr   rminitialresultchangespluralsummaryrq   r   rr   leaf_summary  sD   
z)RecordNode.prettify.<locals>.leaf_summaryr   z: 1 SOP Instancez (to be added)z (to be removed))r   r   r   r   r   r   extendr
   r   r   r   r   r   )r   r   r   r   r   r   r   linerq   rq   rr   prettify  s,   
1



zRecordNode.prettifyc                 C   s*   | j sdS | jdkrdS | j j| jd  S )z=Return the node before the current one (if any), or ``None``.Nr   rh   )r   r   r   r   rq   rq   rr   previous"  s
   
zRecordNode.previousdsc              
      s   t  dd} dd}|r| dnd}d| d}|r&d| d| d	}dg} fd
d|D }|rB| dd| }t|t vrLt td t vrVt td d _ | _z| j	 W dS  t
tfyx } zt| d|d}~ww )a  Set the node's initial directory record dataset.

        The record is used as a starting point when filling the DICOMDIR's
        *Directory Record Sequence* and is modified as required during
        encoding.

        Parameters
        ----------
        ds : pydicom.dataset.Dataset
            Set the node's initial directory record dataset, must be conformant
            to :dcm:`Part 3, Annex F of the DICOM Standard
            <part03/chapter_F.html>`.
        seq_item_tellNDirectoryRecordType r]   zThe zdirectory record is missingzdirectory record at offset z is missingc                    s   g | ]}| vr|qS rq   rq   ru   kwr   rq   rr   rw   C  rx   z*RecordNode._set_record.<locals>.<listcomp>z  one or more required elements: r   r     z a required element)getattrr   r   rj   _NEXT_OFFSETsetattr_LOWER_OFFSETRecordInUseFlagr   r   r   )r   r   offsetr   msgkeywordsmissingr   rq   r  rr   r   -  s.   zRecordNode._set_recordc                 C   s   t t| jjS )z<Return the record's *Directory Record Type* as :class:`str`.)r
   r   r   r   r   rq   rq   rr   r   T  s   zRecordNode.record_typec                 C   s   |j std|j|= dS )zRemove a leaf from the tree

        Parameters
        ----------
        node : pydicom.fileset.RecordNode
            The leaf node (i.e. one with a
            :class:`~pydicom.fileset.FileInstance`) to remove.
        zOnly leaf nodes can be removedN)r   rj   r   r   rq   rq   rr   removeY  s   	zRecordNode.removec                 c   s2    | }|j r|V  |j }|j s|js|V  dS dS )z7Yield nodes up to the level below the tree's root node.N)r   r   r   rq   rq   rr   r   g  s   
zRecordNode.reversec                 C   s   | j r| j jS | S )zReturn the tree's root node.)r   r   r   rq   rq   rr   r   q  s   zRecordNode.rootc                 C   s  | j rdS | j}| j }g }| jdkr$|d|j dd|j dg7 }n^| jdkrH|d|j d|j g7 }t|d	d
rG|d|j	 d n:| jdkrl|d|j
 d|j g7 }t|dd
rk|d|j d n| jdkr{|d|j  n|| j  | dd| S )z+Return a string representation of the node.ROOTr    zPatientID=''zPatientName='r"   z
StudyDate=z
StudyTime=StudyDescriptionNzStudyDescription='r$   z	Modality=zSeriesNumber=SeriesDescriptionzSeriesDescription='r&   zInstanceNumber=r   r   )r   r   r   r   PatientName	StudyDate	StudyTimer  r   r  ModalitySeriesNumberr  InstanceNumberr   r   )r   r   r   r   rq   rq   rr   __str__y  s*   
"


zRecordNode.__str__c                 C   sR   | j t }d|_| jr| jj|_| j t }d|_| jr'| jd j| j t _dS dS )a  Update the record's offset elements.

        Updates the values for *Offset of the Next Directory Record* and
        *Offset of Referenced Lower Level Directory Entity*, provided all of
        the nodes have had their *_offset* attribute set correctly.
        r   N)r   r  valuer   r   r  r   )r   	next_elem
lower_elemrq   rq   rr   _update_record_offsets  s   


z!RecordNode._update_record_offsetsN)r   r   ra   NFra   r   )ra   r   )r   r   ra   N)r   ra   N)+__name__
__module____qualname____doc__r   r   r   propertyr   r   r   r   r   boolr   r   intr   r   r   r   r   r   r   r   r   r   r   r   r   r   r   setterr   r   r   r   r  r   r   r   r  r  rq   rq   rq   rr   r      sZ    
4
R
'

r   c                       sB   e Zd ZdZd fddZeddd	Zedefd
dZ  Z	S )RootNodez-The root node for the File-set's record tree.fsr   ra   Nc                    s   t    || _dS )zCreate a new root node.

        Parameters
        ----------
        fs : pydicom.fileset.FileSet
            The File-set the record tree belongs to.
        N)superr   _fs)r   r)  	__class__rq   rr   r     s   

zRootNode.__init__c                 C      | j S r   )r+  r   rq   rq   rr   r        zRootNode.file_setc                 C   r   )r   Trq   r   rq   rq   rr   r     r   zRootNode.is_root)r)  r   ra   Nr  )
r   r!  r"  r#  r   r$  r   r%  r   __classcell__rq   rq   r,  rr   r(    s    r(  c                       sJ  e Zd ZdZdeddfddZdeddfdd	Zd
eeB de	fddZ
edefddZed+ddZede	fddZede	fddZede	fddZd
edef fddZdeeB defddZede	fddZede	fdd Zdefd!d"Zedefd#d$Zedefd%d&Zedefd'd(Zedefd)d*Z  ZS ),r   zRepresentation of a File in the File-set.

    Attributes
    ----------
    node : pydicom.fileset.RecordNode
        The leaf record that references this instance.
    r   ra   Nc                 C   s:   G dd d}t  | _| | _| d d| _|| _dS )zCreate a new FileInstance.

        Parameters
        ----------
        node : pydicom.fileset.RecordNode
            The record that references this instance.
        c                   @   s   e Zd ZU eed< eed< dS )z$FileInstance.__init__.<locals>.Flagsr   r  N)r   r!  r"  r%  __annotations__rq   rq   rq   rr   Flags  s   
 r2  xN)uuiduuid4_uuid_flags_apply_stage_stage_pathr   )r   r   r2  rq   rq   rr   r     s   	


zFileInstance.__init__flagc                 C   s   |dkrd| j _d| j _d| _dS |dkr3| j jr"d| j _d| _dS d| j _| jjd | j  | _dS |dkrM| j jrDd| j _d| _dS d| j _d| _dS dS )a0  Apply staging to the instance.

        Parameters
        ----------
        flag : str
            The staging to apply, one of ``'+'``, ``'-'`` or ``'x'``.
            This will flag the instance for addition to or removal from the
            File-set, or to reset the staging, respectively.
        r3  FN+Trt   -)r7  r   r  r9  r   _stager6  )r   r:  rq   rq   rr   r8    s"   



zFileInstance._apply_stagenamec                 C   s$   z| |  W dS  t y   Y dS w )a  Return ``True`` if the element with keyword or tag `name` is
        in one of the corresponding directory records.

        Parameters
        ----------
        name : str or int
            The element keyword or tag to search for.

        Returns
        -------
        bool
            ``True`` if the corresponding element is present, ``False``
            otherwise.
        FT)r   )r   r>  rq   rq   rr   r     s   
zFileInstance.__contains__c                    s8   | j j  fdd| j  D }tt|ddd  S )z.Return the File ID of the referenced instance.c                    s   g | ]	}| ur|j qS rq   )r   r   r   rq   rr   rw     r   z'FileInstance.FileID.<locals>.<listcomp>Nrf   )r   r   r   osfspathr   )r   
componentsrq   r?  rr   FileID  s   zFileInstance.FileIDr   c                 C   r   )zWReturn the :class:`~pydicom.fileset.FileSet` this instance belongs
        to.
        )r   r   r   rq   rq   rr   r        zFileInstance.file_setc                 C   r   )z^Return ``True`` if the instance has been staged for addition to
        the File-set.
        )r7  r   r   rq   rq   rr   r      rD  zFileInstance.for_additionc                 C   sP   | j rdS | d jdkr| jtjj}| jg|kS tt	| j| jtjjkS )zeReturn ``True`` if the instance will be moved to a new location
        within the File-set.
        Fr   rh   )
r   r<   rC  splitr@  rt   sepr   r
   r%  )r   file_idrq   rq   rr   
for_moving'  s   zFileInstance.for_movingc                 C   r   )z_Return ``True`` if the instance has been staged for removal from
        the File-set.
        )r7  r  r   rq   rq   rr   r   5  rD  zFileInstance.for_removalc                    sN   t |}|dur!t|}| j D ]}||jv r |j| j  S qt |S )ae  Return the class attribute value for `name`.

        Parameters
        ----------
        name : str
            An element keyword or a class attribute name.

        Returns
        -------
        object
            If `name` matches a DICOM keyword and the element is
            present in one of the directory records then returns the
            corresponding element's value. Otherwise returns the class
            attribute's value (if present). Directory records are searched
            from the lowest (i.e. an IMAGE or similar record type) to the
            highest (PATIENT or similar).
        N)r   r   r   r   r   r  r*  __getattribute__)r   r>  r   r   r,  rq   rr   rI  <  s   
zFileInstance.__getattribute__r   c                 C   s|   t |tr|}nt|}|dkrtd}n|dkrtd}n|dkr&td}| j D ]}||jv r9|j|   S q+t|)a  Return the DataElement with keyword or tag `key`.

        Parameters
        ----------
        key : str or int
            An element keyword or tag.

        Returns
        -------
        pydicom.dataelem.DataElement
            The DataElement corresponding to `key`, if present in one of the
            directory records. Directory records are searched
            from the lowest (i.e. an IMAGE or similar record type) to the
            highest (PATIENT or similar).
        i  i i  i i  i )r   r   r   r   r   r   r   )r   r   r   r   rq   rq   rr   r   W  s   



zFileInstance.__getitem__c                 C   s   | j jdkS )z5Return ``True`` if the instance is privately defined.rX   )r   r   r   rq   rq   rr   
is_private}  r   zFileInstance.is_privatec                 C   s   | j p| jp| jS )zZReturn ``True`` if the instance is staged for moving, addition or
        removal
        )r   rH  r   r   rq   rq   rr   	is_staged  s   zFileInstance.is_stagedc                 C   s    | j rttt| jS t| jS )zWReturn the referenced instance as a
        :class:`~pydicom.dataset.Dataset`.
        )r   r   r
   r   r9  rt   r   rq   rq   rr   load  s   
zFileInstance.loadc                 C   s:   | j rttt| jS ttt| jjtt| jj	 S )aK  Return the path to the corresponding instance as :class:`str`.

        Returns
        -------
        str
            The absolute path to the corresponding instance. If the instance is
            staged for addition to the File-set this will be a path to the
            staged file in the temporary staging directory.
        )
r   r@  rA  r
   r   r9  r   rt   r   r   r   rq   rq   rr   rt     s
   zFileInstance.pathc                 C      t t| jS )z6Return the *SOP Class UID* of the referenced instance.)r
   r   ReferencedSOPClassUIDInFiler   rq   rq   rr   SOPClassUID  r   zFileInstance.SOPClassUIDc                 C   rM  )z9Return the *SOP Instance UID* of the referenced instance.)r
   r   r   r   rq   rq   rr   SOPInstanceUID  r   zFileInstance.SOPInstanceUIDc                 C   rM  )z<Return the *Transfer Syntax UID* of the referenced instance.)r
   r   !ReferencedTransferSyntaxUIDInFiler   rq   rq   rr   TransferSyntaxUID  r   zFileInstance.TransferSyntaxUIDr  )r   r!  r"  r#  r   r   r   r8  r&  r%  r   r$  rC  r   r   rH  r   r	   rI  r   r   rJ  rK  r   rL  rt   r   rO  rP  rR  r0  rq   rq   r,  rr   r     s<    !&	r   c                   @   s  e Zd ZdZdIdedB ddfddZdedefdd	Zded
edefddZ	dJddZ
dKdeejB dedd fddZdefddZededB fddZejdedB ddfddZededB fddZejdedB ddfddZdKdededee fdd Z		dLd!eeB eeeB  B d"ee dB dedee eeeB ee f B fd#d$ZededB fd%d&ZejdedB ddfd'd&Zedefd(d)Zdee fd*d+Zdefd,d-Z 	.	dMded/ed0eddfd1d2Z!	dKded/ed0eddfd3d4Z"ededB fd5d6Z#dedee fd7d8Z$d9eee B ddfd:d;Z%defd<d=Z&ede'fd>d?Z'e'jd@e'ddfdAd?Z'			dNdeejB dB dBededdfdCdDZ(	dOdEe)dFededdfdGdHZ*dS )Pr   z#Representation of a DICOM File-set.Nr   ra   c                 C   s   d| _ t| | _t i i ddd| _t| jd j| jd< t | _g | _	d| _
d| _d| _d| _d| _|r<| | dS t | _dS )zCreate or load a File-set.

        Parameters
        ----------
        ds : pydicom.dataset.Dataset, str or PathLike, optional
            If loading a File-set, the DICOMDIR dataset or the path
            to the DICOMDIR file.
        NF)tr;  r<  ~^rS  rt   )_pathr(  _treer   r=  r   r>  r   _ds
_instancesr   _id_uid_descriptor_charsetrL  r   r   r   r   rq   rq   rr   r     s&   

zFileSet.__init__
ds_or_pathc           	         s  t |ttjB rt|}n|}|j  fdd| D } | jd v r?| jd   }| jd  = | j| |	d t
t|S |rE|d S | |}t|}t|}|}|D ]}t|}||_|}qVt|}||_| j| || jd |j< | j| |	d |j|jdd t
t|S )a  Stage an instance for addition to the File-set.

        If the instance has been staged for removal then calling
        :meth:`~pydicom.fileset.FileSet.add` will cancel the staging
        and the instance will not be removed.

        Parameters
        ----------
        ds_or_path : pydicom.dataset.Dataset, str or PathLike
            The instance to add to the File-set, either as a
            :class:`~pydicom.dataset.Dataset` or the path to the instance.

        Returns
        -------
        FileInstance
            The :class:`~pydicom.fileset.FileInstance` that was added.

        See Also
        --------
        :meth:`~pydicom.fileset.FileSet.add_custom`
        c                    r   rq   rP  r   r   rq   rr   rw   	  r   zFileSet.add.<locals>.<listcomp>r<  r;  r   Tenforce_file_format)r   r   r@  PathLiker   rP  r=  rY  r   r8  r
   r   
_recordifyr   r   r   r   rW  r   save_asrt   )	r   r_  r   have_instancer   
record_genr   r   r   rq   r   rr   r     s:   





zFileSet.addr   c                    s  t |ttjB rt|}n|}|jdkrtd|j  fdd| D } | jd v rH| jd   }| jd  = | j	
| |d tt|S |rN|d S d|j_|j|j_ |j_|jj|j_t|}||_| j| || jd |j< | j	
| |d |j|jd	d
 tt|S )a  Stage an instance for addition to the File-set using custom records.

        This method allows you to add a SOP instance and customize the
        directory records that will be used when writing the DICOMDIR file. It
        must be used when you require PRIVATE records and may be used instead
        of modifying :attr:`~pydicom.fileset.DIRECTORY_RECORDERS` with your
        own record definition functions when the default functions aren't
        suitable.

        The following elements will be added automatically to the supplied
        directory records if required and not present:

        * (0004,1400) *Offset of the Next Directory Record*
        * (0004,1410) *Record In-use Flag*
        * (0004,1420) *Offset of Referenced Lower-Level Directory Entity*
        * (0004,1500) *Referenced File ID*
        * (0004,1510) *Referenced SOP Class UID in File*
        * (0004,1511) *Referenced SOP Instance UID in File*
        * (0004,1512) *Referenced Transfer Syntax UID in File*

        If the instance has been staged for removal then calling
        :meth:`~pydicom.fileset.FileSet.add_custom` will cancel the staging
        and the instance will not be removed.

        Examples
        --------

        Add a SOP Instance using a two record hierarchy of PATIENT -> PRIVATE

        .. code-block:: python

            from pydicom import Dataset, examples
            from pydicom.fileset import FileSet, RecordNode
            from pydicom.uid import generate_uid

            # The instance to be added
            ds = examples.ct

            # Define the leaf node (the PRIVATE record)
            record = Dataset()
            record.DirectoryRecordType = "PRIVATE"
            record.PrivateRecordUID = generate_uid()
            leaf_node = RecordNode(record)

            # Define the top node (the PATIENT record)
            record = Dataset()
            record.DirectoryRecordType = "PATIENT"
            record.PatientID = ds.PatientID
            record.PatientName = ds.PatientName
            top_node = RecordNode(record)

            # Set the node relationship
            leaf_node.parent = top_node

            # Add the instance to the File-set
            fs = FileSet()
            instance = fs.add_custom(ds, leaf_node)

        Parameters
        ----------
        ds_or_path : pydicom.dataset.Dataset, str or PathLike
            The instance to add to the File-set, either as a
            :class:`~pydicom.dataset.Dataset` or the path to the instance.
        leaf : pydicom.fileset.RecordNode
            The leaf node for the instance, should have its ancestors nodes set
            correctly as well as their corresponding directory records. Should
            have no more than 7 ancestors due to the semantics used by
            :class:`~pydicom.fileset.FileSet` when creating the directory
            structure.

        Returns
        -------
        FileInstance
            The :class:`~pydicom.fileset.FileInstance` that was added.

        See Also
        --------
        :meth:`~pydicom.fileset.FileSet.add`
        rb   zrThe 'leaf' node must not have more than 7 ancestors as 'FileSet' supports a maximum directory structure depth of 8c                    r   rq   r`  r   r   rq   rr   rw     r   z&FileSet.add_custom.<locals>.<listcomp>r<  r;  r   NTra  )r   r   r@  rc  r   r   rj   rP  r=  rY  r   r8  r
   r   r   r   rO  rN  r   	file_metarR  rQ  r   rW  r   re  rt   )r   r_  r   r   rf  r   rq   r   rr   
add_custom3  s:   Q






zFileSet.add_customc                 C   s   g | j _g | _d| _t | _d| _t | _d| _	d| _
i | jd< i | jd< d| jd< d| jd< | jd   t | jd< t| jd j| jd< dS )	zClear the File-set.Nr;  r<  FrT  rU  rS  rt   )rW  r   rY  rV  r   rX  rZ  r   r[  r\  r]  r=  cleanupr   r   r>  r   rq   rq   rr   clear  s   



zFileSet.clearFrt   r   c                 C   sn  t |}| jrt | j|krtdt| dkrd| _t| dkr%tdg }| jd  D ]}||j	 | j
|j	 q.g }| D ])}||j |t |j }|jjddd t|j| |jtjj|j	j_qB|d }t|d	}t|}	| j|	d|d
 W d   n1 sw   Y  t| |D ]	\}}
|
|j	j_q|D ]}| j
| qt }|j|dd |S )a  Copy the File-set to a new root directory and return the copied
        File-set.

        Changes staged to the original :class:`~pydicom.fileset.FileSet` will
        be applied to the new File-set. The original
        :class:`~pydicom.fileset.FileSet` will remain staged.

        Parameters
        ----------
        path : str or PathLike
            The root directory where the File-set is to be copied to.
        force_implicit : bool, optional
            If ``True`` force the DICOMDIR file to be encoded using *Implicit
            VR Little Endian* which is non-conformant to the DICOM Standard
            (default ``False``).

        Returns
        -------
        pydicom.fileset.FileSet
            The copied File-set as a :class:`~pydicom.fileset.FileSet`.
        z3Cannot copy the File-set as the 'path' is unchanged@B TmUpydicom doesn't support writing File-sets with more than 1838265625 managed instancesr<  parentsexist_okDICOMDIRwb)	copy_safer   Nraise_orphans)r   rt   rj   ri   r   NotImplementedErrorr=  valuesr   r   rW  r  r   rC  r   mkdirshutilcopyfilerE  r@  rF  r   openr   _write_dicomdirzipr   r   rL  )r   rt   r   detached_nodesr   file_idsdstpr   frG  r   r)  rq   rq   rr   copy  sB   zFileSet.copyc                 C   sn   t  }d|_t |_t|j_| j|j_t|j_	| j
|_d|_d|_d|_g |_| jr.| j|_| jr5| j|_|S )z&Return a new minimal DICOMDIR dataset.Nr   )r   filenamer   rh  r   rR  r   MediaStorageSOPInstanceUIDr   MediaStorageSOPClassUIDID	FileSetIDrY   r\   FileSetConsistencyFlagDirectoryRecordSequencedescriptor_file_idFileSetDescriptorFileIDdescriptor_character_set+SpecificCharacterSetOfFileSetDescriptorFiler^  rq   rq   rr   _create_dicomdir  s"   
zFileSet._create_dicomdirc                 C   r.  )zlReturn the *Specific Character Set of File-set Descriptor File*
        (if available) or ``None``.
        )r]  r   rq   rq   rr   r  (  s   z FileSet.descriptor_character_setvalc                 C   s0   || j krdS || _ | jr|| j_d| jd< dS )a  Set the *Specific Character Set of File-set Descriptor File*.

        The descriptor file itself is used for user comments related to the
        File-set (e.g. a README file) and is up the user to create.

        Parameters
        ----------
        val : str or None
            The value to use for the DICOMDIR's (0004,1142) *Specific
            Character Set of File-set Descriptor File*. See :dcm:`C.12.1.1.2
            in Part 3 of the DICOM Standard
            <part03/sect_C.12.html#sect_C.12.1.1.2>` for defined terms.

        See Also
        --------
        :attr:`~pydicom.fileset.FileSet.descriptor_file_id` set the descriptor
        file ID for the file that uses the character set.
        NTrU  )r]  rX  r  r=  r   r  rq   rq   rr   r  /  s   
c                 C   r.  )zDReturn the *File-set Descriptor File ID* (if available) or ``None``.)r\  r   rq   rq   rr   r  K  r/  zFileSet.descriptor_file_idc                 C   s   || j krdS |du rn]t|trKz&t|dksJ |D ]}t|ts%J dt|  kr2dks5J  J qW n tyB   tdw tt| j}nt|tredt|  kr_dksdtd tdnt	d|| _ | j
rt| j | j
_d| jd	< dS )
a  Set the *File-set Descriptor File ID*.

        The descriptor file itself is used for user comments related to the
        File-set (e.g. a README file) and is up the user to create.

        Parameters
        ----------
        val : str, list of str or None
            The value to use for the DICOMDIR's (0004,1141) *File-set
            Descriptor File ID*. Should be the relative path to the descriptor
            file and has a maximum length of 8 components, with each component
            up to 16 characters long.

        Raises
        ------
        ValueError
            If `val` has more than 8 items or if each item is longer than 16
            characters.

        See Also
        --------
        :attr:`~pydicom.fileset.FileSet.descriptor_character_set` the
        character set used in the descriptor file, required if an expanded or
        replaced character set is used.
        Nre   r      zfThe 'File-set Descriptor File ID' has a maximum of 8 components, each between 0 and 16 characters longzREach 'File-set Descriptor File ID' component has a maximum length of 16 charactersz:The 'DescriptorFileID' must be a str, list of str, or NoneTrU  )r\  r   r   ri   r   AssertionErrorrj   r   r}   	TypeErrorrX  r  r=  )r   r  r   rq   rq   rr   r  P  sD   

"

rL  kwargsc                    s^   s	| j dd S d dttB dtdtf fddfdd	| D }s- s-td
 |S )aT  Return matching instances in the File-set

        **Limitations**

        * Only single value matching is supported so neither
          ``PatientID=['1234567', '7654321']`` or ``PatientID='1234567',
          PatientID='7654321'`` will work (although the first example will
          work if the *Patient ID* is actually multi-valued).
        * Repeating group and private elements cannot be used when searching.

        Parameters
        ----------
        load : bool, optional
            If ``True``, then load the SOP Instances belonging to the
            File-set and perform the search against their available elements.
            Otherwise (default) search only the elements available in the
            corresponding directory records (more efficient, but only a limited
            number of elements are available).
        **kwargs
            Search parameters, as element keyword=value (i.e.
            ``PatientID='1234567', StudyDescription="My study"``.

        Returns
        -------
        list of pydicom.fileset.FileInstance
            A list of matching instances.
        NFr   r  ra   c              
      sl   r    t fdd|D rd| D ]\}}z | j|ks%J W q ttfy3   Y  dS w dS )Nc                    s   g | ]}| v qS rq   rq   r   r  rq   rr   rw         z/FileSet.find.<locals>.match.<locals>.<listcomp>TF)rL  allitemsr  r  r   )r   r  r   r  )has_elementsrL  r  rr   r     s   zFileSet.find.<locals>.matchc                    s    g | ]}|fi  r|qS rq   rq   )ru   r   )r  r   rq   rr   rw     s     z FileSet.find.<locals>.<listcomp>zNone of the records in the DICOMDIR dataset contain all the query elements, consider using the 'load' parameter to expand the search to the corresponding SOP instances)rY  r   r   r	   r%  r   )r   rL  r  matchesrq   )r  r  rL  r   rr   find  s    zFileSet.findelements	instancesc                 C   s   t |tr|n|g}dd |D }dd |D }|pt| }|D ](}|r(| }|D ]}	|	|vr1q*d||	< ||	 j}
|
||	 vrG||	 |
 q*q dd | D }|s^|r^td| d t |tsi||d	  S |S )
a  Return a list of unique values for given element(s).

        Parameters
        ----------
        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.
        load : bool, optional
            If ``True``, then load the SOP Instances belonging to the
            File-set and perform the search against their available elements.
            Otherwise (default) search only the elements available in the
            corresponding directory records (more efficient, but only a limited
            number of elements are available).

        Returns
        -------
        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.
        c                 S   s   i | ]}|d qS r  rq   ru   r   rq   rq   rr   
<dictcomp>  r   z'FileSet.find_values.<locals>.<dictcomp>c                 S   s   i | ]}|g qS rq   rq   r  rq   rq   rr   r    r   Tc                 S   s   g | ]\}}|s|qS rq   rq   )ru   r   vrq   rq   rr   rw     rx   z'FileSet.find_values.<locals>.<listcomp>z4None of the records in the DICOMDIR dataset contain z], consider using the 'load' parameter to expand the search to the corresponding SOP instancesr   )r   r   iterrL  r  r   r  r   )r   r  r  rL  element_listhas_elementresultsiter_instancesr   r   r  missing_elementsrq   rq   rr   find_values  s4   


zFileSet.find_valuesc                 C   r.  )z4Return the *File-set ID* (if available) or ``None``.)rZ  r   rq   rq   rr   r    r/  z
FileSet.IDc                 C   sb   || j krdS |du sdt|  krdkr-n td|| _ | jr&|| j_d| jd< dS td)a  Set the File-set ID.

        Parameters
        ----------
        val : str or None
            The value to use for the DICOMDIR's (0004,1130) *File-set ID*.

        Raises
        ------
        ValueError
            If `val` is greater than 16 characters long.
        Nr   r  TrU  z8The maximum length of the 'File-set ID' is 16 characters)rZ  ri   rX  r  r=  rj   r  rq   rq   rr   r    s   
"c                    s   t  fdddD S )z=Return ``True`` if the File-set is new or has changes staged.c                 3   s    | ]} j | V  qd S r  )r=  )ru   cr   rq   rr   	<genexpr>,  s    z$FileSet.is_staged.<locals>.<genexpr>z+-^~)r~   r   rq   r   rr   rK  )  s   zFileSet.is_stagedc                 c   s    | j dd E dH  dS )z?Yield :class:`~pydicom.fileset.FileInstance` from the File-set.N)rY  r   rq   rq   rr   r   .  s   zFileSet.__iter__c                 C   s
   t | jS )z/Return the number of instances in the File-set.)ri   rY  r   rq   rq   rr   __len__2  s   
zFileSet.__len__Tinclude_orphansrv  c                 C   s  t |tr|}nt|}|jdd}|tkrtd|jj}|tkr,t	d|j
 d zttt|jjdd}W n tyI   td|j  tyS   td	w |   ttdB |d
d| _ttdB |jd}|sxt }||j_|| _ttdB |dd| _ttdB |dd| _|j| _|| _| ||| g }	| D ]G}
|
jj}|du r|	 |
 qztt| j!| jdd W n  ty   |	 |
 t	d|
jj" dtt| j!|   Y qw |
j#rd| j$d< q|	D ]}
| j%&|
 qdS )a  Load an existing File-set.

        Existing File-sets that do not use the same directory structure as
        *pydicom* will be staged to be moved to a new structure. This is
        because the DICOM Standard attaches no semantics to *how* the files
        in a File-set are to be structured so it's impossible to determine what
        the layout will be when changes are to be made.

        Parameters
        ----------
        ds_or_path : pydicom.dataset.Dataset, str or PathLike
            An existing File-set's DICOMDIR, either as a
            :class:`~pydicom.dataset.Dataset` or the path to the DICOMDIR file
            as :class:`str` or pathlike.
        include_orphans : bool, optional
            If ``True`` (default) include instances referenced by orphaned
            directory records in the File-set.
        raise_orphans : bool, optional
            If ``True`` then raise an exception if orphaned directory records
            are found in the File-set (default ``False``).
        r  Nz_Unable to load the File-set as the supplied dataset is not a 'Media Storage Directory' instancez6The DICOMDIR dataset uses an invalid transfer syntax 'z8' and will be updated to use 'Explicit VR Little Endian'T)rz   zfUnable to load the File-set as the 'filename' attribute for the DICOMDIR dataset is not a valid path: zyUnable to load the File-set as the DICOMDIR dataset must have a 'filename' attribute set to the path of the DICOMDIR filer  r  r  r  z?The referenced SOP Instance for the directory record at offset z does not exist: rT  )'r   r   r   rh  r   r   rj   rR  r   r   r>  r   r
   r   r  resolveFileNotFoundErrorr  rk  rZ  r   r   r  r[  r\  r]  r   rV  rX  _parse_recordsr   r   r   rt   r   rH  r=  rY  r  )r   r_  r  rv  r   	sop_classtsyntaxrt   uidbad_instancesr   rG  rq   rq   rr   rL  6  s   



zFileSet.loadc                    s  i  t tt |jD ]}t t|j}t|}||_| |< q
 rE |t j	 }j
|_t|jtdrE |jt j	 }j
|_t|jtds2dtddf fddj
jD ]}| qVt tttj
krldS |rrtdt  dd j
D  } fd	d
|D }dd
 |D }|r|stdt| d dS |D ],}|jj}	|j}
|
du rqt tj|
 }|jdkrĈ||}n|}|	|jj_qdS )a  Parse the records in an existing DICOMDIR.

        Parameters
        ----------
        ds : pydicom.dataset.Dataset
            The File-set's DICOMDIR dataset.
        include_orphans : bool
            If ``True`` then include within the File-set orphaned records that
            contain a valid (and unique) *Referenced File ID* element. Orphaned
            records are those that aren't placed within the *Directory Record
            Sequence* hierarchy.
        raise_orphans : bool, optional
            If ``True`` then raise an exception if orphaned directory records
            are found in the File-set (default ``False``).
        Nr   ra   c                    s   t | jtd }|r* | }| |_t |jtd }|r) | }| |_t |jtd }|sn	d| jvr3| j| = d| jv rDt| | _j| j | j	D ]}| qGd S )Nr   )
r  r   r  r   r  r   r   rY  r   r   )r   child_offsetr   next_offsetrecordsrecurse_noder   rq   rr   r    s&   




z,FileSet._parse_records.<locals>.recurse_nodez0The DICOMDIR contains orphaned directory recordsc                 S   r   rq   )r   r   rq   rq   rr   r     r   z)FileSet._parse_records.<locals>.<setcomp>c                    s   g | ]} | qS rq   rq   )ru   o)r  rq   rr   rw     r  z*FileSet._parse_records.<locals>.<listcomp>c                 S   s   g | ]	}d |j v r|qS )r   )r   )ru   rrq   rq   rr   rw     r   zThe DICOMDIR has z` orphaned directory records that reference an instance that will not be included in the File-setrX   )r
   r   r   r  r&  r   r   r   _FIRST_OFFSETr  rW  r   r  r   r  r   ri   r   r  rj   setr   r   r   r   r   rt   r   ri  r   r   )r   r   r  rv  r   r  r   missing_setr  original_valuerG  rt   r   rq   r  rr   r    sN   



zFileSet._parse_recordsc                 C   s   | j durt| j S | j S )zxReturn the absolute path to the File-set root directory as
        :class:`str` (if set) or ``None`` otherwise.
        N)rV  r@  rA  r   rq   rq   rr   rt     s   
zFileSet.pathc                 c   s^   t |}|dkrKzt| |}W n ty) } ztd| d| d|d}~ww d|_d|_d|_||_d|_|j|_	|j
|_|jj|_|V  dS g }t|}ddd	|fD ]<}zt| |}W n tyy } ztd| d| d|d}~ww d|_d|_d|_||_d
|v r|j|_|| qW|d }d|_|j|_	|j
|_|jj|_|E dH  dS )a#  Yield directory records for a SOP Instance.

        Parameters
        ----------
        ds : pydicom.dataset.Dataset
            The SOP Instance to create DICOMDIR directory records for.

        Yields
        ------
        ds : pydicom.dataset.Dataset
            A directory record for the instance, ordered from highest to
            lowest level.

        Raises
        ------
        ValueError
            If unable to create the required directory records because of
            a missing required element or element value.
        r    zUnable to use the default 'z' record creator: z. See DICOM PS3.3 Section F.5. Either update the instance, define your own record creation function or use 'FileSet.add_custom()' insteadNr   r  r"   r$   r      )_single_level_record_typeDIRECTORY_RECORDERSrj   rZ   r  r[   r   r   rO  rN  rP  r   rh  rR  rQ  _four_level_record_typer   r   )r   r   r   r   r   r  	leaf_typer   rq   rq   rr   rd    sj   	
	
zFileSet._recordifyr   c                 C   s   t |tr|D ]}| | qdS || jvrtd|j| jd v rP|j}|j|= | jd |j= z	t	|j
  W n	 tyB   Y nw |d | j| dS |j| jd vrm|d || jd |j< | j| dS dS )a  Stage instance(s) for removal from the File-set.

        If the instance has been staged for addition to the File-set, calling
        :meth:`~pydicom.fileset.FileSet.remove` will cancel the staging and
        the instance will not be added.

        Parameters
        ----------
        instance : pydicom.fileset.FileInstance or a list of FileInstance
            The instance(s) to remove from the File-set.
        Nz No such instance in the File-setr;  r<  )r   r   r  rY  rj   rP  r=  r   r   r   rt   unlinkr  r8  )r   r   itemr   rq   rq   rr   r  _  s,   



zFileSet.removec                 C   s\  dd| j pd d| jpd d| j d| jpd d| jp d g}| jrg }| js2|d n|d	 | jd
 rA|d | jd rbt	| jd dkrQdnd}|t	| jd  d|  | jd rt	| jd dkrrdnd}|t	| jd  d|  |dd
|  | jjsd
|S |d |dd | j D  d
|S )z.Return a string representation of the FileSet.zDICOM File-setz  Root directory: z(no value available)z  File-set ID: z  File-set UID: z  Descriptor file ID: z!  Descriptor file character set: zDICOMDIR creationzDICOMDIR updaterT  zdirectory structure updater;  rh   r   r]   r   r<  r   z  Changes staged for write(): r   
z
  Managed instances:c                 S   s   g | ]}d | qS )z    rq   r   rq   rq   rr   rw     r   z#FileSet.__str__.<locals>.<listcomp>)rt   r  r   r  r  rK  rX  r   r=  ri   r   rW  r   r   r   )r   r   r   rp   rq   rq   rr   r    s<   








zFileSet.__str__c                 C   rM  )zReturn the File-set's UID.)r
   r   r[  r   rq   rq   rr   r     r   zFileSet.UIDr  c                 C   sD   || j krdS t|}|jsJ || _ | jr|| jj_d| jd< dS )zSet the File-set UID.

        Parameters
        ----------
        uid : pydicom.uid.UID
            The UID to use as the new File-set UID.
        NTrU  )r[  r   is_validrX  rh  r  r=  )r   r  rq   rq   rr   r     s   
	

use_existingc              	      s  |sj du rtd|rj rtd|rt|_js dS ttjd }tjd }|r7|r7td|s@|jd O }tdkrId	_	td
krSt
djd  D ]}z	t|j   W n	 tyn   Y nw j|j qZ|r|st|d}t|}j||d W d   n1 sw   Y  j|d	d dS dd D }	fddD }
|	|
@   fddD D ]}|jd |j< |d tj|jj |j  qD ]?}j|j }|jjd	d	d |jjd v r|j }tj}n
j|jj }tj}|t|t| |j tj j!|jj"_#qt|d}t|}j||d W d   n	1 s;w   Y  j|d	d dS )a4
  Write the File-set, or changes to the File-set, to the file system.

        .. warning::

            If modifying an existing File-set it's **strongly recommended**
            that you follow standard data management practices and ensure that
            you have an up-to-date backup of the original data.

        By default, for both new or existing File-sets, *pydicom* uses the
        following directory structure semantics when writing out changes:

        * For instances defined using the standard four-levels of directory
          records (i.e. PATIENT/STUDY/SERIES + one of the record types
          such as IMAGE or RT DOSE): ``PTxxxxxx/STxxxxxx/SExxxxxx/`` with a
          filename such as ``IMxxxxxx`` (for IMAGE), where the first two
          characters are dependent on the record type and ``xxxxxx`` is a
          numeric or alphanumeric index.
        * For instances defined using the standard one-level directory record
          (i.e. PALETTE, IMPLANT): a filename such as ``PAxxxxxx`` (for
          PALETTE).
        * For instances defined using PRIVATE directory records then the
          structure will be along the lines of ``P0xxxxxx/P1xxxxxx/P2xxxxxx``
          for PRIVATE/PRIVATE/PRIVATE, ``PTxxxxxx/STxxxxxx/P2xxxxxx`` for
          PATIENT/STUDY/PRIVATE.

        When only changes to the DICOMDIR file are required or instances have
        only been removed from an existing File-set you can use the
        `use_existing` keyword parameter to keep the existing directory
        structure and only update the DICOMDIR file.

        Parameters
        ----------
        path : str or PathLike, optional
            For new File-sets, the absolute path to the root directory where
            the File-set will be written. Using `path` with an existing
            File-set will raise :class:`ValueError`.
        use_existing : bool, optional
            If ``True`` and no instances have been added to the File-set
            (removals are OK), then only update the DICOMDIR file, keeping
            the current directory structure rather than converting everything
            to the semantics used by *pydicom* for File-sets (default
            ``False``).
        force_implicit : bool, optional
            If ``True`` force the DICOMDIR file to be encoded using *Implicit
            VR Little Endian* which is non-conformant to the DICOM Standard
            (default ``False``).

        Raises
        ------
        ValueError
            If `use_existing` is ``True`` but instances have been staged
            for addition to the File-set.
        Nz=The path to the root directory is required for a new File-setzqThe path for an existing File-set cannot be changed, use 'FileSet.copy()' to write the File-set to a new locationrr  r;  zi'Fileset.write()' called with 'use_existing' but additions to the File-set's managed instances are stagedrT  rl  Trm  rn  r<  rs  )r   ru  c                 S   s   h | ]}t |jqS rq   )r   rC  r   rq   rq   rr   r   F  r   z FileSet.write.<locals>.<setcomp>c                    s$   h | ]}|j  jd  vr|jjqS )r;  )rP  r=  r   r   r   r   rq   rr   r   G  s    c                    s   g | ]
}|j j v r|qS rq   )r   r   r   )
collisionsrq   rr   rw   K  s    z!FileSet.write.<locals>.<listcomp>ro  )$rt   rj   r   rV  rK  r
   r%  r=  ri   r   rw  rx  r  r  rW  r  r   r|  r   r}  rL  rP  r8  rz  r{  r   rC  r   ry  mover@  rA  rE  rF  r   r   )r   rt   r  r   r  major_changer   r   r  foutfinr  srcfnrq   )r  r   rr   write  s   ;



zFileSet.writer   rt  c                 C   s  | j }|s|s|  }t|j_d}|rt|j_d}|jjj|_|jjj|_|t }d|_	|t
 }d|_	|d t||jdd t||dd  | }t||dd	  | | }	| jD ]}
|	|
_|	d7 }	|	|
|7 }	|
jjrw|	d7 }	q_g |_| jD ]>}
|
j}|s|
  n't|}|t }d|_	|
jr|
jj|_	|t }d|_	|
jr|
jd j|t _	ttt |j| q~t||d	d  | jjr| jjd j|_	| jjd
 j|_	| | t!|| t!|| | dd dS dS )a  Encode and write the File-set's DICOMDIR dataset.

        Parameters
        ----------
        fp : file-like
            The file-like to write the encoded DICOMDIR dataset to. Must
            have ``write()``, ``tell()`` and ``seek()`` methods.
        copy_safe : bool, optional
            If ``True`` then the function doesn't make any changes to the
            public parts of the current :class:`~pydicom.fileset.FileSet`
            instance.
        force_implicit : bool, optional
            Force encoding the DICOMDIR with 'Implicit VR Little Endian' which
            is non-conformant to the DICOM Standard (default ``False``).
           re   r   s                                                                                                                                   DICMT)enforce_standardNi  i  rf      )"rX  r  r   rh  rR  r   r   r   r  r  _LAST_OFFSETr  r   r   r   rW  r   r   r   !is_undefined_length_sequence_itemr  r  r  deepcopyr  r   r  r   r
   r   r   r   seekr   )r   r   rt  r   r   
seq_offset
first_elem	last_elemtell_offset_firstr  r   r   r  r  rq   rq   rr   r}  g  sf   








zFileSet._write_dicomdirr  r  r  )NF)TF)NFF)FF)+r   r!  r"  r#  
DSPathTyper   r   r   r   ri  rk  r   r@  rc  r%  r  r   r  r$  r  r'  r  r	   r   r  r&  dictr  r  rK  r   r   r  rL  r  rt   rd  r  r  r   r  r   r}  rq   rq   rq   rr   r     s    1G
I=A

?
m
d	O'+
 r   r   r
  c                 C   sj   |D ]0}t ttt|}t|}|| vr td| d| d| | jdkr(qtd| d| ddS )a  Check the dataset module for the Type 1 `keywords`.

    Parameters
    ----------
    ds : pydicom.dataset.Dataset
        The dataset to check.
    keywords : list of str
        The DICOM keywords for Type 1 elements that are to be checked.

    Raises
    ------
    KeyError
        If an element is not in the dataset.
    ValueError
        If the element is present but has no value.
    zThe instance's z 'z' element is missingr   z' element cannot be emptyN)r   r
   r&  r   r   rj   r<   )r   r
  r   r   r>  rq   rq   rr   _check_dataset  s   r  c                 C   s*   t | dg t }| d|_| j|_|S )z,Return a PATIENT directory record from `ds`.r   r  )r  r   r   r  r   r   r   rq   rq   rr   _define_patient  s
   r  c                 C   sd   t | g d t }| j|_| j|_| d|_d| v r&t | dg | j|_| j|_| d|_|S )z*Return a STUDY directory record from `ds`.)r  r  StudyIDr  r   AccessionNumber)	r  r   r  r  r   r  r   r  r  r  rq   rq   rr   _define_study  s   r  c                 C   0   t | g d t }| j|_| j|_| j|_|S )z+Return a SERIES directory record from `ds`.)r  r   r  )r  r   r  r   r  r  rq   rq   rr   _define_series	     r  c                 C   s   t | dg t }| j|_|S )z+Return an IMAGE directory record from `ds`.r  )r  r   r  r  rq   rq   rr   _define_image	  s   r  c                 C   (   t | ddg t }| j|_| j|_|S )z-Return an RT DOSE directory record from `ds`.r  DoseSummationType)r  r   r  r  r  rq   rq   rr   _define_rt_dose	  
   r  c                 C   @   t | ddg t }| j|_| j|_| d|_| d|_|S )z6Return an RT STRUCTURE SET directory record from `ds`.r  StructureSetLabelStructureSetDateStructureSetTime)r  r   r  r  r   r  r  r  rq   rq   rr   _define_rt_structure_set$	     r  c                 C   r  )z-Return an RT PLAN directory record from `ds`.r  RTPlanLabel
RTPlanDate
RTPlanTime)r  r   r  r  r   r  r  r  rq   rq   rr   _define_rt_plan1	  r  r  c                 C   s6   t | dg t }| j|_| d|_| d|_|S )z5Return an RT TREAT RECORD directory record from `ds`.r  TreatmentDateTreatmentTime)r  r   r  r   r  r  r  rq   rq   rr   _define_rt_treatment_record>	  s   r  c                 C   s   t | g d t }| j|_| j|_| j|_| j|_| d|_| d|_d| v r4t | dg | j	|_	d| v rBt | dg | j
|_
|S )z1Return a PRESENTATION directory record from `ds`.)PresentationCreationDatePresentationCreationTimer  ContentLabelContentDescriptionContentCreatorNameReferencedSeriesSequenceBlendingSequence)r  r   r  r  r  r  r   r  r  r  r  r  rq   rq   rr   _define_presentationJ	  s$   
r   c                 C   s   t | g d t }| j|_| j|_| j|_| j|_| j|_d| v r,t | dg | j|_| j|_d| v r>t | dg | j	|_	|S )z0Return a SR DOCUMENT directory record from `ds`.)r  CompletionFlagVerificationFlagContentDateContentTimeConceptNameCodeSequenceVerificationDateTimeContentSequence)
r  r   r  r  r  r  r  r  r  r  r  rq   rq   rr   _define_sr_documenth	  s$   r  c                 C   sT   t | g d t }| j|_| j|_| j|_| j|_d| v r(t | dg | j|_|S )z3Return a KEY OBJECT DOC directory record from `ds`.)r  r  r  r  r  )r  r   r  r  r  r  r  r  rq   rq   rr   _define_key_object_doc	  s   
r	  c                 C   s|   t | g d t }| j|_| j|_| j|_| j|_d| v r(t | dg | j|_| j|_| j|_| j	|_	| j
|_
| j|_|S )z2Return an SPECTROSCOPY directory record from `ds`.)		ImageTyper  r  r  NumberOfFramesRowsColumnsDataPointRowsDataPointColumnsReferencedImageEvidenceSequence)r  r   r
  r  r  r  r  r  r  r  r  r  r  rq   rq   rr   _define_spectroscopy	  s$   r  c                 C   sF   t | g d t }| j|_| j|_| j|_| j|_| dg |_|S )z5Return a HANGING PROTOCOL directory record from `ds`.)HangingProtocolCreatorHangingProtocolCreationDateTime!HangingProtocolDefinitionSequenceNumberOfPriorsReferenced-HangingProtocolUserIdentificationCodeSequence)r  r   r  r  r  r  r   r  r  rq   rq   rr   _define_hanging_protocol	  s   
r  c                 C   st   t | ddg t }| d|_| d|_| j|_| d|_d| v r.t | dg | j|_| d|_| j	|_	|S )z/Return an ENCAP DOC directory record from `ds`.r  MIMETypeOfEncapsulatedDocumentr  r  DocumentTitleHL7InstanceIdentifierr  )
r  r   r   r  r  r  r  r  r  r  r  rq   rq   rr   _define_encap_doc	  s   r  c                 C   s*   t | dg t }| j|_| d|_|S )z,Return a PALETTE directory record from `ds`.r  r  )r  r   r  r   r  r  rq   rq   rr   _define_palette	  s
   r  c                 C   sL   t | g d t }| j|_| j|_d| v r t | dg | j|_| j|_|S )z,Return a IMPLANT directory record from `ds`.)ManufacturerImplantNameImplantPartNumberImplantSize)r  r   r  r  r   r  r  rq   rq   rr   _define_implant	  s   r!  c                 C   r  )z1Return a IMPLANT ASSY directory record from `ds`.)ImplantAssemblyTemplateNamer  ProcedureTypeCodeSequence)r  r   r"  r  r#  r  rq   rq   rr   _define_implant_assy
  s   r$  c                 C   r  )z2Return a IMPLANT GROUP directory record from `ds`.ImplantTemplateGroupNameImplantTemplateGroupIssuer)r  r   r%  r&  r  rq   rq   rr   _define_implant_group
  r  r'  c                 C   r  )z1Return a SURFACE SCAN directory record from `ds`.r  r  )r  r   r  r  r  rq   rq   rr   _define_surface_scan!
  r  r(  c                 C   s4   t | ddg t }| j|_| j|_| d|_|S )z/Return a ASSESSMENT directory record from `ds`.r  InstanceCreationDateInstanceCreationTime)r  r   r  r)  r   r*  r  rq   rq   rr   _define_assessment,
  s   r+  c                 C   sn   t | dg t }| j|_d| v rt | dg | j|_d| v r)t | dg | j|_| d|_| d|_|S )z1Return a RADIOTHERAPY directory record from `ds`.r  UserContentLabelUserContentLongLabelr  r  )r  r   r  r,  r-  r   r  r  r  rq   rq   rr   _define_radiotherapy8
  s   r.  c                 C   r  )z6Return a WAVEFORM/RAW DATA directory record from `ds`.)r  r  r  )r  r   r  r  r  r  rq   rq   rr   _define_generic_contentK
  r  r/  c                 C   sP   t | g d t }| j|_| j|_| j|_| j|_| d|_| d|_|S )zCReturn a generic content identification directory record from `ds`.)r  r  r  r  r  r  )	r  r   r  r  r  r  r   r  r  r  rq   rq   rr   _define_generic_content_idW
  s   r0  c                 C   s   t  S )z+Return an empty directory record from `ds`.)r   r  rq   rq   rr   _define_emptyg
  s   r1  )rJ   rK   rL   rM   rN   rO   rP   rQ   rR   rS   rT   rU   rV   rW   rM   rN   rO   rP   rK   rL   rR   rS   rT   rU   rV   c                 C   s6   t tdB t| dd}zt| W S  ty   Y dS w )z7Return a single-level *Directory Record Type* for `ds`.NrO  r    )r
   r   r  _SINGLE_LEVEL_SOP_CLASSESr   )r   r  rq   rq   rr   r    s   
r  c                 C   sr   t | dd}|dv rdS |dkrdS d| v rdS d| v rd	S ttdB t | d
d}zt| W S  ty8   Y dS w )z9Return the fourth-level *Directory Record Type* for `ds`.r  N)RTINTENTRTSEGANNRTRADrW   rQ   EncapsulatedDocumentrJ   r  r*   rO  r&   )r  r
   r   _FOUR_LEVEL_SOP_CLASSESr   )r   modalityr  rq   rq   rr   r    s   
r  )r]   r   F)r#  collections.abcr   r   r   r  r@  pathlibr   r   rz  tempfiler   typingr   r   r	   r
   r4  pydicom.charsetr   pydicom.datadictr   r   pydicom.dataelemr   pydicom.datasetr   r   r   pydicom.filebaser   r   pydicom.filereaderr   pydicom.filewriterr   r   r   pydicom.miscr   pydicom.tagr   r   pydicom.uidr  sopr   r   r   r   r   compiler   r   r  r  r  r  r   r&  r%  rs   r   r   r(  r   rc  r  r   r   r  r  r  r  r  r  r  r  r  r   r  r	  r  r  r  r  r!  r$  r'  r(  r+  r.  r/  r0  r1  r  HangingProtocolStorageColorPaletteStorageGenericImplantTemplateStorageImplantAssemblyTemplateStorageImplantTemplateGroupStorager2  RTDoseStorageRTStructureSetStorageRTBeamsTreatmentRecordStorageRTBrachyTreatmentRecordStorageRTTreatmentSummaryRecordStorage RTIonBeamsTreatmentRecordStorage)GrayscaleSoftcopyPresentationStateStorage%ColorSoftcopyPresentationStateStorage+PseudoColorSoftcopyPresentationStateStorage(BlendingSoftcopyPresentationStateStorage.XAXRFGrayscaleSoftcopyPresentationStateStorageBasicStructuredDisplayStorageBasicVoiceAudioWaveformStorageTwelveLeadECGWaveformStorageGeneralECGWaveformStorageAmbulatoryECGWaveformStorageHemodynamicWaveformStorage'CardiacElectrophysiologyWaveformStorageArterialPulseWaveformStorageRespiratoryWaveformStorageGeneralAudioWaveformStorage/RoutineScalpElectroencephalogramWaveformStorageElectromyogramWaveformStorageElectrooculogramWaveformStorage(SleepElectroencephalogramWaveformStorage&MultichannelRespiratoryWaveformStorageBodyPositionWaveformStorageBasicTextSRStorageEnhancedSRStorageComprehensiveSRStorageMammographyCADSRStorageChestCADSRStorageProcedureLogStorageXRayRadiationDoseSRStorage"SpectaclePrescriptionReportStorageColonCADSRStorage*MacularGridThicknessAndVolumeReportStorageImplantationPlanSRStorageComprehensive3DSRStorage)RadiopharmaceuticalRadiationDoseSRStorageExtensibleSRStorageAcquisitionContextSRStorageSimplifiedAdultEchoSRStoragePatientRadiationDoseSRStorage*PlannedImagingAgentAdministrationSRStorage,PerformedImagingAgentAdministrationSRStorage!KeyObjectSelectionDocumentStorageMRSpectroscopyStorageRawDataStorageSpatialRegistrationStorage$DeformableSpatialRegistrationStorageSpatialFiducialsStorageRealWorldValueMappingStorageStereometricRelationshipStorageLensometryMeasurementsStorage!AutorefractionMeasurementsStorageKeratometryMeasurementsStorage'SubjectiveRefractionMeasurementsStorageVisualAcuityMeasurementsStorage"OphthalmicAxialMeasurementsStorage7OphthalmicVisualFieldStaticPerimetryMeasurementsStorageSurfaceSegmentationStorageSurfaceScanMeshStorageSurfaceScanPointCloudStorageTractographyResultsStorageContentAssessmentResultsStorager7  r  r  rq   rq   rq   rr   <module>   sl  

	
"
6-   y w          
$	
!-	
 !"#$%&'()*+,-./01234F
