Skip to content

Patient

pyorthanc.Patient

Bases: Resource

Represent a Patient that is in an Orthanc server

This object has many getters that allow the user to retrieve metadata or the entire DICOM file of the Patient

Source code in pyorthanc/_resources/patient.py
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
class Patient(Resource):
    """Represent a Patient that is in an Orthanc server

    This object has many getters that allow the user to retrieve metadata
    or the entire DICOM file of the Patient
    """

    def get_main_information(self) -> Dict:
        """Get Patient information

        Returns
        -------
        Dict
            Dictionary of patient main information.
        """
        return self.client.get_patients_id(self.id_)

    @property
    def patient_id(self) -> str:
        """Get patient ID"""
        return self._get_main_dicom_tag_value('PatientID')

    @property
    def name(self) -> str:
        """Get patient name"""
        return self._get_main_dicom_tag_value('PatientName')

    @property
    def birth_date(self) -> datetime:
        """Get patient birthdate"""
        date = self._get_main_dicom_tag_value('PatientBirthDate')

        return util.make_datetime_from_dicom_date(date)

    @property
    def sex(self) -> str:
        """Get patient sex"""
        return self._get_main_dicom_tag_value('PatientSex')

    @property
    def other_patient_ids(self) -> str:
        return self._get_main_dicom_tag_value('OtherPatientIDs').split('\\')

    @property
    def is_stable(self):
        return self.get_main_information()['IsStable']

    @property
    def last_update(self) -> datetime:
        last_updated_date_and_time = self.get_main_information()['LastUpdate'].split('T')
        date = last_updated_date_and_time[0]
        time = last_updated_date_and_time[1]

        return util.make_datetime_from_dicom_date(date, time)

    @property
    def labels(self) -> List[str]:
        return self.get_main_information()['Labels']

    def add_label(self, label: str) -> None:
        self.client.put_patients_id_labels_label(self.id_, label)

    def remove_label(self, label):
        self.client.delete_patients_id_labels_label(self.id_, label)

    def get_zip(self) -> bytes:
        """Get the bytes of the zip file

        Get the .zip file.

        Returns
        -------
        bytes
            Bytes of Zip file of the patient.

        Examples
        --------
        ```python
        from pyorthanc import Orthanc, Patient
        a_patient = Patient(
            'A_PATIENT_IDENTIFIER',
            Orthanc('http://localhost:8042')
        )
        bytes_content = a_patient.get_zip()
        with open('patient_zip_file_path.zip', 'wb') as file_handler:
            file_handler.write(bytes_content)
        ```
        """
        return self.client.get_patients_id_archive(self.id_)

    def download(self, filepath: Union[str, BinaryIO], with_progres: bool = False) -> None:
        """Download the zip file to a target path or buffer

        This method is an alternative to the `.get_zip()` method for large files.
        The `.get_zip()` method will pull all the data in a single GET call,
        while `.download()` stream the data to a file or a buffer.
        Favor the `.download()` method to avoid timeout and memory issues.

        Examples
        --------
        ```python
        from pyorthanc import Orthanc, Patient
        a_patient = Patient('A_PATIENT_IDENTIFIER', Orthanc('http://localhost:8042'))

        # Download a zip
        a_patient.download('patient.zip')

        # Download a zip and show progress
        a_patient.download('patient.zip', with_progres=True)

        # Or download in a buffer in memory
        buffer = io.BytesIO()
        a_patient.download(buffer)
        # Now do whatever you want to do
        buffer.seek(0)
        zip_bytes = buffer.read()
        ```
        """
        self._download_file(f'{self.client.url}/patients/{self.id_}/archive', filepath, with_progres)

    def get_patient_module(self, simplify: bool = False, short: bool = False) -> Dict:
        """Get patient module in a simplified version

        The method returns the DICOM patient module
        (PatientName, PatientID, PatientBirthDate, ...)

        Parameters
        ----------
        simplify
            Get the simplified version of the tags
        short
            Get the short version of the tags

        Returns
        -------
        Dict
            DICOM Patient module.
        """
        params = self._make_response_format_params(simplify, short)

        return dict(self.client.get_patients_id_module(
            self.id_,
            params=params
        ))

    @property
    def protected(self) -> bool:
        """Get if patient is protected against recycling

        Protection against recycling: False means unprotected, True protected.

        Returns
        -------
        bool
            False means unprotected, True means protected.
        """
        return '1' == self.client.get_patients_id_protected(self.id_)

    @protected.setter
    def protected(self, value: bool):
        # As of version 1.11.1, the Orthanc OPEN API file has missing information
        self.client._put(
            f'{self.client.url}/patients/{self.id_}/protected',
            json=1 if value else 0  # 1 means it will be protected, 0 means unprotected
        )

    def is_protected(self) -> bool:
        """Get if patient is protected against recycling

        Protection against recycling: False means unprotected, True protected.

        Returns
        -------
        bool
            False means unprotected, True means protected.
        """
        DeprecationWarning(
            '`patient.is_protected()` is deprecated and will be removed in future release. '
            'Use `patient.protected` instead.'
        )
        return self.protected

    def set_to_protected(self):
        """Set patient to protected state

        Returns
        -------
        None
            Nothing.
        """
        # As of version 1.11.1, the Orthanc OPEN API file has missing information
        warnings.warn(
            '`patient.set_to_protected()` is deprecated and will be removed in future release. '
            'Use `patient.protected = True` instead.',
            DeprecationWarning
        )
        self.protected = True

    def set_to_unprotected(self):
        """Set patient to unprotected state

        Returns
        -------
        None
            Nothing.
        """
        # As of version 1.11.1, the Orthanc OPEN API file has missing information
        warnings.warn(
            '`patient.set_to_protected()` is deprecated and will be removed in future release. '
            'Use `patient.protected = True` instead.',
            DeprecationWarning
        )
        self.protected = False

    @property
    def studies(self) -> List[Study]:
        """Get patient's studies

        Returns
        -------
        List[Study]
            List of the patient's studies
        """
        if self._lock_children:
            if self._child_resources is None:
                studies_ids = self.get_main_information()['Studies']
                self._child_resources = [Study(i, self.client, self._lock_children) for i in studies_ids]

            return self._child_resources

        studies_ids = self.get_main_information()['Studies']

        return [Study(i, self.client) for i in studies_ids]

    def anonymize(self, remove: List = None, replace: Dict = None, keep: List = None,
                  force: bool = False, keep_private_tags: bool = False,
                  keep_source: bool = True, priority: int = 0, permissive: bool = False,
                  private_creator: str = None, dicom_version: str = None) -> 'Patient':
        """Anonymize patient

        If no error has been raise, then it creates a new anonymous patient.
        Documentation: https://book.orthanc-server.com/users/anonymization.html

        Notes
        -----
        This method might be long to run, especially on large patient or when multiple
        patients are anonymized. In those cases, it is recommended to use the `.anonymize_as_job()`

        Parameters
        ----------
        remove
            List of tag to remove
        replace
            Dictionary of {tag: new_content}
        keep
            List of tag to keep unchanged
        force
            Some tags can't be changed without forcing it (e.g. PatientID) for security reason
        keep_private_tags
            If True, keep the private tags from the DICOM instances.
        keep_source
            If False, instructs Orthanc to the remove original resources.
            By default, the original resources are kept in Orthanc.
        priority
            Priority of the job. The lower the value, the higher the priority.
        permissive
            If True, ignore errors during the individual steps of the job.
        private_creator
            The private creator to be used for private tags in Replace.
        dicom_version
            Version of the DICOM standard to be used for anonymization.
            Check out configuration option DeidentifyLogsDicomVersion for possible values.

        Returns
        -------
        Patient
            A New anonymous patient.

        Examples
        --------
        ```python
        new_patient = patient.anonymize()

        new_patient_with_specific_patient_id = patient.anonymize(
            keep=['PatientName'],
            replace={'PatientID': 'TheNewPatientID'},
            force=True
        )
        ```
        """
        remove = [] if remove is None else remove
        replace = {} if replace is None else replace
        keep = [] if keep is None else keep

        data = {
            'Asynchronous': False,
            'Remove': remove,
            'Replace': replace,
            'Keep': keep,
            'Force': force,
            'KeepPrivateTags': keep_private_tags,
            'KeepSource': keep_source,
            'Priority': priority,
            'Permissive': permissive,
        }
        if private_creator is not None:
            data['PrivateCreator'] = private_creator
        if dicom_version is not None:
            data['DicomVersion'] = dicom_version

        try:
            anonymous_patient = self.client.post_patients_id_anonymize(self.id_, data)
        except ReadTimeout:
            raise ReadTimeout(
                'Patient anonymization is too long to process. '
                'Use `.anonymize_as_job` or increase client.timeout.'
            )

        return Patient(anonymous_patient['ID'], self.client)

    def anonymize_as_job(self, remove: List = None, replace: Dict = None, keep: List = None,
                         force: bool = False, keep_private_tags: bool = False,
                         keep_source: bool = True, priority: int = 0, permissive: bool = False,
                         private_creator: str = None, dicom_version: str = None) -> Job:
        """Anonymize patient and return a job

        Launch an anonymization job.
        Documentation: https://book.orthanc-server.com/users/anonymization.html

        Notes
        -----
        This method is useful when anonymizing large patient or launching many
        anonymization jobs. The jobs are sent to Orthanc and processed according
        to the priority.

        Parameters
        ----------
        remove
            List of tag to remove
        replace
            Dictionary of {tag: new_content}
        keep
            List of tag to keep unchanged
        force
            Some tags can't be changed without forcing it (e.g. PatientID) for security reason
        keep_private_tags
            If True, keep the private tags from the DICOM instances.
        keep_source
            If False, instructs Orthanc to the remove original resources.
            By default, the original resources are kept in Orthanc.
        priority
            Priority of the job. The lower the value, the higher the priority.
        permissive
            If True, ignore errors during the individual steps of the job.
        private_creator
            The private creator to be used for private tags in Replace.
        dicom_version
            Version of the DICOM standard to be used for anonymization.
            Check out configuration option DeidentifyLogsDicomVersion for possible values.

        Returns
        -------
        Job
            Return a Job object of the anonymization job.

        Examples
        --------
        For large patient (recommended)
        ```python
        job = patient.anonymize_as_job()
        job.state  # You can follow the job state

        job.wait_until_completion() # Or just wait on its completion
        new_patient = Patient(job.content['ID'], orthanc)
        ```
        """
        remove = [] if remove is None else remove
        replace = {} if replace is None else replace
        keep = [] if keep is None else keep

        data = {
            'Asynchronous': True,
            'Remove': remove,
            'Replace': replace,
            'Keep': keep,
            'Force': force,
            'KeepPrivateTags': keep_private_tags,
            'KeepSource': keep_source,
            'Priority': priority,
            'Permissive': permissive,
        }
        if private_creator is not None:
            data['PrivateCreator'] = private_creator
        if dicom_version is not None:
            data['DicomVersion'] = dicom_version

        job_info = self.client.post_patients_id_anonymize(self.id_, data)

        return Job(job_info['ID'], self.client)

    def modify(self, remove: List = None, replace: Dict = None, keep: List = None,
               force: bool = False, remove_private_tags: bool = False,
               keep_source: bool = True, priority: int = 0, permissive: bool = False,
               private_creator: str = None) -> 'Patient':
        """Modify patient

        If no error has been raise, then modify the patient. If the PatientID is replaced
        (with `force=True`), then return a new patient.
        Documentation: https://book.orthanc-server.com/users/anonymization.html

        Notes
        -----
        This method might be long to run, especially on large patient or when multiple
        patients are modified. In those cases, it is recommended to use the `.modify_as_job()`

        Parameters
        ----------
        remove
            List of tag to remove
        replace
            Dictionary of {tag: new_content}
        keep
            Keep the original value of the specified tags, to be chosen among the StudyInstanceUID,
            SeriesInstanceUID and SOPInstanceUID tags. Avoid this feature as much as possible,
            as this breaks the DICOM model of the real world.
        force
            Some tags can't be changed without forcing it (e.g. PatientID) for security reason
        remove_private_tags
            If True, remove the private tags from the DICOM instances.
        keep_source
            If False, instructs Orthanc to the remove original resources.
            By default, the original resources are kept in Orthanc.
        priority
            Priority of the job. The lower the value, the higher the priority.
        permissive
            If True, ignore errors during the individual steps of the job.
        private_creator
            The private creator to be used for private tags in Replace.

        Returns
        -------
        Patient
            Returns a new patient if the "PatientID" tag has been replaced,
            returns itself if not (in this case, the patient itself is modified).

        Examples
        --------
        ```python
        patient.modify(remove=['PatientName'])
        patient.name  # will raise

        modified_patient = patient.modify(replace={'PatientID': 'TheNewPatientID'}, force=True)
        assert modified_patient.patient_id == 'TheNewPatientID'
        ```
        """
        remove = [] if remove is None else remove
        replace = {} if replace is None else replace
        keep = [] if keep is None else keep

        if 'PatientID' in replace and not force:
            raise errors.ModificationError('If PatientID is replaced, `force` must be `True`')

        data = {
            'Asynchronous': False,
            'Remove': remove,
            'Replace': replace,
            'Keep': keep,
            'Force': force,
            'RemovePrivateTags': remove_private_tags,
            'KeepSource': keep_source,
            'Priority': priority,
            'Permissive': permissive,
        }
        if private_creator is not None:
            data['PrivateCreator'] = private_creator

        try:
            modified_patient = self.client.post_patients_id_modify(self.id_, data)
        except ReadTimeout:
            raise ReadTimeout(
                'Patient modification is too long to process. '
                'Use `.modify_as_job` or increase client.timeout.'
            )

        # Reset cache since a main DICOM tag may have be changed
        self._main_dicom_tags = None

        # if 'PatientID' is not affected, the modified_patient['ID'] is the same as self.id_
        return Patient(modified_patient['ID'], self.client)

    def modify_as_job(self, remove: List = None, replace: Dict = None, keep: List = None,
                      force: bool = False, remove_private_tags: bool = False,
                      keep_source: bool = True, priority: int = 0, permissive: bool = False,
                      private_creator: str = None) -> Job:
        """Modify patient and return a job

        Launch a modification job. If the PatientID is replaced (with `force=True`),
        then return a new patient. If the PatientID is not replaced, the patient itself is modified.
        Documentation: https://book.orthanc-server.com/users/anonymization.html

        Notes
        -----
        This method is useful when modifying large patient or launching many
        modification jobs. The jobs are sent to Orthanc and processed according
        to the priority.

        Parameters
        ----------
        remove
            List of tag to remove
        replace
            Dictionary of {tag: new_content}
        keep
            Keep the original value of the specified tags, to be chosen among the StudyInstanceUID,
            SeriesInstanceUID and SOPInstanceUID tags. Avoid this feature as much as possible,
            as this breaks the DICOM model of the real world.
        force
            Allow the modification of tags related to DICOM identifiers, at the risk of breaking
            the DICOM model of the real world.
        remove_private_tags
            If True, remove the private tags from the DICOM instances.
        keep_source
            If False, instructs Orthanc to the remove original resources.
            By default, the original resources are kept in Orthanc.
        priority
            Priority of the job. The lower the value, the higher the priority.
        permissive
            If True, ignore errors during the individual steps of the job.
        private_creator
            The private creator to be used for private tags in Replace.

        Returns
        -------
        Job
            Return a Job object of the anonymization job.

        Examples
        --------
        For large patient (recommended)
        ```python
        job = patient.modify_as_job(replace={'PatientName': 'NewName'})
        job.state  # You can follow the job state

        job.wait_until_completion() # Or just wait on its completion
        assert patient.name == 'NewName'
        ```
        Or modify the PatientID
        ```python
        job = patient.modify_as_job(replace={'PatientID': 'new-id'}, force=True)
        job.wait_until_completion() # Or just wait on its completion

        modified_patient = Patient(job.content['ID'], client)
        assert patient.patient_id != 'new_id'
        assert modified_patient.patient_id == 'new_id'
        ```
        """
        remove = [] if remove is None else remove
        replace = {} if replace is None else replace
        keep = [] if keep is None else keep

        if 'PatientID' in replace and not force:
            raise errors.ModificationError('If PatientID is affected, `force` must be `True`')

        data = {
            'Asynchronous': True,
            'Remove': remove,
            'Replace': replace,
            'Keep': keep,
            'Force': force,
            'RemovePrivateTags': remove_private_tags,
            'KeepSource': keep_source,
            'Priority': priority,
            'Permissive': permissive,
        }
        if private_creator is not None:
            data['PrivateCreator'] = private_creator

        job_info = self.client.post_patients_id_modify(self.id_, data)

        # Reset cache since a main DICOM tag may have be changed
        self._main_dicom_tags = None

        return Job(job_info['ID'], self.client)

    def get_shared_tags(self, simplify: bool = False, short: bool = False) -> Dict:
        """Retrieve the shared tags of the patient"""
        params = self._make_response_format_params(simplify, short)

        return dict(self.client.get_patients_id_shared_tags(
            self.id_,
            params=params
        ))

    @property
    def shared_tags(self) -> Dict:
        return self.get_shared_tags(simplify=True)

    def remove_empty_studies(self) -> None:
        """Delete empty studies."""
        if self._child_resources is None:
            return

        for study in self._child_resources:
            study.remove_empty_series()

        self._child_resources = [study for study in self._child_resources if study._child_resources != []]

