Skip to content

Commit

Permalink
Merge pull request #15 from danishm/auto-contrast-2
Browse files Browse the repository at this point in the history
Auto Contrast
  • Loading branch information
danishm authored Sep 12, 2018
2 parents 105d5cc + d1d2eea commit f3d00f6
Show file tree
Hide file tree
Showing 15 changed files with 296 additions and 88 deletions.
5 changes: 4 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -18,4 +18,7 @@
docs/build

# Build
build/
build/

# VSCode
.vscode
12 changes: 7 additions & 5 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
FROM python:onbuild
RUN pip install nose
RUN python setup.py install
RUN nosetests -v
RUN mritopng
FROM python:3.6
COPY . /app
WORKDIR /app
RUN pip install -r requirements.txt
RUN python setup.py install
RUN nosetests
RUN mritopng --help
97 changes: 97 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
Convert DICOM Files to PNG
===========================

[![CircleCI](https://circleci.com/gh/danishm/mritopng.svg?style=shield)](https://circleci.com/gh/danishm/mritopng) [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)

*Important Changes*
- **8/26/2018** - Ability to apply auto-contrast to the converted images

Introduction
------------
A simple python module to make it easy to batch convert a binary DICOM file, which is usually an output from
an MRI scan to a PNG image.

The MRI scanning facilities typically hand you a CD containing your MRI scans. This CD will typically not contain
any image files in traditional formats that can be opened up by your default image viewing program. The CD contains
a list of DICOM files, which can only be viewed by the included viewer, which is mostly only supported on a Windows machine.

This module should help you convert all the DICOM based scans to PNG files. This tool can be used as a command line tools as well as a library in your python code

Installation
------------

To have known to work dependencies use beforehand::

pip install -r requirements.txt

`mritopng` comes with a `setup.py` script to use with distutils. After unpacking the distribution, `cd` into the directory and execute the command::

python setup.py install


This will install two things

* The `mritopng` module will be installed; `import mritopng` will allow you to use it
* A command line utility called `mritopng` which can be used from the console

Quick Start
-----------
`mritopng` will install a command line utility that can be used to convert individual DICOM files or folders

### Getting Help

```
$ mritopng --help
usage: mritopng [-h] [-f] [-c] dicom_path png_path
Convert a dicom MRI file to png. To conver a whole folder recursivly, use the
-f option
positional arguments:
dicom_path Full path to the mri file
png_path Full path to the generated png file
optional arguments:
-h, --help show this help message and exit
-f, --folder Convert a whole folder instead of a single file
-c, --auto-contrast Apply contrast after converting default image
```

### Convert Single File

```sh
# Converts the file /DICOM/SCAN1 to a file called output.png,
# while applying auto contrast
$ mritopng --auto-contrast /DICOM/SCAN1 output.png
```

**Note:** If file `output.png` already exists, it will be overwritten

### Convert Folder Tree

The utility can also be used to convert a whole folder recursively by using the `-f` option::

```sh
# Takes all the files in /DICOM, converts the files to png and
# puts them in the /PNG folder with the same structure as /DICOM.
$ mritopng -f /DICOM /PNG
```

**Note:**
- Existing top level folder will NOT be over-written e.g. the example above will fail of the folder `/PNG` already exists
- The tool will try to convert as many files as it can, skipping the ones that it can't

Using it as a Library
---------------------

It's pretty easy to get up and running with `mritopng` in your own project

```py
import mritopng

# Convert a single file with auto-contrast
mritopng.convert_file('/home/user/DICOM/SCAN1', '/home/user/output.png', auto_contrast=True)

# Convert a whole folder recursively
mritopng.convert_folder('/home/user/DICOM/', '/home/user/PNG/')
```
57 changes: 0 additions & 57 deletions README.rst

This file was deleted.

32 changes: 29 additions & 3 deletions circle.yml
Original file line number Diff line number Diff line change
@@ -1,3 +1,29 @@
machine:
post:
- pyenv global 2.7.12 3.4.4
version: 2

jobs:

build:
working_directory: ~/mritopng
docker:
- image: circleci/python:3.6.1
steps:
- checkout

- run:
name: Setup Python Environment
command: |
python3 -m venv venv
. venv/bin/activate
pip install -r requirements.txt
- run:
name: Run Tests
command: |
. venv/bin/activate
nosetests --with-xunit --xunit-file=build/test/test-results.xml
- store_test_results:
path: ~/mritopng/build/test

- store_artifacts:
path: ~/mritopng/build/test
31 changes: 21 additions & 10 deletions mritopng/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,26 @@
import png
import pydicom
import numpy as np
from .models import GrayscaleImage
from .contrast import auto_contrast

def mri_to_png(mri_file, png_file):
def mri_to_png(mri_file, png_file, do_auto_contrast=False):
""" Function to convert from a DICOM image to png
@param mri_file: An opened file like object to read te dicom data
@param png_file: An opened file like object to write the png data
"""

image_2d = extract_grayscale_image(mri_file)

if do_auto_contrast:
image_2d = auto_contrast(image_2d)

# Writing the PNG file
w = png.Writer(image_2d.width, image_2d.height, greyscale=True)
w.write(png_file, image_2d.image)

def extract_grayscale_image(mri_file):
# Extracting data from the mri file
plan = pydicom.read_file(mri_file)
shape = plan.pixel_array.shape
Expand All @@ -23,12 +35,10 @@ def mri_to_png(mri_file, png_file):
#Convert to uint
image_2d_scaled = np.uint8(image_2d_scaled)

# Writing the PNG file
w = png.Writer(shape[1], shape[0], greyscale=True)
w.write(png_file, image_2d_scaled)
return GrayscaleImage(image_2d_scaled, shape[1], shape[0])


def convert_file(mri_file_path, png_file_path):
def convert_file(mri_file_path, png_file_path, auto_contrast=False):
""" Function to convert an MRI binary file to a
PNG image file.
Expand All @@ -38,21 +48,22 @@ def convert_file(mri_file_path, png_file_path):

# Making sure that the mri file exists
if not os.path.exists(mri_file_path):
raise Exception('File "%s" does not exists' % mri_file_path)
raise Exception('Source file "%s" does not exists' % mri_file_path)

# Making sure the png file does not exist
if os.path.exists(png_file_path):
raise Exception('File "%s" already exists' % png_file_path)
print('Removing existing output file %s' % png_file_path)
os.remove(png_file_path)

mri_file = open(mri_file_path, 'rb')
png_file = open(png_file_path, 'wb')

mri_to_png(mri_file, png_file)
mri_to_png(mri_file, png_file, auto_contrast)

png_file.close()


def convert_folder(mri_folder, png_folder):
def convert_folder(mri_folder, png_folder, auto_contrast=False):
""" Convert all MRI files in a folder to png files
in a destination folder
"""
Expand All @@ -77,7 +88,7 @@ def convert_folder(mri_folder, png_folder):

try:
# Convert the actual file
convert_file(mri_file_path, png_file_path)
convert_file(mri_file_path, png_file_path, auto_contrast)
print('SUCCESS: %s --> %s' % (mri_file_path, png_file_path))
except Exception as e:
print('FAIL: %s --> %s : %s' % (mri_file_path, png_file_path, e))
12 changes: 7 additions & 5 deletions mritopng/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,16 +7,18 @@


def main():
parser = argparse.ArgumentParser(description="Convert a dicom MRI file to png")
parser.add_argument('-f', action='store_true')
parser = argparse.ArgumentParser(description="Convert a dicom MRI file to png. To conver a whole folder recursivly, use the -f option")
parser.add_argument('-f', '--folder', action='store_true', help='Convert a whole folder instead of a single file')
parser.add_argument('-c', '--auto-contrast', help='Apply contrast after converting default image', action="store_true")
parser.add_argument('dicom_path', help='Full path to the mri file')
parser.add_argument('png_path', help='Full path to the generated png file')

args = parser.parse_args()
if args.f:
convert_folder(args.dicom_path, args.png_path)
print('Arguments: %s', args)
if args.folder:
convert_folder(args.dicom_path, args.png_path, args.auto_contrast)
else:
convert_file(args.dicom_path, args.png_path)
convert_file(args.dicom_path, args.png_path, args.auto_contrast)

if __name__ == '__main__':
main()
42 changes: 42 additions & 0 deletions mritopng/contrast.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import numpy as np
from .models import GrayscaleImage

def histogram(image):

hist = dict()

# Initialize dict
for shade in range(0, 256):
hist[shade] = 0

for index, val in np.ndenumerate(image.image):
hist[val] += 1

return hist


def shade_at_percentile(hist, percentile):

n = sum(hist.values())
cumulative_sum = 0.0
for shade in range(0, 256):
cumulative_sum += hist[shade]
if cumulative_sum/n >= percentile:
return shade

return None

def auto_contrast(image):
""" Apply auto contrast to an image using
https://stackoverflow.com/questions/9744255/instagram-lux-effect/9761841#9761841
"""
hist = histogram(image)
p5 = shade_at_percentile(hist, .01)
p95 = shade_at_percentile(hist, .99)
a = 255.0/(p95 + p5)
b = -1.0 * a * p5

result = (image.image.astype(float) * a) + b
result = result.clip(0, 255.0)

return GrayscaleImage(np.uint8(result), image.width, image.height)
9 changes: 9 additions & 0 deletions mritopng/models.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
class GrayscaleImage(object):

def __init__(self, image, width, height):
self.image = image
self.width = width
self.height = height

def __str__(self):
return '[%dx%d]' % (self.width, self.height)
7 changes: 4 additions & 3 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
numpy==1.14.1
pydicom==1.0.2
pypng==0.0.18
numpy>=1.14.1
pydicom==1.0.2
pypng==0.0.18
nose
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ def readme():

setup(
name='mritopng',
version='2.1',
version='2.2',
description='Easily convert MRI filed based on the DICOM format to a PNG image',
long_description=readme(),
classifiers=[
Expand Down
Binary file added tests/data/expected/auto-contrast/000012.dcm.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added tests/data/expected/auto-contrast/000017.dcm.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added tests/data/expected/auto-contrast/dicom1.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading

0 comments on commit f3d00f6

Please sign in to comment.