Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Setup pre commit with ruff #84

Merged
merged 4 commits into from
Jun 19, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 8 additions & 13 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ on:
merge_group:

jobs:
launch-unit-tests:
lint-and-test:
runs-on: ubuntu-latest

strategy:
Expand All @@ -23,21 +23,16 @@ jobs:
uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}

- name: Cache Python dependencies
uses: actions/cache@v4
id: pip-cache
with:
path: ~/.cache/pip
key: pip-${{ matrix.python-version }}-${{ hashFiles('**/setup.py') }}
restore-keys: |
pip-${{ matrix.python-version }}-${{ hashFiles('**/setup.py') }}
cache: 'pip'
cache-dependency-path: setup.py
Copy link
Contributor Author

@ue-sho ue-sho May 22, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.


- name: Install python dependencies
if: steps.pip-cache.outputs.cache-hit != 'true'
run: pip install -e .[dev]

- name: Check code style
run: |
python -m pip install --upgrade pip
pip install -e .[dev]
ruff check
ruff format --check

- name: Run test
run: pytest
7 changes: 7 additions & 0 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
repos:
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.4.4
hooks:
- id: ruff
args: [ --fix ]
- id: ruff-format
46 changes: 38 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,14 +25,44 @@ Dicom fields are separated into different groups. Each groups will be anonymized

Installation can be done via pip `pip install dicom-anonymizer` or conda `conda install -c conda-forge dicom-anonymizer`.

# How to test it?
- One time set up:
- virtual environment for this package and activate it. For
example set up using `virtualenv venv` and activate using
`venv\Scripts\activate.bat` (on Windows)
- Install editable version and development requirements using
`pip install -e .[dev]`
- Run unit test using `pytest`

# Local Development Setup

To get started with local development, follow these steps:

1. Create a Virtual Environment:
- On Windows:
```sh
virtualenv env
.\env\Scripts\activate.bat
```
- On MacOS/Linux:
```sh
python -m venv env
source env/bin/activate
```

2. Install Dependencies:
- Install an editable version of the package and the development requirements:
```sh
pip install -e .[dev]
```

3. Set Up Pre-Commit Hooks:
- Install the pre-commit hooks to ensure code quality:
```sh
pre-commit install
```


## How to test it?

To run the unit tests, use the following command:

```sh
pytest
```


# How to build it?
These instructions rely on wheel build-package format. Install it if you have not done it already using:
Expand Down
107 changes: 74 additions & 33 deletions dicomanonymizer/anonymizer.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import sys

if sys.version_info >= (3, 8):
import importlib.metadata as metadata
else:
Expand All @@ -8,25 +9,32 @@
import ast
import json
import os
import sys
import tqdm

from dicomanonymizer.simpledicomanonymizer import anonymize_dicom_file, ActionsMapNameFunctions
from dicomanonymizer.simpledicomanonymizer import (
anonymize_dicom_file,
ActionsMapNameFunctions,
)


def isDICOMType(filePath):
"""
:returns True if input file is a DICOM File. False otherwise.
"""
try:
with open(filePath, 'rb') as tempFile:
with open(filePath, "rb") as tempFile:
tempFile.seek(0x80, os.SEEK_SET)
return tempFile.read(4) == b'DICM'
return tempFile.read(4) == b"DICM"
except IOError:
return False


def anonymize(input_path: str, output_path: str, anonymization_actions: dict, delete_private_tags: bool) -> None:
def anonymize(
input_path: str,
output_path: str,
anonymization_actions: dict,
delete_private_tags: bool,
) -> None:
"""
Read data from input path (folder or file) and launch the anonymization.

Expand All @@ -37,37 +45,42 @@ def anonymize(input_path: str, output_path: str, anonymization_actions: dict, de
:param deletePrivateTags: Whether to delete private tags.
"""
# Get input arguments
input_folder = ''
output_folder = ''
input_folder = ""
output_folder = ""

if os.path.isdir(input_path):
input_folder = input_path

if os.path.isdir(output_path):
output_folder = output_path
if input_folder == '':
if input_folder == "":
output_path = os.path.join(output_folder, os.path.basename(input_path))

if input_folder != '' and output_folder == '':
print('Error, please set a correct output folder path')
if input_folder != "" and output_folder == "":
print("Error, please set a correct output folder path")
sys.exit()

# Generate list of input file if a folder has been set
input_files_list = []
output_files_list = []
if input_folder == '':
if input_folder == "":
input_files_list.append(input_path)
output_files_list.append(output_path)
else:
files = os.listdir(input_folder)
for fileName in files:
if isDICOMType(input_folder + '/' + fileName):
input_files_list.append(input_folder + '/' + fileName)
output_files_list.append(output_folder + '/' + fileName)
if isDICOMType(input_folder + "/" + fileName):
input_files_list.append(input_folder + "/" + fileName)
output_files_list.append(output_folder + "/" + fileName)