birth_date: datetime property

Get patient birthdate

is_stable property

labels: List[str] property

last_update: datetime property

name: str property

Get patient name

other_patient_ids: str property

patient_id: str property

Get patient ID

protected: bool property writable

Get if patient is protected against recycling

Protection against recycling: False means unprotected, True protected.

Returns:

Type Description
bool

False means unprotected, True means protected.

sex: str property

Get patient sex

shared_tags: Dict property

studies: List[Study] property

Get patient's studies

Returns:

Type Description
List[Study]

List of the patient's studies

add_label(label)

Source code in pyorthanc/_resources/patient.py
72
73
def add_label(self, label: str) -> None:
    self.client.put_patients_id_labels_label(self.id_, label)

anonymize(remove=None, replace=None, keep=None, force=False, keep_private_tags=False, keep_source=True, priority=0, permissive=False, private_creator=None, dicom_version=None)

Anonymize patient

If no error has been raise, then it creates a new anonymous patient. Documentation: https://book.orthanc-server.com/users/anonymization.html

Notes

This method might be long to run, especially on large patient or when multiple patients are anonymized. In those cases, it is recommended to use the .anonymize_as_job()

Parameters:

Name Type Description Default
remove List

List of tag to remove

None
replace Dict

Dictionary of {tag: new_content}

