Skip to content

Commit

Permalink
Merge pull request #33 from tomaroberts/dunamai-production-fix
Browse files Browse the repository at this point in the history
Fixes issue with pip install related to dunamai implementation
  • Loading branch information
tomaroberts committed Mar 1, 2024
2 parents 09d20e0 + b2386e5 commit 242cb46
Show file tree
Hide file tree
Showing 19 changed files with 428 additions and 20 deletions.
61 changes: 57 additions & 4 deletions .github/workflows/build_and_test_cli.yml
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
# Workflow to build nii2dcm and test different command line interface (CLI) options
# Workflow to build nii2dcm, run unit tests and then execute command line interface (CLI) end-to-end

name: Build nii2dcm
name: Build & Test nii2dcm

on:
pull_request:

jobs:
build-and-test:
name: Build
venv-build-and-test:
name: venv + E2E

runs-on: ${{ matrix.os }}

Expand Down Expand Up @@ -57,6 +57,10 @@ jobs:
nii2dcm -h
nii2dcm -v
- name: Run unit tests
run: |
pytest tests/
- name: Test DicomMRISVR creation
run: |
# run nii2dcm
Expand All @@ -65,3 +69,52 @@ jobs:
ls ./output
# assert DICOM files exist
[ -f "./output/IM_0001.dcm" ] && echo "Output DICOM file exists" || exit 1
- name: Build pytest coverage file
run: |
pytest --junitxml=pytest.xml --cov-report=term-missing:skip-covered --cov=nii2dcm tests/ | tee pytest-coverage.txt ; echo $?
- name: Pytest coverage comment
id: coverageComment
uses: MishaKav/pytest-coverage-comment@main
with:
pytest-coverage-path: ./pytest-coverage.txt
junitxml-path: ./pytest.xml

- name: Update Coverage Badge
uses: schneegans/dynamic-badges-action@v1.7.0
with:
auth: ${{ secrets.PYTEST_COVERAGE_COMMENT }}
gistID: 57ef8057d04f67dbe6e64df410b83079
filename: nii2dcm-pytest-coverage-comment.json
label: Coverage Report
message: ${{ steps.coverageComment.outputs.coverage }}
color: ${{ steps.coverageComment.outputs.color }}
namedLogo: python

container-build-and-test:
name: Container

runs-on: ${{ matrix.os }}

strategy:
fail-fast: false
matrix:
os: [ ubuntu-latest ]
python-version: [ '3.9' ]

steps:
- uses: actions/checkout@v3
with:
fetch-depth: 0

- name: Build container
run: |
docker build -t nii2dcm --progress=plain --no-cache .
docker ps
- name: Test nii2dcm container
run: |
docker run nii2dcm -h
echo "nii2dcm version:"
docker run nii2dcm -v
60 changes: 58 additions & 2 deletions .github/workflows/publish_pypi.yml
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ permissions:
actions: write

jobs:
testpypi-publish:
pypi-publish:

