Skip to content

Commit

Permalink
Output to .txt and format numbers with commas
Browse files Browse the repository at this point in the history
  • Loading branch information
jennydaman committed Mar 31, 2022
1 parent 612ae21 commit b07cf7a
Show file tree
Hide file tree
Showing 6 changed files with 63 additions and 18 deletions.
22 changes: 22 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,28 @@ mv brain_recon.nii.gz incoming/
singularity exec docker://fnndsc/pl-mri-preview mri_preview incoming/ outgoing/
```

## File Types

### `--input-suffix`

Every input file with a file name ending with a value given by `--input-suffix`
is processed. Unmatched files are ignored. Supported formats are listed on
[NiBabel](https://nipy.org/nibabel/)'s website.

### `--output`

`pl-mri-preview` creates image files. Supported output formats are
any which are supported by
[matplotlib](https://matplotlib.org/stable/api/_as_gen/matplotlib.pyplot.savefig.html),
including `.png`, `.jpg`, and `.svg`.

The special type `.txt` writes a plaintext file instead, e.g.

```
2236612 voxels
230437.66977741406 mm^3
```

## About Brain Volume

### Input File
Expand Down
Binary file modified examples/out/sub-feta037_T2w.nii.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 2 additions & 0 deletions examples/out/sub-feta037_T2w.nii.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
2236612 voxels
230437.66977741406 mm^3
53 changes: 37 additions & 16 deletions mri_preview.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
from pathlib import Path
from argparse import ArgumentParser, Namespace, ArgumentDefaultsHelpFormatter
from importlib.metadata import Distribution
from typing import Iterable

import nibabel as nib
import numpy.typing as npt
Expand Down Expand Up @@ -35,25 +36,25 @@
formatter_class=ArgumentDefaultsHelpFormatter)
parser.add_argument('-b', '--background', default=0.0, type=float,
help='threshold indicating background voxels')
parser.add_argument('-p', '--pattern', default='**/*.nii*',
help='input file pattern')
parser.add_argument('-o', '--output', default='.png',
help='output file extension')
parser.add_argument('-i', '--inputs', default='.nii,.nii.gz,.mnc,.mgz',
help='file extension of input files, comma-separated')
parser.add_argument('-o', '--outputs', default='.png,.txt',
help='output file extensions, comma-separated')
parser.add_argument('-V', '--version', action='version',
version=f'$(prog)s {__version__}')


def total_volume(img, threshold: float = 0.0) -> tuple[int, str]:
def total_volume(img, threshold: float = 0.0) -> tuple[int, float, str]:
"""
:param img: nibabel image
:param threshold: foreground intensity threshold
:return: total number of voxels and volume with units
:return: total number of voxels, volume, and cubic units of the volume
"""
data = img.get_fdata()
units, _ = img.header.get_xyzt_units()
num_voxels = count_positive(data, threshold)
total_vol = num_voxels * get_voxel_size(img.affine)
return num_voxels, f'{total_vol:.3f} {units}\u00B3'
return num_voxels, total_vol, units


def get_voxel_size(affine: npt.NDArray) -> float:
Expand Down Expand Up @@ -98,6 +99,24 @@ def slices_figure(data: npt.NDArray, caption: str) -> plt.Figure:
return fig


def multi_mapper(inputdir: Path, outputdir: Path, file_extensions: str) -> Iterable[tuple[Path, Path]]:
for file_extension in file_extensions.split(','):
yield from PathMapper(
inputdir, outputdir,
glob=f'**/*{file_extension}', suffix='.out', fail_if_empty=False
)


def save_as(img, output: Path, num_voxels: int, total_vol: float, units: str) -> None:
if output.name.endswith('.txt'):
with output.open('w') as f:
f.write(f'{num_voxels} voxels\n{total_vol} {units}^3')
else:
text = f'total volume = \n{num_voxels:,} voxels\n{total_vol:,.1f} {units}\u00B3'
fig = slices_figure(img.get_fdata(), text)
fig.savefig(output)


@chris_plugin(
parser=parser,
title='Brain Volume',
Expand All @@ -107,19 +126,21 @@ def slices_figure(data: npt.NDArray, caption: str) -> plt.Figure:
)
def main(options: Namespace, inputdir: Path, outputdir: Path):
print(DISPLAY_TITLE, file=sys.stderr, flush=True)
logger.debug('input files pattern: "{}"', options.pattern)
logger.debug('input files: {}', options.inputs.split(','))
logger.debug('output formats: {}', options.outputs.split(','))
logger.debug('background threshold: {}', options.background)
mapper = PathMapper(inputdir, outputdir, glob=options.pattern, suffix=options.output)
mapper = multi_mapper(inputdir, outputdir, options.inputs)

for input_file, output_file in mapper:
for input_file, output_base in mapper:
try:
img = nib.load(input_file)
data = img.get_fdata()
num_voxels, total_vol = total_volume(img, options.background)
text = f'total volume = \n{num_voxels} voxels\n{total_vol}'
fig = slices_figure(data, text)
fig.savefig(output_file)
logger.info('{} -> {}: {} voxels, volume={}', input_file, output_file, num_voxels, total_vol)
num_voxels, total_vol, units = total_volume(img, options.background)

logger.info('{}: {} voxels, volume={} {}^3', input_file, num_voxels, total_vol, units)
for output_ext in options.outputs.split(','):
output_file = output_base.with_suffix(output_ext)
save_as(img, output_file, num_voxels, total_vol, units)
logger.info('\t-> {}', output_file)
except Exception:
logger.error('Failed to process {}', input_file)
raise
Expand Down
2 changes: 1 addition & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
chris_plugin~=0.0.14
chris_plugin~=0.0.15
nibabel~=3.2.2
matplotlib~=3.5.1
loguru~=0.6.0
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

setup(
name='mri-preview',
version='1.0.0',
version='1.1.0',
description='A ChRIS plugin to preview the center slices of MRI',
author='Jennings Zhang',
author_email='Jennings.Zhang@childrens.harvard.edu',
Expand Down

0 comments on commit b07cf7a

Please sign in to comment.