None
keep List

List of tag to keep unchanged

None
force bool

Some tags can't be changed without forcing it (e.g. PatientID) for security reason

False
keep_private_tags bool

If True, keep the private tags from the DICOM instances.

False
keep_source bool

If False, instructs Orthanc to the remove original resources. By default, the original resources are kept in Orthanc.

True
priority int

Priority of the job. The lower the value, the higher the priority.

0
permissive bool

If True, ignore errors during the individual steps of the job.

False
private_creator str

The private creator to be used for private tags in Replace.

None
dicom_version str

Version of the DICOM standard to be used for anonymization. Check out configuration option DeidentifyLogsDicomVersion for possible values.

None

Returns:

Type Description
Patient

A New anonymous patient.

Examples:

new_patient = patient.anonymize()

new_patient_with_specific_patient_id = patient.anonymize(
    keep=['PatientName'],
    replace={'PatientID': 'TheNewPatientID'},
    force=True
)
Source code in pyorthanc/_resources/patient.py
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
def anonymize(self, remove: List = None, replace: Dict = None, keep: List = None,
              force: bool = False, keep_private_tags: bool = False,
              keep_source: bool = True, priority: int = 0, permissive: bool = False,
              private_creator: str = None, dicom_version: str = None) -> 'Patient':
    """Anonymize patient

    If no error has been raise, then it creates a new anonymous patient.
    Documentation: https://book.orthanc-server.com/users/anonymization.html

    Notes
    -----
    This method might be long to run, especially on large patient or when multiple
    patients are anonymized. In those cases, it is recommended to use the `.anonymize_as_job()`

    Parameters
    ----------
    remove
        List of tag to remove
    replace
        Dictionary of {tag: new_content}
    keep
        List of tag to keep unchanged
    force
        Some tags can't be changed without forcing it (e.g. PatientID) for security reason
    keep_private_tags
        If True, keep the private tags from the DICOM instances.
    keep_source
        If False, instructs Orthanc to the remove original resources.
        By default, the original resources are kept in Orthanc.
    priority
        Priority of the job. The lower the value, the higher the priority.
    permissive
        If True, ignore errors during the individual steps of the job.
    private_creator
        The private creator to be used for private tags in Replace.
    dicom_version
        Version of the DICOM standard to be used for anonymization.
        Check out configuration option DeidentifyLogsDicomVersion for possible values.

    Returns
    -------
    Patient
        A New anonymous patient.

    Examples
    --------
    ```python
    new_patient = patient.anonymize()

    new_patient_with_specific_patient_id = patient.anonymize(
        keep=['PatientName'],
        replace={'PatientID': 'TheNewPatientID'},
        force=True
    )
    ```
    """
    remove = [] if remove is None else remove
    replace = {} if replace is None else replace
    keep = [] if keep is None else keep

    data = {
        'Asynchronous': False,
        'Remove': remove,
        'Replace': replace,
        'Keep': keep,
        'Force': force,
        'KeepPrivateTags': keep_private_tags,
        'KeepSource': keep_source,
        'Priority': priority,
        'Permissive': permissive,
    }
    if private_creator is not None:
        data['PrivateCreator'] = private_creator
    if dicom_version is not None:
        data['DicomVersion'] = dicom_version

    try:
        anonymous_patient = self.client.post_patients_id_anonymize(self.id_, data)
    except ReadTimeout:
        raise ReadTimeout(
            'Patient anonymization is too long to process. '
            'Use `.anonymize_as_job` or increase client.timeout.'
        )

    return Patient(anonymous_patient['ID'], self.client)

