Skip to content

Study

pyorthanc.Study

Bases: Resource

Represent a study 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 Series

Source code in pyorthanc/_resources/study.py
 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
class Study(Resource):
    """Represent a study 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 Series
    """

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

        Returns
        -------
        Dict
            Dictionary of study information
        """
        if self.lock:
            if self._information is None:
                # Setup self._information for the first time when study is lock
                self._information = self.client.get_studies_id(self.id_)

            return self._information

        return self.client.get_studies_id(self.id_)

    @property
    def referring_physician_name(self) -> str:
        """Get referring physician name"""
        return self._get_main_dicom_tag_value('ReferringPhysicianName')

    @property
    def requesting_physician(self) -> str:
        """Get referring physician name"""
        return self._get_main_dicom_tag_value('RequestingPhysician')

    @property
    def date(self) -> datetime:
        """Get study date

        The date have precision to the second (if available).

        Returns
        -------
        datetime
            Study date
        """
        date_string = self._get_main_dicom_tag_value('StudyDate')
        try:
            time_string = self._get_main_dicom_tag_value('StudyTime')
        except errors.TagDoesNotExistError:
            time_string = None

        return util.make_datetime_from_dicom_date(date_string, time_string)

    @property
    def study_id(self) -> str:
        """Get Study ID"""
        return self._get_main_dicom_tag_value('StudyID')

    @property
    def uid(self) -> str:
        """Get StudyInstanceUID"""
        return self._get_main_dicom_tag_value('StudyInstanceUID')

    @property
    def patient_identifier(self) -> str:
        """Get the Orthanc identifier of the parent patient"""
        return self.get_main_information()['ParentPatient']

    @property
    def parent_patient(self) -> Patient:
        from . import Patient
        return Patient(self.patient_identifier, self.client)

    @property
    def patient_information(self) -> Dict:
        """Get patient information"""
        return self.get_main_information()['PatientMainDicomTags']

    @property
    def series(self) -> List[Series]:
        """Get Study series"""
        if self.lock:
            if self._child_resources is None:
                series_ids = self.get_main_information()['Series']
                self._child_resources = [Series(i, self.client, self.lock) for i in series_ids]

            return self._child_resources

        series_ids = self.get_main_information()['Series']

        return [Series(i, self.client, self.lock) for i in series_ids]

    @property
    def accession_number(self) -> str:
        return self._get_main_dicom_tag_value('AccessionNumber')

    @property
    def description(self) -> str:
        return self._get_main_dicom_tag_value('StudyDescription')

    @property
    def institution_name(self) -> str:
        return self._get_main_dicom_tag_value('InstitutionName')

    @property
    def requested_procedure_description(self) -> str:
        return self._get_main_dicom_tag_value('RequestedProcedureDescription')

    @property
    def is_stable(self) -> bool:
        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_studies_id_labels_label(self.id_, label)

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

    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) -> 'Study':
        """Anonymize study

        If no error has been raise, return an anonymous study.
        Documentation: https://book.orthanc-server.com/users/anonymization.html

        Notes
        -----
        This method might be long to run, especially on large study or when multiple
        studies 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. StudyID) 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
        -------
        Study
            A New anonymous study.

        Examples
        --------
        ```python
        new_study = study.anonymize()

        new_study_with_specific_study_id = study.anonymize(
            replace={'StudyDescription': 'A description'}
        )
        """
        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_study = self.client.post_studies_id_anonymize(self.id_, data)
        except ReadTimeout:
            raise ReadTimeout(
                'Study anonymization is too long to process. '
                'Use `.anonymize_as_job` or increase client.timeout.'
            )

        return Study(anonymous_study['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 study 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 study 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. StudyInstanceUID) 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 study (recommended)
        ```python
        job = study.anonymize_as_job()
        job.state  # You can follow the job state

        job.wait_until_completion() # Or just wait on its completion
        new_study = Study(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_studies_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) -> 'Study':
        """Modify study

        If no error has been raise, then create a modified version of the study.
        If keep=['StudyInstanceUID'] and force=True are use, then the study itself is changed.
        Documentation: https://book.orthanc-server.com/users/anonymization.html

        Notes
        -----
        This method might be long to run, especially on large study or when multiple
        studies 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. StudyInstanceUID) 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
        -------
        Study
            Returns a new modified study or returns itself if keep=['StudyInstanceUID']
            (in this case, the study itself is modified).

        Examples
        --------
        ```python
        # Create a modified study
        modified_study = study.modify(replace={'StudyInstanceUID': '1.2.840.113745.101000.1008000.38048.4626.5933732'}, force=True)
        assert modified_study.uid == '1.2.840.113745.101000.1008000.38048.4626.5933732'

        # Modify itself
        study.modify(replace={'ReferringPhysicianName': 'last^first'}, keep=['StudyInstanceUID'], force=True)
        assert study.referring_physician_name == 'last^first'
        ```
        """
        remove = [] if remove is None else remove
        replace = {} if replace is None else replace
        keep = [] if keep is None else keep

        if 'StudyInstanceUID' in replace and not force:
            raise errors.ModificationError('If StudyInstanceUID 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_study = self.client.post_studies_id_modify(self.id_, data)
        except ReadTimeout:
            raise ReadTimeout(
                'Study modification is too long to process. '
                'Use `.modify_as_job` or increase client.timeout.'
            )

        # if 'StudyInstanceUID' is not affected, the modified_study['ID'] is the same as self.id_
        return Study(modified_study['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 study and return a job

        Launch a modification job. If keep=['StudyInstanceUID'] (with `force=True`),
        then modified this study. If the StudyInstanceUID is not keeped, this creates
        a new modified study.
        Documentation: https://book.orthanc-server.com/users/anonymization.html

        Notes
        -----
        This method is useful when modifying large study 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 modification job.

        Examples
        --------
        For large study (recommended)
        ```python
        job = study.modify_as_job(replace={'StudyDescription': 'a description'})
        job.state  # You can follow the job state

        job.wait_until_completion() # Or just wait on its completion
        modified_study = Study(job.content['ID'], client)
        assert modified_study.description == 'a description'
        ```
        Or modify the StudyInstanceUID
        ```python
        job = study.modify_as_job(
            replace={'StudyInstanceUID': '1.2.840.113745.101000.1008000.38048.4626.5933732'},
            force=True
        )
        job.wait_until_completion() # Or just wait on its completion

        modified_study = Study(job.content['ID'], client)
        modified_study.uid == '1.2.840.113745.101000.1008000.38048.4626.5933732'
        ```
        Or keep the StudyInstanceUID
        ```python
        job = study.modify_as_job(
            replace={'StudyDescription': 'a description'},
            keep=['StudyInstanceUID'],
            force=True
        )
        job.wait_until_completion()

        assert study.description == 'a description'
        ```
        """
        remove = [] if remove is None else remove
        replace = {} if replace is None else replace
        keep = [] if keep is None else keep

        if 'StudyInstanceUID' in replace and not force:
            raise errors.ModificationError('If StudyInstanceUID 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_studies_id_modify(self.id_, data)

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

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

        Get the .zip file.

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

        Examples
        --------
        ```python
        from pyorthanc import Orthanc, Study
        a_study = Study('STUDY_IDENTIFIER', Orthanc('http://localhost:8042'))

        bytes_content = a_study.get_zip()
        with open('study_zip_file_path.zip', 'wb') as file_handler:
            file_handler.write(bytes_content)
        ```
        """
        return self.client.get_studies_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, Study
        a_study = Study('STUDY_IDENTIFIER', Orthanc('http://localhost:8042'))

        # Download a zip
        a_study.download('study.zip')

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

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

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

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

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

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

        for series in self._child_resources:
            series.remove_empty_instances()

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

accession_number: str property

date: datetime property

Get study date

The date have precision to the second (if available).

Returns:

Type Description
datetime

Study date

description: str property

institution_name: str property

is_stable: bool property

labels: List[str] property

last_update: datetime property

parent_patient: Patient property

patient_identifier: str property

Get the Orthanc identifier of the parent patient

patient_information: Dict property

Get patient information

referring_physician_name: str property

Get referring physician name

requested_procedure_description: str property

requesting_physician: str property

Get referring physician name

series: List[Series] property

Get Study series

shared_tags: Dict property

study_id: str property

Get Study ID

uid: str property

Get StudyInstanceUID

add_label(label)

Source code in pyorthanc/_resources/study.py
141
142
def add_label(self, label: str) -> None:
    self.client.put_studies_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 study

If no error has been raise, return an anonymous study. Documentation: https://book.orthanc-server.com/users/anonymization.html

Notes

This method might be long to run, especially on large study or when multiple studies 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. StudyID) 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
Study

A New anonymous study.

Examples:

```python new_study = study.anonymize()

new_study_with_specific_study_id = study.anonymize( replace={'StudyDescription': 'A description'} )

Source code in pyorthanc/_resources/study.py
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
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) -> 'Study':
    """Anonymize study

    If no error has been raise, return an anonymous study.
    Documentation: https://book.orthanc-server.com/users/anonymization.html

    Notes
    -----
    This method might be long to run, especially on large study or when multiple
    studies 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. StudyID) 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
    -------
    Study
        A New anonymous study.

    Examples
    --------
    ```python
    new_study = study.anonymize()

    new_study_with_specific_study_id = study.anonymize(
        replace={'StudyDescription': 'A description'}
    )
    """
    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_study = self.client.post_studies_id_anonymize(self.id_, data)
    except ReadTimeout:
        raise ReadTimeout(
            'Study anonymization is too long to process. '
            'Use `.anonymize_as_job` or increase client.timeout.'
        )

    return Study(anonymous_study['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 study 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 study 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. StudyInstanceUID) 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 study (recommended)

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

job.wait_until_completion() # Or just wait on its completion
new_study = Study(job.content['ID'], orthanc)
Source code in pyorthanc/_resources/study.py
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
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 study 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 study 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. StudyInstanceUID) 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 study (recommended)
    ```python
    job = study.anonymize_as_job()
    job.state  # You can follow the job state

    job.wait_until_completion() # Or just wait on its completion
    new_study = Study(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_studies_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, Study
a_study = Study('STUDY_IDENTIFIER', Orthanc('http://localhost:8042'))

# Download a zip
a_study.download('study.zip')

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

# Or download in a buffer in memory
buffer = io.BytesIO()
a_study.download(buffer)
# Now do whatever you want to do
buffer.seek(0)
zip_bytes = buffer.read()
Source code in pyorthanc/_resources/study.py
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
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, Study
    a_study = Study('STUDY_IDENTIFIER', Orthanc('http://localhost:8042'))

    # Download a zip
    a_study.download('study.zip')

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

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

get_main_information()

Get Study information

Returns:

Type Description
Dict

Dictionary of study information

Source code in pyorthanc/_resources/study.py
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
def get_main_information(self) -> Dict:
    """Get Study information

    Returns
    -------
    Dict
        Dictionary of study information
    """
    if self.lock:
        if self._information is None:
            # Setup self._information for the first time when study is lock
            self._information = self.client.get_studies_id(self.id_)

        return self._information

    return self.client.get_studies_id(self.id_)

get_shared_tags(simplify=False, short=False)

Retrieve the shared tags of the study

Source code in pyorthanc/_resources/study.py
558
559
560
561
562
563
564
565
def get_shared_tags(self, simplify: bool = False, short: bool = False) -> Dict:
    """Retrieve the shared tags of the study"""
    params = self._make_response_format_params(simplify, short)

    return dict(self.client.get_studies_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 study.

Examples:

from pyorthanc import Orthanc, Study
a_study = Study('STUDY_IDENTIFIER', Orthanc('http://localhost:8042'))

bytes_content = a_study.get_zip()
with open('study_zip_file_path.zip', 'wb') as file_handler:
    file_handler.write(bytes_content)
Source code in pyorthanc/_resources/study.py
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
def get_zip(self) -> bytes:
    """Get the bytes of the zip file

    Get the .zip file.

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

    Examples
    --------
    ```python
    from pyorthanc import Orthanc, Study
    a_study = Study('STUDY_IDENTIFIER', Orthanc('http://localhost:8042'))

    bytes_content = a_study.get_zip()
    with open('study_zip_file_path.zip', 'wb') as file_handler:
        file_handler.write(bytes_content)
    ```
    """
    return self.client.get_studies_id_archive(self.id_)

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

Modify study

If no error has been raise, then create a modified version of the study. If keep=['StudyInstanceUID'] and force=True are use, then the study itself is changed. Documentation: https://book.orthanc-server.com/users/anonymization.html

Notes

This method might be long to run, especially on large study or when multiple studies 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. StudyInstanceUID) 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
Study

Returns a new modified study or returns itself if keep=['StudyInstanceUID'] (in this case, the study itself is modified).

Examples:

# Create a modified study
modified_study = study.modify(replace={'StudyInstanceUID': '1.2.840.113745.101000.1008000.38048.4626.5933732'}, force=True)
assert modified_study.uid == '1.2.840.113745.101000.1008000.38048.4626.5933732'

# Modify itself
study.modify(replace={'ReferringPhysicianName': 'last^first'}, keep=['StudyInstanceUID'], force=True)
assert study.referring_physician_name == 'last^first'
Source code in pyorthanc/_resources/study.py
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
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) -> 'Study':
    """Modify study

    If no error has been raise, then create a modified version of the study.
    If keep=['StudyInstanceUID'] and force=True are use, then the study itself is changed.
    Documentation: https://book.orthanc-server.com/users/anonymization.html

    Notes
    -----
    This method might be long to run, especially on large study or when multiple
    studies 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. StudyInstanceUID) 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
    -------
    Study
        Returns a new modified study or returns itself if keep=['StudyInstanceUID']
        (in this case, the study itself is modified).

    Examples
    --------
    ```python
    # Create a modified study
    modified_study = study.modify(replace={'StudyInstanceUID': '1.2.840.113745.101000.1008000.38048.4626.5933732'}, force=True)
    assert modified_study.uid == '1.2.840.113745.101000.1008000.38048.4626.5933732'

    # Modify itself
    study.modify(replace={'ReferringPhysicianName': 'last^first'}, keep=['StudyInstanceUID'], force=True)
    assert study.referring_physician_name == 'last^first'
    ```
    """
    remove = [] if remove is None else remove
    replace = {} if replace is None else replace
    keep = [] if keep is None else keep

    if 'StudyInstanceUID' in replace and not force:
        raise errors.ModificationError('If StudyInstanceUID 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_study = self.client.post_studies_id_modify(self.id_, data)
    except ReadTimeout:
        raise ReadTimeout(
            'Study modification is too long to process. '
            'Use `.modify_as_job` or increase client.timeout.'
        )

    # if 'StudyInstanceUID' is not affected, the modified_study['ID'] is the same as self.id_
    return Study(modified_study['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 study and return a job

Launch a modification job. If keep=['StudyInstanceUID'] (with force=True), then modified this study. If the StudyInstanceUID is not keeped, this creates a new modified study. Documentation: https://book.orthanc-server.com/users/anonymization.html

Notes

This method is useful when modifying large study 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 modification job.

Examples:

For large study (recommended)

job = study.modify_as_job(replace={'StudyDescription': 'a description'})
job.state  # You can follow the job state

job.wait_until_completion() # Or just wait on its completion
modified_study = Study(job.content['ID'], client)
assert modified_study.description == 'a description'

Or modify the StudyInstanceUID

job = study.modify_as_job(
    replace={'StudyInstanceUID': '1.2.840.113745.101000.1008000.38048.4626.5933732'},
    force=True
)
job.wait_until_completion() # Or just wait on its completion

modified_study = Study(job.content['ID'], client)
modified_study.uid == '1.2.840.113745.101000.1008000.38048.4626.5933732'

Or keep the StudyInstanceUID

job = study.modify_as_job(
    replace={'StudyDescription': 'a description'},
    keep=['StudyInstanceUID'],
    force=True
)
job.wait_until_completion()

assert study.description == 'a description'
Source code in pyorthanc/_resources/study.py
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
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 study and return a job

    Launch a modification job. If keep=['StudyInstanceUID'] (with `force=True`),
    then modified this study. If the StudyInstanceUID is not keeped, this creates
    a new modified study.
    Documentation: https://book.orthanc-server.com/users/anonymization.html

    Notes
    -----
    This method is useful when modifying large study 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 modification job.

    Examples
    --------
    For large study (recommended)
    ```python
    job = study.modify_as_job(replace={'StudyDescription': 'a description'})
    job.state  # You can follow the job state

    job.wait_until_completion() # Or just wait on its completion
    modified_study = Study(job.content['ID'], client)
    assert modified_study.description == 'a description'
    ```
    Or modify the StudyInstanceUID
    ```python
    job = study.modify_as_job(
        replace={'StudyInstanceUID': '1.2.840.113745.101000.1008000.38048.4626.5933732'},
        force=True
    )
    job.wait_until_completion() # Or just wait on its completion

    modified_study = Study(job.content['ID'], client)
    modified_study.uid == '1.2.840.113745.101000.1008000.38048.4626.5933732'
    ```
    Or keep the StudyInstanceUID
    ```python
    job = study.modify_as_job(
        replace={'StudyDescription': 'a description'},
        keep=['StudyInstanceUID'],
        force=True
    )
    job.wait_until_completion()

    assert study.description == 'a description'
    ```
    """
    remove = [] if remove is None else remove
    replace = {} if replace is None else replace
    keep = [] if keep is None else keep

    if 'StudyInstanceUID' in replace and not force:
        raise errors.ModificationError('If StudyInstanceUID 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_studies_id_modify(self.id_, data)

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

remove_empty_series()

Delete empty series.

Source code in pyorthanc/_resources/study.py
571
572
573
574
575
576
577
578
579
def remove_empty_series(self) -> None:
    """Delete empty series."""
    if self._child_resources is None:
        return

    for series in self._child_resources:
        series.remove_empty_instances()

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

remove_label(label)

Source code in pyorthanc/_resources/study.py
144
145
def remove_label(self, label):
    self.client.delete_studies_id_labels_label(self.id_, label)