/usr/share/pyshared/nibabel/nicom/csareader.py is in python-nibabel 1.2.2-1.
This file is owned by root:root, with mode 0o644.
The actual contents of the file can be viewed below.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 | ''' CSA header reader from SPM spec
'''
import numpy as np
from ..py3k import ZEROB, asbytes, asstr
from .structreader import Unpacker
# DICOM VR code to Python type
_CONVERTERS = {
'FL': float, # float
'FD': float, # double
'DS': float, # decimal string
'SS': int, # signed short
'US': int, # unsigned short
'SL': int, # signed long
'UL': int, # unsigned long
'IS': int, # integer string
}
class CSAError(Exception):
pass
class CSAReadError(CSAError):
pass
def get_csa_header(dcm_data, csa_type='image'):
''' Get CSA header information from DICOM header
Return None if the header does not contain CSA information of the
specified `csa_type`
Parameters
----------
dcm_data : dicom.Dataset
DICOM dataset. Needs only implement the tag fetch with
``dcm_data[group, element]`` syntax
csa_type : {'image', 'series'}, optional
Type of CSA field to read; default is 'image'
Returns
-------
csa_info : None or dict
Parsed CSA field of `csa_type` or None, if we cannot find the CSA
information.
'''
csa_type = csa_type.lower()
if csa_type == 'image':
element_no = 0x1010
label = 'Image'
elif csa_type == 'series':
element_no = 0x1020
label = 'Series'
else:
raise ValueError('Invalid CSA header type "%s"'
% csa_type)
try:
tag = dcm_data[0x29, element_no]
except KeyError:
return None
if tag.name != '[CSA %s Header Info]' % label:
return None
return read(tag.value)
def read(csa_str):
''' Read CSA header from string `csa_str`
Parameters
----------
csa_str : str
byte string containing CSA header information
Returns
-------
header : dict
header information as dict, where `header` has fields (at least)
``type, n_tags, tags``. ``header['tags']`` is also a dictionary
with one key, value pair for each tag in the header.
'''
csa_len = len(csa_str)
csa_dict = {'tags': {}}
hdr_id = csa_str[:4]
up_str = Unpacker(csa_str, endian='<')
if hdr_id == asbytes('SV10'): # CSA2
hdr_type = 2
up_str.ptr = 4 # omit the SV10
csa_dict['unused0'] = up_str.read(4)
else: # CSA1
hdr_type = 1
csa_dict['type'] = hdr_type
csa_dict['n_tags'], csa_dict['check'] = up_str.unpack('2I')
if not 0 < csa_dict['n_tags'] <= 128:
raise CSAReadError('Number of tags `t` should be '
'0 < t <= 128')
for tag_no in range(csa_dict['n_tags']):
name, vm, vr, syngodt, n_items, last3 = \
up_str.unpack('64si4s3i')
vr = nt_str(vr)
name = nt_str(name)
tag = {'n_items': n_items,
'vm': vm, # value multiplicity
'vr': vr, # value representation
'syngodt': syngodt,
'last3': last3,
'tag_no': tag_no}
if vm == 0:
n_values = n_items
else:
n_values = vm
# data converter
converter = _CONVERTERS.get(vr)
# CSA1 specific length modifier
if tag_no == 1:
tag0_n_items = n_items
assert n_items < 100
items = []
for item_no in range(n_items):
x0,x1,x2,x3 = up_str.unpack('4i')
ptr = up_str.ptr
if hdr_type == 1: # CSA1 - odd length calculation
item_len = x0 - tag0_n_items
if item_len < 0 or (ptr + item_len) > csa_len:
if item_no < vm:
items.append('')
break
else: # CSA2
item_len = x1
if (ptr + item_len) > csa_len:
raise CSAReadError('Item is too long, '
'aborting read')
if item_no >= n_values:
assert item_len == 0
continue
item = nt_str(up_str.read(item_len))
if converter:
# we may have fewer real items than are given in
# n_items, but we don't know how many - assume that
# we've reached the end when we hit an empty item
if item_len == 0:
n_values = item_no
continue
item = converter(item)
items.append(item)
# go to 4 byte boundary
plus4 = item_len % 4
if plus4 != 0:
up_str.ptr += (4-plus4)
tag['items'] = items
csa_dict['tags'][name] = tag
return csa_dict
def get_scalar(csa_dict, tag_name):
try:
items = csa_dict['tags'][tag_name]['items']
except KeyError:
return None
if len(items) == 0:
return None
return items[0]
def get_vector(csa_dict, tag_name, n):
try:
items = csa_dict['tags'][tag_name]['items']
except KeyError:
return None
if len(items) == 0:
return None
if len(items) != n:
raise ValueError('Expecting %d vector' % n)
return np.array(items)
def is_mosaic(csa_dict):
''' Return True if the data is of Mosaic type
Parameters
----------
csa_dict : dict
dict containing read CSA data
Returns
-------
tf : bool
True if the `dcm_data` appears to be of Siemens mosaic type,
False otherwise
'''
if csa_dict is None:
return False
if get_acq_mat_txt(csa_dict) is None:
return False
n_o_m = get_n_mosaic(csa_dict)
return not (n_o_m is None) and n_o_m != 0
def get_n_mosaic(csa_dict):
return get_scalar(csa_dict, 'NumberOfImagesInMosaic')
def get_acq_mat_txt(csa_dict):
return get_scalar(csa_dict, 'AcquisitionMatrixText')
def get_slice_normal(csa_dict):
return get_vector(csa_dict, 'SliceNormalVector', 3)
def get_b_matrix(csa_dict):
vals = get_vector(csa_dict, 'B_matrix', 6)
if vals is None:
return
# the 6 vector is the upper triangle of the symmetric B matrix
inds = np.array([0, 1, 2, 1, 3, 4, 2, 4, 5])
B = np.array(vals)[inds]
return B.reshape(3,3)
def get_b_value(csa_dict):
return get_scalar(csa_dict, 'B_value')
def get_g_vector(csa_dict):
return get_vector(csa_dict, 'DiffusionGradientDirection', 3)
def get_ice_dims(csa_dict):
dims = get_scalar(csa_dict, 'ICE_Dims')
if dims is None:
return None
return dims.split('_')
def nt_str(s):
''' Strip string to first null
Parameters
----------
s : str
Returns
-------
sdash : str
s stripped to first occurence of null (0)
'''
zero_pos = s.find(ZEROB)
if zero_pos == -1:
return s
return asstr(s[:zero_pos])
|