name: Publish to PyPI
runs-on: ubuntu-latest
Expand Down Expand Up @@ -70,7 +70,7 @@ jobs:
- name: Create dist/
run: |
python setup.py sdist bdist_wheel
python setup.py bdist_wheel
twine check dist/*
- name: Publish package to PyPI
Expand All @@ -86,6 +86,7 @@ jobs:
time: '150' # seconds

- name: Install latest PyPI version in fresh venv
id: attempt1
run: |
NII2DCM_VERSION=`echo "$(nii2dcm -v)"`
echo $NII2DCM_VERSION
Expand All @@ -97,3 +98,58 @@ jobs:
nii2dcm -h
echo "nii2dcm version:"
nii2dcm -v
continue-on-error: true

- name: Wait longer
if: steps.attempt1.outcome != 'success'
uses: GuillaumeFalourd/wait-sleep-action@v1
with:
time: '150' # seconds

- name: Re-attempt PyPI install
if: steps.attempt1.outcome != 'success'
run: |
NII2DCM_VERSION=`echo "$(nii2dcm -v)"`
echo $NII2DCM_VERSION
python -m venv nii2dcm-temp
source nii2dcm-temp/bin/activate
pip install --upgrade pip
pip install setuptools wheel
pip install nii2dcm==$NII2DCM_VERSION
nii2dcm -h
echo "nii2dcm version:"
nii2dcm -v
ghcr-publish:
needs: pypi-publish
runs-on: ubuntu-latest

permissions:
contents: read
packages: write

steps:
- name: Checkout repository
uses: actions/checkout@v4

- name: Log in to GHCR
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ secrets.GHCR_USERNAME }}
password: ${{ secrets.GHCR_TOKEN }}

- name: Extract metadata (tags, labels) for Docker
id: meta
uses: docker/metadata-action@v5
with:
images: |
ghcr.io/tomaroberts/nii2dcm
- name: Build and push Docker image
uses: docker/build-push-action@v5
with:
context: .
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
26 changes: 25 additions & 1 deletion .github/workflows/publish_testpypi.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,12 @@
# They are provided by a third-party and are governed by
# separate terms of service, privacy policy, and support
# documentation.
#

name: Publish package to TestPyPI

on:
pull_request:
push:
branches:
- main
Expand Down Expand Up @@ -67,7 +69,7 @@ jobs:
- name: Create dist/
run: |
python setup.py sdist bdist_wheel
python setup.py bdist_wheel
twine check dist/*
- name: Publish package to TestPyPI
Expand All @@ -84,6 +86,28 @@ jobs:
time: '150' # seconds

- name: Install latest TestPyPI version in fresh venv
id: attempt1
run: |
NII2DCM_VERSION=`echo "$(nii2dcm -v)"`
echo $NII2DCM_VERSION
python -m venv nii2dcm-temp
source nii2dcm-temp/bin/activate
pip install --upgrade pip
pip install setuptools wheel
pip install --index-url https://test.pypi.org/simple/ --extra-index-url https://pypi.org/simple/ nii2dcm==$NII2DCM_VERSION
nii2dcm -h
echo "nii2dcm version:"
nii2dcm -v
continue-on-error: true

- name: Wait longer
if: steps.attempt1.outcome != 'success'
uses: GuillaumeFalourd/wait-sleep-action@v1
with:
time: '150' # seconds

- name: Re-attempt TestPyPI install
if: steps.attempt1.outcome != 'success'
run: |
NII2DCM_VERSION=`echo "$(nii2dcm -v)"`
echo $NII2DCM_VERSION
Expand Down
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,8 @@ coverage.xml
.hypothesis/
.pytest_cache/
cover/
pytest-coverage.txt
pytest.xml

# Sphinx documentation
docs/_build/
Expand Down
29 changes: 29 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# Use the official Python image as the base image
FROM python:3.9-slim

LABEL org.opencontainers.image.source https://github.com/tomaroberts/nii2dcm

# Setup
COPY . /home/nii2dcm
WORKDIR /home/nii2dcm

# Install system dependencies
RUN apt-get update && apt-get install -y \
bash git \
&& apt-get clean

# Update base packages
RUN pip install --upgrade pip && \
pip install setuptools wheel

# Install nii2dcm requirements
RUN pip install -r requirements.txt

# Build package from source
RUN pip install .

# Test nii2dcm install
# To see output locally during build process: docker build -t nii2dcm --progress=plain .
RUN nii2dcm -h

ENTRYPOINT ["nii2dcm"]
26 changes: 25 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,10 @@
·
<a href="https://github.com/tomaroberts/nii2dcm/issues">Request Feature</a>
</p>
<p align="center">
<img src="https://github.com/tomaroberts/nii2dcm/actions/workflows/build_and_test_cli.yml/badge.svg?branch=unit-tests">
<img src="https://img.shields.io/endpoint?url=https://gist.githubusercontent.com/tomaroberts/57ef8057d04f67dbe6e64df410b83079/raw/nii2dcm-pytest-coverage-comment.json">
</p>
</div>


Expand Down Expand Up @@ -53,6 +57,7 @@ To install and run nii2dcm locally, you have two options:

### pip

Create a new Python virtual environment, then:
```shell
pip install nii2dcm
```
Expand All @@ -74,7 +79,6 @@ python -m pip install --upgrade pip

Install dependencies and nii2dcm:
```sh
pip install setuptools wheel
pip install -r requirements.txt
pip install .
```
Expand Down Expand Up @@ -138,6 +142,26 @@ Currently, attributes to transfer are [listed here in the DicomMRI class](https:
<p align="right">(<a href="#readme-top">back to top</a>)</p>


<!-- Docker -->
## Docker
nii2dcm is also available as a Docker container.

Pull the latest container with:
```shell
docker pull ghcr.io/tomaroberts/nii2dcm/nii2dcm:latest
```

Run the containerised nii2dcm:
```shell
# display nii2dcm version
docker run nii2dcm -v

# perform nii2dcm conversion
docker run nii2dcm nifti-file.nii.gz dicom-output-directory/ -d MR
```

<p align="right">(<a href="#readme-top">back to top</a>)</p>

<!-- ROADMAP -->
## Roadmap

Expand Down
6 changes: 5 additions & 1 deletion nii2dcm/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,11 @@ def cli(args=None):

parser.add_argument("input_file", type=str, help="[.nii/.nii.gz] input NIfTI file")
parser.add_argument("output_dir", type=str, help="[directory] output DICOM path")
parser.add_argument("-d", "--dicom_type", type=str, help="[string] type of DICOM. e.g. MR, CT, US, XR, etc.")
parser.add_argument(
"-d", "--dicom_type",
type=str,
help="[string] type of DICOM. Available types: MR, SVR."
)
parser.add_argument("-r", "--ref_dicom", type=str, help="[.dcm] Reference DICOM file for Attribute transfer")
parser.add_argument("-v", "--version", action="version", version=__version__)

Expand Down
4 changes: 2 additions & 2 deletions nii2dcm/_version.py
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
from dunamai import Version, Style
__version__ = Version.from_git().serialize(metadata=False, style=Style.SemVer)
import dunamai as _dunamai
__version__ = _dunamai.get_version("nii2dcm", third_choice=_dunamai.Version.from_any_vcs).serialize()
3 changes: 1 addition & 2 deletions nii2dcm/dcm.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@
import pydicom as pyd
from pydicom.dataset import FileDataset, FileMetaDataset

from nii2dcm.utils import dcm_dictionary_update
from nii2dcm.modules.patient import Patient
from nii2dcm.modules.general_study import GeneralStudy
from nii2dcm.modules.patient_study import PatientStudy
Expand Down Expand Up @@ -67,7 +66,7 @@ def __init__(self, filename=nii2dcm_temp_filename):

"""
Set Dicom Date/Time
Important: doing this once sets all Instances/Series/Study creation dates and times to the same values. Whereas,
Important: doing this once sets all Instances/Series/Study creation dates and times to the same values. Whereas,
doing this within the Modules would every so slightly offset the times
"""
# TODO shift to utils.py and propagate to Modules, or, create method within this Dicom class
Expand Down
8 changes: 4 additions & 4 deletions nii2dcm/dcm_writer.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,19 +6,19 @@
import pydicom as pyd


def write_slice(dcm, img_data, instance_index, output_dir):
def write_slice(dcm, img_data, slice_index, output_dir):
"""
write a single DICOM slice
dcm – nii2dcm DICOM object
img_data - [nX, nY, nSlice] image pixel data, such as from NIfTI file
instance_indexinstance index (important: counts from 0)
slice_indexslice index in nibabel img_data array (important: counts from 0, whereas DICOM instances count from 1)
output_dir – output DICOM file save location
"""

output_filename = r'IM_%04d.dcm' % (instance_index + 1) # begin filename from 1, e.g. IM_0001.dcm
output_filename = r'IM_%04d.dcm' % (slice_index + 1) # begin filename from 1, e.g. IM_0001.dcm

img_slice = img_data[:, :, instance_index]
img_slice = img_data[:, :, slice_index]

# Instance UID – unique to current slice
dcm.ds.SOPInstanceUID = pyd.uid.generate_uid(None)
Expand Down
4 changes: 2 additions & 2 deletions nii2dcm/modules/mr_image.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,9 @@ def __init__(self):
# https://dicom.nema.org/medical/Dicom/current/output/chtml/part03/sect_C.8.3.html#sect_C.8.3.1.1.1
# For now, will omit thereby inheriting parent value
# self.ds.ImageType = ''

self.ds.SamplesPerPixel = 1

# PhotometricInterpretation
# TODO: decide MONOCHROME1 or MONOCHROME2 as default
# https://dicom.nema.org/medical/Dicom/current/output/chtml/part03/sect_C.7.6.3.html#sect_C.7.6.3.1.2
Expand Down
2 changes: 2 additions & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
setuptools
wheel
numpy==1.23.2
matplotlib==3.6.2
nibabel==5.0.0
Expand Down
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,5 @@

setup(
name="nii2dcm",
version=Version.from_git().serialize(metadata=False, style=Style.SemVer),
version=Version.from_any_vcs().serialize(metadata=False, style=Style.SemVer),
)
Empty file added tests/__init__.py
Empty file.
Loading

0 comments on commit 242cb46

Please sign in to comment.