Skip to content

Commit

Permalink
Merge branch 'release/0.4.0'
Browse files Browse the repository at this point in the history
* release/0.4.0: (55 commits)
  Update Makefile and Pipfile to streamline dependencies and improve documentation
  Bump version: 0.4.0-dev -> 0.4.0
  Bump version: 0.3.13-dev -> 0.4.0-dev
  Update release checklist to correct git flow command for starting a release
  Fix InstanceNumber check to handle empty string and ensure proper type conversion for scheduled protocol index
  streamline DICOM attribute setting and improve type keyword handling
  Add method to retrieve scheduled protocol code in OrthodonticPhotograph class and enhance unit tests
  Refactor logging configuration and clean up whitespace in test_photography.py
  Add properties for series number and instance number in DicomBase
  Update lock file.
  Fix test cases to use 'test.dcm' instead of 'd90.dcm'
  Moved fhir2dicom4ortho to its own repository.
  Remove unused JPEG UID imports in model.py for cleaner code
  Fix condition in set_image to require either input_image_filename or input_image_bytes
  Add assertion for successful response status in e2e tests
  Fix username parameter to pacs_wado_username
  Rename process_bundle to build_and_send_dicom_image for clarity in DICOM image processing
  Refactor task processing to use build_and_send_dicom_image and add utility for extracting Scheduled Protocol from Basic resource
  Remove extension from ImagingStudy and add subject reference to Basic.
  Enhance DICOM handling and task status management in FHIR API integration
  ...
  • Loading branch information
zgypa committed Jan 21, 2025
2 parents 2d85c1d + 3bc29ec commit f3a63fe
Show file tree
Hide file tree
Showing 26 changed files with 1,753 additions and 1,092 deletions.
4 changes: 2 additions & 2 deletions .bumpversion.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
commit = True
tag = True
tag_name = v{new_version}
current_version = 0.3.12
current_version = 0.4.0
parse = (?P<major>\d+)\.(?P<minor>\d+)\.(?P<patch>\d+)(\-(?P<release>[a-z]+))?
message = Bump version: {current_version} -> {new_version}
serialize =
Expand All @@ -11,7 +11,7 @@ serialize =

[bumpversion:file:dicom4ortho/config.py]

[bumpversion:file:setup.py]
[bumpversion:file:pyproject.toml]

[bumpversion:file:README.md]

Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,4 @@ build
modules/dicom3tools
docs/.buildinfo
.test*
*.sqlite
1 change: 1 addition & 0 deletions .python-version
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
3.11
3 changes: 1 addition & 2 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,6 @@ default: clean build

.PHONY: lint
lint:
$(LINTER) setup.py
$(LINTER) $(MAIN)

.PHONY: test
Expand All @@ -53,7 +52,7 @@ $(DIST):

.PHONY: build
build: lint test $(DIST) update_resources
python3 -m setup sdist
python3 -m build

.PHONY: update_resources
update_resources:
Expand Down
11 changes: 9 additions & 2 deletions Pipfile
Original file line number Diff line number Diff line change
Expand Up @@ -10,17 +10,24 @@ twine = "*"
autopep8 = "*"
bumpversion = "*"
pytest = "*"
build = "*"
prettytable = "*"

[packages]
pydicom = "*"
# There is a vulnerability flaw in <8.2.0 of Pillow.
pillow = ">=9.2.0"
pynetdicom = "*"
dicom-photo = {path = ".", editable = true}
# There is a vulnerability flaw in <1.26.5 of urllib3.
urllib3 = ">=1.26.5"
sphinx = "*"
numpy = "*"
fastapi = "*"
"fhir.resources" = "*"
pydantic = "*"
uvicorn = "*"
apscheduler = "*"


[requires]
python_version = "3"
python_version = ">3.10"
1,559 changes: 977 additions & 582 deletions Pipfile.lock

Large diffs are not rendered by default.

6 changes: 5 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
<img src="https://raw.githubusercontent.com/open-ortho/dicom4ortho/master/images/open-ortho.png" alt="Logo" width="80" height="80">
</a>

<h3 align="center">dicom4ortho 0.3.12</h3>
<h3 align="center">dicom4ortho 0.4.0</h3>

<p align="center">
A Python library to create fully defined orthodontic photographs in DICOM.
Expand Down Expand Up @@ -137,6 +137,10 @@ then use dicom4ortho like this:
Where `filename` should be a `.csv` file. Passing a single image file with
metadata through arguments is planned for future implementations.

generate a new UID for DICOM usage with this root:

$ d4o_generate

## Known Issues

Please check the [Implementation Status](docs/IMPLEMENTATION_STATUS.md)
Expand Down
3 changes: 0 additions & 3 deletions dicom4ortho/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,3 @@
import dicom4ortho.config

__version__ = dicom4ortho.config.VERSION
__url__ = 'https://github.com/open-ortho/dicom4ortho'
__author__ = 'Toni Magni'
__email__ = '[email protected]'
29 changes: 5 additions & 24 deletions dicom4ortho/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@