anonymize_as_job(remove=None, replace=None, keep=None, force=False, keep_private_tags=False, keep_source=True, priority=0, permissive=False, private_creator=None, dicom_version=None)

Anonymize patient and return a job

Launch an anonymization job. Documentation: https://book.orthanc-server.com/users/anonymization.html

Notes

This method is useful when anonymizing large patient or launching many anonymization jobs. The jobs are sent to Orthanc and processed according to the priority.

Parameters:

Name Type Description Default
remove List

List of tag to remove

None
replace Dict

Dictionary of {tag: new_content}

None
keep List

List of tag to keep unchanged

None
force bool

Some tags can't be changed without forcing it (e.g. PatientID) for security reason

False
keep_private_tags bool

If True, keep the private tags from the DICOM instances.

False
keep_source bool

If False, instructs Orthanc to the remove original resources. By default, the original resources are kept in Orthanc.

True
priority int

Priority of the job. The lower the value, the higher the priority.

0
permissive bool

If True, ignore errors during the individual steps of the job.

False
private_creator str

The private creator to be used for private tags in Replace.

None
dicom_version str

Version of the DICOM standard to be used for anonymization. Check out configuration option DeidentifyLogsDicomVersion for possible values.

None

Returns:

Type Description
Job

Return a Job object of the anonymization job.

