-
Notifications
You must be signed in to change notification settings - Fork 20
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
Fixes and improvements (by mtnhuck) #13
Changes from all commits
eb40d08
7b5737e
4fa8695
e19b5ed
882effc
ef32caf
80ee7ff
e4eb087
9a0dd89
36a2695
77e9bcf
ce99948
e517ccc
62cf18f
dc8d63e
735eab0
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
language: python | ||
|
||
cache: | ||
directories: | ||
- $HOME/.cache/pip | ||
|
||
python: | ||
- 2.7 | ||
- 3.5 | ||
- 3.6 | ||
- 3.7 | ||
|
||
install: | ||
- travis_retry pip install -e . | ||
- travis_retry pip install pytest pytest-cov coverage | ||
|
||
script: | ||
- python -m pytest --cov=bids2nda -s -v bids2nda | ||
|
||
after_success: | ||
- travis_retry pip install codecov | ||
- codecov |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -15,6 +15,7 @@ | |
import nibabel as nb | ||
import json | ||
import pandas as pd | ||
import numpy as np | ||
|
||
|
||
# Gather our code in a main() function | ||
|
@@ -74,6 +75,55 @@ def dict_append(d, key, value): | |
d[key] = [value, ] | ||
|
||
|
||
def cosine_to_orientation(iop): | ||
"""Deduce slicing from cosines | ||
|
||
From http://nipy.org/nibabel/dicom/dicom_orientation.html#dicom-voxel-to | ||
-patient-coordinate-system-mapping | ||
|
||
From Section C.7.6.1.1.1 we see that the "positive row axis" is left to | ||
right, and is the direction of the rows, given by the direction of last | ||
pixel in the first row from the first pixel in that row. Similarly the | ||
"positive column axis" is top to bottom and is the direction of the columns, | ||
given by the direction of the last pixel in the first column from the first | ||
pixel in that column. | ||
|
||
Let's rephrase: the first three values of "Image Orientation Patient" are | ||
the direction cosine for the "positive row axis". That is, they express the | ||
direction change in (x, y, z), in the DICOM patient coordinate system | ||
(DPCS), as you move along the row. That is, as you move from one column to | ||
the next. That is, as the column array index changes. Similarly, the second | ||
triplet of values of "Image Orientation Patient" (img_ornt_pat[3:] in | ||
Python), are the direction cosine for the "positive column axis", and | ||
express the direction you move, in the DPCS, as you move from row to row, | ||
and therefore as the row index changes. | ||
|
||
Parameters | ||
---------- | ||
iop: list of float | ||
Values of the ImageOrientationPatient field | ||
|
||
Returns | ||
------- | ||
{'Axial', 'Coronal', 'Sagittal'} | ||
""" | ||
# Solution based on https://stackoverflow.com/a/45469577 | ||
iop_round = np.round(iop) | ||
plane = np.cross(iop_round[0:3], iop_round[3:6]) | ||
plane = np.abs(plane) | ||
if plane[0] == 1: | ||
return "Sagittal" | ||
elif plane[1] == 1: | ||
return "Coronal" | ||
elif plane[2] == 1: | ||
return "Axial" | ||
else: | ||
raise RuntimeError( | ||
"Could not deduce the image orientation of %r. 'plane' value is %r" | ||
% (iop, plane) | ||
) | ||
|
||
|
||
def run(args): | ||
|
||
guid_mapping = dict([line.split(" - ") for line in open(args.guid_mapping).read().split("\n") if line != '']) | ||
|
@@ -155,18 +205,29 @@ def run(args): | |
else: | ||
description = suffix | ||
dict_append(image03_dict, 'experiment_id', '') | ||
# Shortcut for the global.const section -- apparently might not be flattened fully | ||
metadata_const = metadata.get('global', {}).get('const', {}) | ||
dict_append(image03_dict, 'image_description', description) | ||
dict_append(image03_dict, 'scan_type', suffix_to_scan_type[suffix]) | ||
dict_append(image03_dict, 'scan_object', "Live") | ||
dict_append(image03_dict, 'image_file_format', "NIFTI") | ||
dict_append(image03_dict, 'image_modality', "MRI") | ||
dict_append(image03_dict, 'scanner_manufacturer_pd', metadata.get("Manufacturer", "")) | ||
dict_append(image03_dict, 'scanner_type_pd', metadata.get("ManufacturersModelName", "")) | ||
dict_append(image03_dict, 'scanner_software_versions_pd', metadata.get("HardcopyDeviceSoftwareVersion", "")) | ||
dict_append(image03_dict, 'scanner_software_versions_pd', metadata.get("SoftwareVersions", "")) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. +1 -- I do not see the HardcopyDeviceSoftwareVersion present anywhere. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think this one is settled |
||
dict_append(image03_dict, 'magnetic_field_strength', metadata.get("MagneticFieldStrength", "")) | ||
dict_append(image03_dict, 'mri_echo_time_pd', metadata.get("EchoTime", "")) | ||
dict_append(image03_dict, 'flip_angle', metadata.get("FlipAngle", "")) | ||
dict_append(image03_dict, 'receive_coil', metadata.get("ReceiveCoilName", "")) | ||
# ImageOrientationPatientDICOM is populated by recent dcm2niix, | ||
# and ImageOrientationPatient might be provided by exhastive metadata | ||
# record done by heudiconv | ||
iop = metadata.get( | ||
'ImageOrientationPatientDICOM', | ||
metadata_const.get("ImageOrientationPatient", None) | ||
) | ||
dict_append(image03_dict, 'image_orientation', cosine_to_orientation(iop) if iop else '') | ||
|
||
dict_append(image03_dict, 'transformation_performed', 'Yes') | ||
dict_append(image03_dict, 'transformation_type', 'BIDS2NDA') | ||
|
||
|
@@ -194,6 +255,8 @@ def run(args): | |
dict_append(image03_dict, 'image_resolution1', nii.header.get_zooms()[0]) | ||
dict_append(image03_dict, 'image_resolution2', nii.header.get_zooms()[1]) | ||
dict_append(image03_dict, 'image_resolution3', nii.header.get_zooms()[2]) | ||
dict_append(image03_dict, 'image_slice_thickness', metadata_const.get("SliceThickness", nii.header.get_zooms()[2])) | ||
dict_append(image03_dict, 'photomet_interpret', metadata.get("global",{}).get("const",{}).get("PhotometricInterpretation","")) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This beast I am not sure even where to get from. In our use-case we just adjusted the code to provide There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I pinged the NIH help desk for some guidance on the interpretation of this field. Will report back. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Thanks! |
||
if len(nii.shape) > 3: | ||
image_resolution4 = nii.header.get_zooms()[3] | ||
else: | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
from ..main import cosine_to_orientation | ||
|
||
|
||
def test_cosine_to_orientation(): | ||
assert cosine_to_orientation([0.9, -0.03, -0.1, 0.03, 0.9, 0.1]) == 'Axial' | ||
assert cosine_to_orientation([0, 0.9, 0.1, 0.03, 0.1, -0.9]) == 'Sagittal' | ||
|
||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
am I reading it right @mtnhuck that it is pretty much yet another "mapping" but now for the task/runs? unfortunately that url requires a login so can't assess. Could you please give an example?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is a number that comes from the NDA archive, ie they provide you with a number for your experiment. I'm still waiting to hear back from them if it is at the session level or the scan level (ie would resting-state need a different experiment number than a face-perception task?). An example number which I generated on their website is 867.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok, let's wait for them to reply and then make it an option or mapping
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
According to the .gov ppl,
experiment_id
is per "experiment within the study" so resting state would get one and face-perception would get another.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As agreed later on, this is the id for user to provide. We have a preliminary patch ready to submit (will be done after this PR is merged) to provide the mapping from BIDS task id to experiment_id via a simple .tsv file.