Skip to content

Commit

Permalink
Warn when neither --in-place nor --output-directory are specified
Browse files Browse the repository at this point in the history
  • Loading branch information
blairconrad committed Aug 26, 2022
1 parent 78ff3e4 commit d0db6e2
Show file tree
Hide file tree
Showing 5 changed files with 46 additions and 16 deletions.
27 changes: 19 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@ Dicognito is a [Python](https://www.python.org/) module and command-line utility
Use it to anonymize one or more DICOM files belonging to one or any number of patients. Objects will remain grouped
in their original patients, studies, and series.

Anonymization causes significant attributes, such as identifiers, names, and
addresses, to be replaced by new values. Dates and times will be shifted into the
past, but their order will remain consistent within and across the files.

The package is [available on pypi](https://pypi.org/project/dicognito/) and can be installed from the command line by typing

```
Expand All @@ -17,15 +21,22 @@ pip install dicognito
Once installed, a `dicognito` command will be added to your Python scripts directory.
You can run it on entire filesystem trees or a collection of files specified by glob like so:

```bash
# Recurse down the filesystem, anonymizing all found DICOM files.
# Anonymized files will be placed in out-dir, named by new SOP
# instance UID.
dicognito --output-directory out-dir .

# Anonymize all files in the current directory with the dcm extension
# (-o is an alias for --output-directory).
dicognito -o out-dir *.dcm

# Anonymize all files in the current directory with the dcm extension
# but overwrite the original files.
# Note: repeatedly anonymizing the same files will cause date attributes
# to move farther into the past.
dicognito --in-place *.dcm
```
dicognito . # recurses down the filesystem, anonymizing all found DICOM files
dicognito *.dcm # anonymizes all files in the current directory with the dcm extension
```

Files will be anonymized in place, with significant attributes, such as identifiers, names, and
addresses, replaced by random values. Dates and times will be shifted a random amount, but their
order will remain consistent within and across the files.

Get more help via `dicognito --help`.

## Anonymizing from within Python
Expand Down
5 changes: 5 additions & 0 deletions src/dicognito/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,11 @@ def main(main_args: Optional[Sequence[str]] = None) -> None:
raise ValueError("Invalid log level: %s" % args.log_level)
logging.basicConfig(format="", level=numeric_level)

if not args.in_place and not args.output_directory:
logging.warning(
"Neither --output-directory/-o nor --in-place were specified. This will be an error in the future."
)

anonymizer = Anonymizer(id_prefix=args.id_prefix, id_suffix=args.id_suffix, seed=args.seed)

pipeline = Pipeline()
Expand Down
6 changes: 6 additions & 0 deletions src/dicognito/release_notes.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
### Changed

- If neither `--in-place` or `--output-directory`/`-o` are specified on the command line, a
warning will be printed. The anonymization will proceed as if `--in-place` were specified.
A future release will require that one of the options be specified.

### New

- `--in-place` flag added to explicitly specify in place editing of source files. It's an error
Expand Down
24 changes: 16 additions & 8 deletions tests/test_commandline.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,19 @@ def setup_module(module):
shutil.copytree(orig_dir, data_dir)


def test_overwrite_files():
def test_implicit_in_place_warns_but_anonymizes(caplog):
test_name = get_test_name()
orig_dataset = read_original_file(test_name, "p01_s01_s01_i01.dcm")
assert "CompressedSamples^MR1" == orig_dataset.PatientName

run_dicognito(path_to(""))

log_record = [log for log in caplog.records if log.levelname == "WARNING"][0]
assert (
"Neither --output-directory/-o nor --in-place were specified. This will be an error in the future."
in log_record.getMessage()
)

run_dicognito(path_to("p*"))

anon_dataset = read_file(test_name, "p01_s01_s01_i01.dcm")
Expand Down Expand Up @@ -128,7 +136,7 @@ def test_non_dicom_files_logged_at_info(caplog):
assert "CompressedSamples^MR1" == orig_dataset.PatientName

set_log_level(caplog, logging.INFO)
run_dicognito(path_to(""))
run_dicognito("--in-place", path_to(""))
anon_dataset = read_file(test_name, "p01_s01_s01_i01.dcm")
assert orig_dataset.PatientName != anon_dataset.PatientName

Expand All @@ -141,7 +149,7 @@ def test_burned_in_annotation_default(caplog):
expected_warnings = {"Burned In Annotation is YES in " + path_to("burned_in_yes.dcm")}

set_log_level(caplog, logging.WARNING)
run_dicognito(path_to(""))
run_dicognito("--in-place", path_to(""))

messages = {log.getMessage() for log in caplog.records if log.levelname == "WARNING"}
assert messages == expected_warnings
Expand All @@ -155,7 +163,7 @@ def test_burned_in_annotation_unless_no(caplog):
}

set_log_level(caplog, logging.WARNING)
run_dicognito(path_to(""), "--assume-burned-in-annotation", "unless-no")
run_dicognito("--in-place", path_to(""), "--assume-burned-in-annotation", "unless-no")

messages = {log.getMessage() for log in caplog.records if log.levelname == "WARNING"}
assert messages == expected_warnings
Expand All @@ -165,15 +173,15 @@ def test_burned_in_annotation_if_yes(caplog):
expected_warnings = {"Burned In Annotation is YES in " + path_to("burned_in_yes.dcm")}

set_log_level(caplog, logging.WARNING)
run_dicognito(path_to(""), "--assume-burned-in-annotation", "if-yes")
run_dicognito("--in-place", path_to(""), "--assume-burned-in-annotation", "if-yes")

messages = {log.getMessage() for log in caplog.records if log.levelname == "WARNING"}
assert messages == expected_warnings


def test_burned_in_annotation_never(caplog):
set_log_level(caplog, logging.WARNING)
run_dicognito(path_to(""), "--assume-burned-in-annotation", "never")
run_dicognito("--in-place", path_to(""), "--assume-burned-in-annotation", "never")

messages = {log.getMessage() for log in caplog.records if log.levelname == "WARNING"}
assert not messages
Expand All @@ -183,7 +191,7 @@ def test_burned_in_annotation_warn(caplog):
expected_warnings = {"Burned In Annotation is YES in " + path_to("burned_in_yes.dcm")}

set_log_level(caplog, logging.WARNING)
run_dicognito(path_to(""), "--on-burned-in-annotation", "warn")
run_dicognito("--in-place", path_to(""), "--on-burned-in-annotation", "warn")

messages = {log.getMessage() for log in caplog.records if log.levelname == "WARNING"}
assert messages == expected_warnings
Expand All @@ -194,7 +202,7 @@ def test_burned_in_annotation_fail(caplog):
expected_message = "Burned In Annotation is YES in " + input_file_name

with pytest.raises(SystemExit):
run_dicognito(path_to(""), "--on-burned-in-annotation", "fail")
run_dicognito("--in-place", path_to(""), "--on-burned-in-annotation", "fail")

log_record = [log for log in caplog.records if log.levelname == "ERROR"][0]
assert f"Error occurred while converting {input_file_name}. Aborting.\nError was:" in log_record.getMessage()
Expand Down

0 comments on commit d0db6e2

Please sign in to comment.