Examples:

For large patient (recommended)

job = patient.anonymize_as_job()
job.state  # You can follow the job state

job.wait_until_completion() # Or just wait on its completion
new_patient = Patient(job.content['ID'], orthanc)
Source code in pyorthanc/_resources/patient.py
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
def anonymize_as_job(self, remove: List = None, replace: Dict = None, keep: List = None,
                     force: bool = False, keep_private_tags: bool = False,
                     keep_source: bool = True, priority: int = 0, permissive: bool = False,
                     private_creator: str = None, dicom_version: str = None) -> Job:
    """Anonymize patient and return a job

    Launch an anonymization job.
    Documentation: https://book.orthanc-server.com/users/anonymization.html

    Notes
    -----
    This method is useful when anonymizing large patient or launching many
    anonymization jobs. The jobs are sent to Orthanc and processed according
    to the priority.

    Parameters
    ----------
    remove
        List of tag to remove
    replace
        Dictionary of {tag: new_content}
    keep
        List of tag to keep unchanged
    force
        Some tags can't be changed without forcing it (e.g. PatientID) for security reason
    keep_private_tags
        If True, keep the private tags from the DICOM instances.
    keep_source
        If False, instructs Orthanc to the remove original resources.
        By default, the original resources are kept in Orthanc.
    priority
        Priority of the job. The lower the value, the higher the priority.
    permissive
        If True, ignore errors during the individual steps of the job.
    private_creator
        The private creator to be used for private tags in Replace.
    dicom_version
        Version of the DICOM standard to be used for anonymization.
        Check out configuration option DeidentifyLogsDicomVersion for possible values.

    Returns
    -------
    Job
        Return a Job object of the anonymization job.

    Examples
    --------
    For large patient (recommended)
    ```python
    job = patient.anonymize_as_job()
    job.state  # You can follow the job state

    job.wait_until_completion() # Or just wait on its completion
    new_patient = Patient(job.content['ID'], orthanc)
    ```
    """
    remove = [] if remove is None else remove
    replace = {} if replace is None else replace
    keep = [] if keep is None else keep

    data = {
        'Asynchronous': True,
        'Remove': remove,
        'Replace': replace,
        'Keep': keep,
        'Force': force,
        'KeepPrivateTags': keep_private_tags,
        'KeepSource': keep_source,
        'Priority': priority,
        'Permissive': permissive,
    }
    if private_creator is not None:
        data['PrivateCreator'] = private_creator
    if dicom_version is not None:
        data['DicomVersion'] = dicom_version

    job_info = self.client.post_patients_id_anonymize(self.id_, data)

    return Job(job_info['ID'], self.client)

download(filepath, with_progres=False)

Download the zip file to a target path or buffer

This method is an alternative to the .get_zip() method for large files. The .get_zip() method will pull all the data in a single GET call, while .download() stream the data to a file or a buffer. Favor the .download() method to avoid timeout and memory issues.

Examples:

from pyorthanc import Orthanc, Patient
a_patient = Patient('A_PATIENT_IDENTIFIER', Orthanc('http://localhost:8042'))