import dicom4ortho.config as config
import dicom4ortho.controller as controller
from dicom4ortho.utils import generate_dicom_uid

LIST_IMAGE_TYPES = 'list-image-types'

Expand Down Expand Up @@ -125,22 +126,6 @@ def main(argv=None):
default='EV01',
metavar='<image_type>',
)
parser.add_argument(
"--teeth",
dest="teeth",
nargs="*",
help="Add this tooth to image. Tooth should be clearly visible. \
Use ISO tooth numbering. Add as many as necessary, divided by a \
space, like: '--teeth 18 17 16'.",
)
parser.add_argument(
"--add-max-allowed-teeth",
dest="add_max_allowed_teeth",
action="store_true",
help="Adds the maximum allowed teeth for each image type. Assumes \
adult patient with all teeth present and clearly visible for the \
specified image type.",
)
parser.add_argument(
"--validate",
dest="validate",
Expand Down Expand Up @@ -177,24 +162,17 @@ def main(argv=None):
c = controller.OrthodonticController(
url_codes=args.url_codes,
url_views=args.url_views)
if args.add_max_allowed_teeth:
teeth = config.ADD_MAX_ALLOWED_TEETH
elif args.teeth:
teeth = args.teeth
else:
teeth = []

if args.validate is True:
c.validate_dicom_file(args.input_filename)
return 0
elif args.input_filename.lower().endswith('.csv'):
c.bulk_convert_from_csv(args.input_filename, teeth=teeth)
c.bulk_convert_from_csv(args.input_filename)
return 0
else:
c.convert_image_to_dicom4orthograph_and_save({
'image_type': args.image_type,
'input_image_filename': args.input_filename,
'teeth': teeth,
'output_image_filename': args.output_filename})
c.photo.print()
return 0
Expand All @@ -204,5 +182,8 @@ def main(argv=None):
return 120


def generate_uid():
print(generate_dicom_uid())

if __name__ == "__main__":
sys.exit(main(sys.argv[1:]))
4 changes: 1 addition & 3 deletions dicom4ortho/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
from pathlib import Path
import importlib.resources as importlib_resources

VERSION = '0.3.12'
VERSION = '0.4.0'
PROJECT_NAME = 'dicom4ortho'
__url__ = 'https://github.com/open-ortho/dicom4ortho'
__author__ = 'Toni Magni'
Expand Down Expand Up @@ -54,8 +54,6 @@
# The default IDs used for SeriesNumber StudyID and InstanceNumber
IDS_NUMBERS = '000'

ADD_MAX_ALLOWED_TEETH = 'ALL'

# This is populated by controller.OrthodonticController._load_image_types()
image_types = {}

79 changes: 46 additions & 33 deletions dicom4ortho/controller.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import os
import csv
from pathlib import Path
from pydicom.dataset import Dataset

from dicom4ortho.config import DICOM3TOOLS_PATH
from dicom4ortho.model import DicomBase
Expand All @@ -14,7 +15,6 @@
import logging
logger = logging.getLogger(__name__)


class OrthodonticController(object):
""" Controller
Expand All @@ -37,13 +37,12 @@ def __init__(self, **kwargs):
url_codes=kwargs.get('url_codes'),
url_views=kwargs.get('url_views'))

def bulk_convert_from_csv(self, csv_input, teeth=None):
def bulk_convert_from_csv(self, csv_input):
with open(csv_input, mode='r') as csv_file:
csv_reader = csv.DictReader(csv_file, delimiter=',')
for row in csv_reader:
row['input_image_filename'] = (
Path(csv_input).parent / row['input_image_filename'])
row['teeth'] = teeth
self.convert_image_to_dicom4orthograph_and_save(metadata=row)

def convert_image_to_dicom4orthograph(self, metadata) -> OrthodonticPhotograph:
Expand All @@ -67,9 +66,6 @@ def convert_image_to_dicom4orthograph(self, metadata) -> OrthodonticPhotograph:
- "Posttreatment"
days_after_event : number of days from treatment_event_type
burned_in_annotation : 'YES' or 'NO'. Default = 'NO'.
teeth : array of teeth visible in the photograph.
Use ISO notation in string. Example:
teeth=['24','25','26','27','28','34','35','36','37','38']
output_image_filename : filename to write dicom image into.
Default is the same name as the input file name with replaced
extension.
Expand All @@ -81,17 +77,27 @@ def convert_image_to_dicom4orthograph(self, metadata) -> OrthodonticPhotograph:

self.photo = OrthodonticPhotograph(**metadata)

# TODO: check if metadata['teeth'] contains teeth and add
# What teeth are shown in the images is something we cannot guess from
# what image type is taken, and shold be entered manually or
# automtaicaly by the implementing software. Therefore, i would like
# the controller to have an option to add teeth and provide this option
# to the end user which, in this case, is the CLI, and the CSV import
# file.
# if metadata['teeth']

return self.photo

def convert_image_plus_mwl_to_dicom4orthograph(self, image_bytes, mwl:Dataset) -> OrthodonticPhotograph:
''' Converts a PIL image into an OrthodonticPhotograph using a DICOM MWL for metadata.
The MWL is passed as a pydicom Dataset object, and should contain a single ScheduledProtocolCode.
Parameters:
image_bytes (bytes): Image bytes. Purposely set to raw bytes to avoid file I/O. Purposely avoiding PIL Image, because once in PIL Image, the image will be decoded and re-encoded even when saved as JPEG. This would result in loss of image quality.
mwl (Dataset): DICOM MWL object.
'''
metadata = {
'input_image_filename': None,
'input_image_bytes': image_bytes,
'dicom_mwl': mwl,
}
self.photo = OrthodonticPhotograph(**metadata)
pass

def convert_image_to_dicom4orthograph_and_save(self, metadata):
_photo = self.convert_image_to_dicom4orthograph(metadata=metadata)
_photo.save()
Expand Down Expand Up @@ -141,14 +147,14 @@ def send(self, send_method, **kwargs):
**kwargs: Additional keyword arguments depending on the send method:
For send_method 'dimse':
pacs_ip (str): IP address of the PACS server.
pacs_port (int): Port of the PACS server.
pacs_aet (str): AE Title of the PACS server.
pacs_dimse_hostname (str): IP address of the PACS server.
pacs_dimse_port (int): Port of the PACS server.
pacs_dimse_aet (str): AE Title of the PACS server.
For send_method 'wado':
dicomweb_url (str): URL of the DICOMweb server.
username (str, optional): Username for DICOMweb authentication.
password (str, optional): Password for DICOMweb authentication.
pacs_wado_url (str): URL of the DICOMweb server.
pacs_wado_username (str, optional): Username for DICOMweb authentication.
pacs_wado_password (str, optional): Password for DICOMweb authentication.
Raises:
ValueError: If an invalid send method is specified or required kwargs are missing.
Expand All @@ -157,40 +163,47 @@ def send(self, send_method, **kwargs):
send(
dicom_files=['path/to/output.dcm'],
send_method='dimse',
pacs_ip='127.0.0.1',
pacs_port=104,
pacs_aet='PACS_AET'
pacs_dimse_hostname='127.0.0.1',
pacs_dimse_port=104,
pacs_dimse_aet='PACS_AET'
)
send(
orthodontic_series=orthodontic_series,
send_method='wado',
dicomweb_url='http://dicomweb-server.com/dicomweb/studies',
username='user',
password='pass'
pacs_wado_url='http://dicomweb-server.com/dicomweb/studies',
pacs_wado_username='user',
pacs_wado_password='pass'
)
Returns either a DICOM Dateset or a response containing the response.
"""

# Convert image to DICOM (assuming you have a function for this)

# Send the DICOM file based on the specified method
if send_method == 'dimse':
return dimse.send(
dicom_datasets=kwargs.get('dicom_datasets', None),
dicom_files=kwargs.get('dicom_files', None),
orthodontic_series=kwargs.get('orthodontic_series', None),
pacs_ip=kwargs['pacs_ip'],
pacs_port=kwargs['pacs_port'],
pacs_aet=kwargs['pacs_aet'])
pacs_dimse_hostname=kwargs['pacs_dimse_hostname'],
pacs_dimse_port=kwargs['pacs_dimse_port'],
pacs_dimse_aet=kwargs['pacs_dimse_aet'])

elif send_method == 'wado':
return wado.send(
dicom_files=kwargs.get('dicom_files', None),
orthodontic_series=kwargs.get('orthodontic_series', None),
dicomweb_url=kwargs['dicomweb_url'],
username=kwargs.get('username'),
password=kwargs.get('password'),
pacs_wado_url=kwargs['pacs_wado_url'],
pacs_wado_username=kwargs.get('pacs_wado_username'),
pacs_wado_password=kwargs.get('pacs_wado_password'),
ssl_certificate=kwargs.get('ssl_certificate'),
ssl_verify=kwargs.get('ssl_verify'),
)
else:
logger.error('Invalid send method specified.')


def get_image_view_code(self, ds:Dataset, code_system):
pass
3 changes: 1 addition & 2 deletions dicom4ortho/defaults.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,4 @@
logger.warning("DEPRECATION WARNING: dicom4ortho.defaults is deprecated. Make use of dicom4orto.config and dicom4ortho.utils instead.")

from dicom4ortho.utils import generate_dicom_uid
from dicom4ortho.config import *
from dicom4ortho.config import __author__, __creation_date__, __email__, __short_description__, __updated__, __url__
from dicom4ortho.config import *
Loading

0 comments on commit f3a63fe

Please sign in to comment.