progress_bar = tqdm.tqdm(total=len(input_files_list))
for cpt in range(len(input_files_list)):
anonymize_dicom_file(input_files_list[cpt], output_files_list[cpt], anonymization_actions, delete_private_tags)
anonymize_dicom_file(
input_files_list[cpt],
output_files_list[cpt],
anonymization_actions,
delete_private_tags,
)
progress_bar.update(1)

progress_bar.close()
Expand All @@ -83,7 +96,9 @@ def parse_tag_actions_arguments(t_arguments: list, new_anonymization_actions: di
for current_tag_parameters in t_arguments:
nb_parameters = len(current_tag_parameters)
if nb_parameters < 2:
raise ValueError("-t parameters should always have 2 values: tag and action")
raise ValueError(
"-t parameters should always have 2 values: tag and action"
)

tag = current_tag_parameters[0]
action_name = current_tag_parameters[1]
Expand All @@ -96,9 +111,15 @@ def parse_tag_actions_arguments(t_arguments: list, new_anonymization_actions: di
action_arguments = current_tag_parameters[2:]

if len(action_arguments) != action_object.number_of_expected_arguments:
raise ValueError(f"Wrong number of arguments for action {action_name}: found {len(action_arguments)}")

action = action_object.function if not len(action_arguments) else action_object.function(action_arguments)
raise ValueError(
f"Wrong number of arguments for action {action_name}: found {len(action_arguments)}"
)

action = (
action_object.function
if not len(action_arguments)
else action_object.function(action_arguments)
)
tag_list = [ast.literal_eval(tag)]

new_anonymization_actions.update({tag: action for tag in tag_list})
Expand All @@ -116,7 +137,7 @@ def parse_dictionary_argument(dictionary_argument, new_anonymization_actions):
for tag, action_or_options in data.items():
if isinstance(action_or_options, dict):
try:
action_name = action_or_options.pop('action')
action_name = action_or_options.pop("action")
except KeyError as e:
raise ValueError(f"Missing field in tag {tag}: {e.args[0]}")
try:
Expand All @@ -125,7 +146,8 @@ def parse_dictionary_argument(dictionary_argument, new_anonymization_actions):
raise ValueError(f"Action {action_name} is not recognized.")
if len(action_or_options) != action_object.number_of_expected_arguments:
raise ValueError(
f"Wrong number of arguments for action {action_name}: found {len(action_or_options)}")
f"Wrong number of arguments for action {action_name}: found {len(action_or_options)}"
)
action = action_object.function(action_or_options)
else:
try:
Expand All @@ -140,18 +162,35 @@ def parse_dictionary_argument(dictionary_argument, new_anonymization_actions):
def main():
version_info = metadata.version("dicom_anonymizer")
parser = argparse.ArgumentParser(add_help=True)
parser.add_argument('input', help='Path to the input dicom file or input directory which contains dicom files')
parser.add_argument(
'output', help='Path to the output dicom file or output directory which will contains dicom files')
"input",
help="Path to the input dicom file or input directory which contains dicom files",
)
parser.add_argument(
"output",
help="Path to the output dicom file or output directory which will contains dicom files",
)
parser.add_argument(
"-t",
action="append",
nargs="*",
help="tags action : Defines a new action to apply on the tag.'regexp' action takes two arguments: "
"1. regexp to find substring 2. the string that will replace the previous found string",
)
parser.add_argument(
"-v", "--version", action="version", version=f"%(prog)s {version_info}"
)
parser.add_argument(
"--dictionary",
action="store",
help="File which contains a dictionary that can be added to the original one",
)
parser.add_argument(
'-t', action='append', nargs='*',
help='tags action : Defines a new action to apply on the tag.\'regexp\' action takes two arguments: '
'1. regexp to find substring 2. the string that will replace the previous found string')
parser.add_argument('-v', '--version', action='version', version=f'%(prog)s {version_info}')
parser.add_argument('--dictionary', action='store',
help='File which contains a dictionary that can be added to the original one')
parser.add_argument('--keepPrivateTags', action='store_true', dest='keepPrivateTags',
help='If used, then private tags won\'t be deleted')
"--keepPrivateTags",
action="store_true",
dest="keepPrivateTags",
help="If used, then private tags won't be deleted",
)
parser.set_defaults(keepPrivateTags=False)
args = parser.parse_args()

Expand All @@ -168,8 +207,10 @@ def main():
parse_dictionary_argument(args.dictionary, new_anonymization_actions)

# Launch the anonymization
anonymize(input_path, output_path, new_anonymization_actions, not args.keepPrivateTags)
anonymize(
input_path, output_path, new_anonymization_actions, not args.keepPrivateTags
)


if __name__ == '__main__':
if __name__ == "__main__":
main()
Loading