# Download a zip
a_patient.download('patient.zip')

# Download a zip and show progress
a_patient.download('patient.zip', with_progres=True)

# Or download in a buffer in memory
buffer = io.BytesIO()
a_patient.download(buffer)
# Now do whatever you want to do
buffer.seek(0)
zip_bytes = buffer.read()
Source code in pyorthanc/_resources/patient.py
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
def download(self, filepath: Union[str, BinaryIO], with_progres: bool = False) -> None:
    """Download the zip file to a target path or buffer

    This method is an alternative to the `.get_zip()` method for large files.
    The `.get_zip()` method will pull all the data in a single GET call,
    while `.download()` stream the data to a file or a buffer.
    Favor the `.download()` method to avoid timeout and memory issues.

    Examples
    --------
    ```python
    from pyorthanc import Orthanc, Patient
    a_patient = Patient('A_PATIENT_IDENTIFIER', Orthanc('http://localhost:8042'))

    # Download a zip
    a_patient.download('patient.zip')

    # Download a zip and show progress
    a_patient.download('patient.zip', with_progres=True)

    # Or download in a buffer in memory
    buffer = io.BytesIO()
    a_patient.download(buffer)
    # Now do whatever you want to do
    buffer.seek(0)
    zip_bytes = buffer.read()
    ```
    """
    self._download_file(f'{self.client.url}/patients/{self.id_}/archive', filepath, with_progres)

get_main_information()

Get Patient information

Returns:

Type Description
Dict

Dictionary of patient main information.

Source code in pyorthanc/_resources/patient.py
20
21
22
23
24
25
26
27
28
def get_main_information(self) -> Dict:
    """Get Patient information

    Returns
    -------
    Dict
        Dictionary of patient main information.
    """
    return self.client.get_patients_id(self.id_)

get_patient_module(simplify=False, short=False)

Get patient module in a simplified version

The method returns the DICOM patient module (PatientName, PatientID, PatientBirthDate, ...)

Parameters:

Name Type Description Default
simplify bool

Get the simplified version of the tags

False
short bool

Get the short version of the tags

False

Returns:

Type Description
Dict

DICOM Patient module.

Source code in pyorthanc/_resources/patient.py
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
def get_patient_module(self, simplify: bool = False, short: bool = False) -> Dict:
    """Get patient module in a simplified version

    The method returns the DICOM patient module
    (PatientName, PatientID, PatientBirthDate, ...)

    Parameters
    ----------
    simplify
        Get the simplified version of the tags
    short
        Get the short version of the tags

    Returns
    -------
    Dict
        DICOM Patient module.
    """
    params = self._make_response_format_params(simplify, short)

    return dict(self.client.get_patients_id_module(
        self.id_,
        params=params
    ))

get_shared_tags(simplify=False, short=False)

Retrieve the shared tags of the patient

Source code in pyorthanc/_resources/patient.py
597
598
599
600
601
602
603
604
def get_shared_tags(self, simplify: bool = False, short: bool = False) -> Dict:
    """Retrieve the shared tags of the patient"""
    params = self._make_response_format_params(simplify, short)

    return dict(self.client.get_patients_id_shared_tags(
        self.id_,
        params=params
    ))

get_zip()

Get the bytes of the zip file

Get the .zip file.

Returns:

Type Description
bytes

Bytes of Zip file of the patient.

Examples:

from pyorthanc import Orthanc, Patient
a_patient = Patient(
    'A_PATIENT_IDENTIFIER',
    Orthanc('http://localhost:8042')
)
bytes_content = a_patient.get_zip()
with open('patient_zip_file_path.zip', 'wb') as file_handler:
    file_handler.write(bytes_content)
Source code in pyorthanc/_resources/patient.py
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
def get_zip(self) -> bytes:
    """Get the bytes of the zip file

    Get the .zip file.

    Returns
    -------
    bytes
        Bytes of Zip file of the patient.

    Examples
    --------
    ```python
    from pyorthanc import Orthanc, Patient
    a_patient = Patient(
        'A_PATIENT_IDENTIFIER',
        Orthanc('http://localhost:8042')
    )
    bytes_content = a_patient.get_zip()
    with open('patient_zip_file_path.zip', 'wb') as file_handler:
        file_handler.write(bytes_content)
    ```
    """
    return self.client.get_patients_id_archive(self.id_)

is_protected()

Get if patient is protected against recycling

Protection against recycling: False means unprotected, True protected.

Returns:

Type Description
bool

False means unprotected, True means protected.

Source code in pyorthanc/_resources/patient.py
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
def is_protected(self) -> bool:
    """Get if patient is protected against recycling

    Protection against recycling: False means unprotected, True protected.

    Returns
    -------
    bool
        False means unprotected, True means protected.
    """
    DeprecationWarning(
        '`patient.is_protected()` is deprecated and will be removed in future release. '
        'Use `patient.protected` instead.'
    )
    return self.protected

modify(remove=None, replace=None, keep=None, force=False, remove_private_tags=False, keep_source=True, priority=0, permissive=False, private_creator=None)

Modify patient

If no error has been raise, then modify the patient. If the PatientID is replaced (with force=True), then return a new patient. Documentation: https://book.orthanc-server.com/users/anonymization.html

Notes

This method might be long to run, especially on large patient or when multiple patients are modified. In those cases, it is recommended to use the .modify_as_job()

Parameters:

Name Type Description Default
remove List

List of tag to remove

None
replace Dict

Dictionary of {tag: new_content}

None
keep List

Keep the original value of the specified tags, to be chosen among the StudyInstanceUID, SeriesInstanceUID and SOPInstanceUID tags. Avoid this feature as much as possible, as this breaks the DICOM model of the real world.

None
force bool

Some tags can't be changed without forcing it (e.g. PatientID) for security reason

False
remove_private_tags bool

If True, remove the private tags from the DICOM instances.

False
keep_source bool

If False, instructs Orthanc to the remove original resources. By default, the original resources are kept in Orthanc.

True
priority int

Priority of the job. The lower the value, the higher the priority.

0
permissive bool

If True, ignore errors during the individual steps of the job.

False
private_creator str

The private creator to be used for private tags in Replace.

None

Returns:

Type Description
Patient

Returns a new patient if the "PatientID" tag has been replaced, returns itself if not (in this case, the patient itself is modified).

Examples:

patient.modify(remove=['PatientName'])
patient.name  # will raise

modified_patient = patient.modify(replace={'PatientID': 'TheNewPatientID'}, force=True)
assert modified_patient.patient_id == 'TheNewPatientID'
Source code in pyorthanc/_resources/patient.py
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
def modify(self, remove: List = None, replace: Dict = None, keep: List = None,
           force: bool = False, remove_private_tags: bool = False,
           keep_source: bool = True, priority: int = 0, permissive: bool = False,
           private_creator: str = None) -> 'Patient':
    """Modify patient

    If no error has been raise, then modify the patient. If the PatientID is replaced
    (with `force=True`), then return a new patient.
    Documentation: https://book.orthanc-server.com/users/anonymization.html

    Notes
    -----
    This method might be long to run, especially on large patient or when multiple
    patients are modified. In those cases, it is recommended to use the `.modify_as_job()`

    Parameters
    ----------
    remove
        List of tag to remove
    replace
        Dictionary of {tag: new_content}
    keep
        Keep the original value of the specified tags, to be chosen among the StudyInstanceUID,
        SeriesInstanceUID and SOPInstanceUID tags. Avoid this feature as much as possible,
        as this breaks the DICOM model of the real world.
    force
        Some tags can't be changed without forcing it (e.g. PatientID) for security reason
    remove_private_tags
        If True, remove the private tags from the DICOM instances.
    keep_source
        If False, instructs Orthanc to the remove original resources.
        By default, the original resources are kept in Orthanc.
    priority
        Priority of the job. The lower the value, the higher the priority.
    permissive
        If True, ignore errors during the individual steps of the job.
    private_creator
        The private creator to be used for private tags in Replace.

    Returns
    -------
    Patient
        Returns a new patient if the "PatientID" tag has been replaced,
        returns itself if not (in this case, the patient itself is modified).

    Examples
    --------
    ```python
    patient.modify(remove=['PatientName'])
    patient.name  # will raise

    modified_patient = patient.modify(replace={'PatientID': 'TheNewPatientID'}, force=True)
    assert modified_patient.patient_id == 'TheNewPatientID'
    ```
    """
    remove = [] if remove is None else remove
    replace = {} if replace is None else replace
    keep = [] if keep is None else keep

    if 'PatientID' in replace and not force:
        raise errors.ModificationError('If PatientID is replaced, `force` must be `True`')

    data = {
        'Asynchronous': False,
        'Remove': remove,
        'Replace': replace,
        'Keep': keep,
        'Force': force,
        'RemovePrivateTags': remove_private_tags,
        'KeepSource': keep_source,
        'Priority': priority,
        'Permissive': permissive,
    }
    if private_creator is not None:
        data['PrivateCreator'] = private_creator

    try:
        modified_patient = self.client.post_patients_id_modify(self.id_, data)
    except ReadTimeout:
        raise ReadTimeout(
            'Patient modification is too long to process. '
            'Use `.modify_as_job` or increase client.timeout.'
        )

    # Reset cache since a main DICOM tag may have be changed
    self._main_dicom_tags = None

    # if 'PatientID' is not affected, the modified_patient['ID'] is the same as self.id_
    return Patient(modified_patient['ID'], self.client)

modify_as_job(remove=None, replace=None, keep=None, force=False, remove_private_tags=False, keep_source=True, priority=0, permissive=False, private_creator=None)

Modify patient and return a job

Launch a modification job. If the PatientID is replaced (with force=True), then return a new patient. If the PatientID is not replaced, the patient itself is modified. Documentation: https://book.orthanc-server.com/users/anonymization.html

Notes

This method is useful when modifying large patient or launching many modification jobs. The jobs are sent to Orthanc and processed according to the priority.

Parameters:

Name Type Description Default
remove List

List of tag to remove

None
replace Dict

Dictionary of {tag: new_content}

None
keep List

Keep the original value of the specified tags, to be chosen among the StudyInstanceUID, SeriesInstanceUID and SOPInstanceUID tags. Avoid this feature as much as possible, as this breaks the DICOM model of the real world.

None
force bool

Allow the modification of tags related to DICOM identifiers, at the risk of breaking the DICOM model of the real world.

False
remove_private_tags bool

If True, remove the private tags from the DICOM instances.

False
keep_source bool

If False, instructs Orthanc to the remove original resources. By default, the original resources are kept in Orthanc.

True
priority int

Priority of the job. The lower the value, the higher the priority.

0
permissive bool

If True, ignore errors during the individual steps of the job.

False
private_creator str

The private creator to be used for private tags in Replace.

None

Returns:

Type Description
Job

Return a Job object of the anonymization job.

Examples:

For large patient (recommended)

job = patient.modify_as_job(replace={'PatientName': 'NewName'})
job.state  # You can follow the job state

job.wait_until_completion() # Or just wait on its completion
assert patient.name == 'NewName'

Or modify the PatientID

job = patient.modify_as_job(replace={'PatientID': 'new-id'}, force=True)
job.wait_until_completion() # Or just wait on its completion

modified_patient = Patient(job.content['ID'], client)
assert patient.patient_id != 'new_id'
assert modified_patient.patient_id == 'new_id'
Source code in pyorthanc/_resources/patient.py
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
def modify_as_job(self, remove: List = None, replace: Dict = None, keep: List = None,
                  force: bool = False, remove_private_tags: bool = False,
                  keep_source: bool = True, priority: int = 0, permissive: bool = False,
                  private_creator: str = None) -> Job:
    """Modify patient and return a job

    Launch a modification job. If the PatientID is replaced (with `force=True`),
    then return a new patient. If the PatientID is not replaced, the patient itself is modified.
    Documentation: https://book.orthanc-server.com/users/anonymization.html

    Notes
    -----
    This method is useful when modifying large patient or launching many
    modification jobs. The jobs are sent to Orthanc and processed according
    to the priority.

    Parameters
    ----------
    remove
        List of tag to remove
    replace
        Dictionary of {tag: new_content}
    keep
        Keep the original value of the specified tags, to be chosen among the StudyInstanceUID,
        SeriesInstanceUID and SOPInstanceUID tags. Avoid this feature as much as possible,
        as this breaks the DICOM model of the real world.
    force
        Allow the modification of tags related to DICOM identifiers, at the risk of breaking
        the DICOM model of the real world.
    remove_private_tags
        If True, remove the private tags from the DICOM instances.
    keep_source
        If False, instructs Orthanc to the remove original resources.
        By default, the original resources are kept in Orthanc.
    priority
        Priority of the job. The lower the value, the higher the priority.
    permissive
        If True, ignore errors during the individual steps of the job.
    private_creator
        The private creator to be used for private tags in Replace.

    Returns
    -------
    Job
        Return a Job object of the anonymization job.

    Examples
    --------
    For large patient (recommended)
    ```python
    job = patient.modify_as_job(replace={'PatientName': 'NewName'})
    job.state  # You can follow the job state

    job.wait_until_completion() # Or just wait on its completion
    assert patient.name == 'NewName'
    ```
    Or modify the PatientID
    ```python
    job = patient.modify_as_job(replace={'PatientID': 'new-id'}, force=True)
    job.wait_until_completion() # Or just wait on its completion

    modified_patient = Patient(job.content['ID'], client)
    assert patient.patient_id != 'new_id'
    assert modified_patient.patient_id == 'new_id'
    ```
    """
    remove = [] if remove is None else remove
    replace = {} if replace is None else replace
    keep = [] if keep is None else keep

    if 'PatientID' in replace and not force:
        raise errors.ModificationError('If PatientID is affected, `force` must be `True`')

    data = {
        'Asynchronous': True,
        'Remove': remove,
        'Replace': replace,
        'Keep': keep,
        'Force': force,
        'RemovePrivateTags': remove_private_tags,
        'KeepSource': keep_source,
        'Priority': priority,
        'Permissive': permissive,
    }
    if private_creator is not None:
        data['PrivateCreator'] = private_creator

    job_info = self.client.post_patients_id_modify(self.id_, data)

    # Reset cache since a main DICOM tag may have be changed
    self._main_dicom_tags = None

    return Job(job_info['ID'], self.client)

remove_empty_studies()

Delete empty studies.

Source code in pyorthanc/_resources/patient.py
610
611
612
613
614
615
616
617
618
def remove_empty_studies(self) -> None:
    """Delete empty studies."""
    if self._child_resources is None:
        return

    for study in self._child_resources:
        study.remove_empty_series()

    self._child_resources = [study for study in self._child_resources if study._child_resources != []]

remove_label(label)

Source code in pyorthanc/_resources/patient.py
75
76
def remove_label(self, label):
    self.client.delete_patients_id_labels_label(self.id_, label)

set_to_protected()

Set patient to protected state

Returns:

Type Description
None

Nothing.

Source code in pyorthanc/_resources/patient.py
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
def set_to_protected(self):
    """Set patient to protected state

    Returns
    -------
    None
        Nothing.
    """
    # As of version 1.11.1, the Orthanc OPEN API file has missing information
    warnings.warn(
        '`patient.set_to_protected()` is deprecated and will be removed in future release. '
        'Use `patient.protected = True` instead.',
        DeprecationWarning
    )
    self.protected = True

set_to_unprotected()

Set patient to unprotected state

Returns:

Type Description
None

Nothing.

Source code in pyorthanc/_resources/patient.py
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
def set_to_unprotected(self):
    """Set patient to unprotected state

    Returns
    -------
    None
        Nothing.
    """
    # As of version 1.11.1, the Orthanc OPEN API file has missing information
    warnings.warn(
        '`patient.set_to_protected()` is deprecated and will be removed in future release. '
        'Use `patient.protected = True` instead.',
        DeprecationWarning
    )
    self.protected = False