UNPKG

qiprofile

Version:

Quantitative Imaging Profile ( QiPr) web application

93 lines (87 loc) 4.03 kB
define ['angular', 'niftiParser', 'ndarray'], (ng, niftiParser, ndarray) -> nifti = ng.module 'qiprofile.nifti', [] nifti.factory 'Nifti', -> # Unquoted whitespace matcher (doesn't handle escapes). WHITESPACE_REGEX = /\s+(?=([^"]*"[^"]*")*[^"]*$)/g # Parses the image file content. Returns the # {header, data}, where: # * *header* is an object {nifti, nrrd, dicom}, where *nifti* is # is the NIfTI header, NRRD is the NRRD header and *dicom* is # the embedded DICOM meta-data object # * *data* is the image binary ndarray in dimension order # [x, y, z, time], where *z* is the slice index and *time* # is the volume index. # # @buffer the binary image file ArrayBuffer # @returns the parsed image {header, data} object parse: (buffer) -> # Extract the NIfTI header. niftiHeader = niftiParser.parseNIfTIHeader(buffer) # Extract the NRRD header. nrrdHeader = niftiParser.parseNRRDHeader(buffer) # Extract the embedded DICOM metadata. extensions = niftiParser.parseHeaderExtensions(buffer) if extensions.length > 1 throw new Error("The NIfTI file has more than one extension") if extensions.length == 1 extension = extensions[0] # Note: the conventional idiom: # String.fromCharCode.apply(null, data) # results in a stack overflow. The work-around is to convert # the characters one byte at a time. chars = (String.fromCharCode(c) for c in extension.data) # Get rid of whitespace. json = chars.join('').replace(WHITESPACE_REGEX, '') # Note: JSON parse fails with message that it can't find # a JSON object. The work-around to this work-around bug is # to search for the substrings we want and hammer together # a minimal JSON string. # TODO - unravel this mess. windowCenterRegex = /("WindowCenter":\[[^\]]+\])/ windowWidthRegex = /("WindowWidth":\[[^\]]+\])/ matches = windowCenterRegex.exec(json) if matches? item1 = matches[1] matches = windowWidthRegex.exec(json) if matches? item2 = matches[1] json = '{' + [item1, item2].join(',') + '}' # Parse the JSON. dicom = JSON.parse(json) else dicom = null else dicom = null else dicom = null # Extract the image binary data. parsed = niftiParser.parse(buffer) # The binary image ndarray. Per the nifti-js site, the NIfTI # header sizes attribute is the inverse of the raw NIfTI # ndarray order, and suggests reversing the sizes in the # ndarray constructor argument below. There is (of course) # no rationale given for this. We dispense with this advice # and retain the input sizes, which results in a data shape # consistent with the input shape. # # The dcmstack layout is in [time, x, y, z] order. This differs # from the expected [x, y, z, time] order described # in, e.g., https://brainder.org/2012/09/23/the-nifti-file-format/, # We retain the input layout, which has ramifications for the # array slicing and dicing in sliceDisplay.coffee. # # Note: the js ndarray default stride differs from the expected # nibabel default stride, after accounting for the difference # in the underlying datum size (byte for nibabel, int16 for js). # Reversing the sizes as described above still results in an # incorrect stride. The work-around is to calculate the stride # by hand. # TODO - bring this up with the ndarray dev team. stride = (parsed.sizes[0...i].reduce(_.multiply, 1) for i in [0...parsed.sizes.length]) data = ndarray(parsed.data, parsed.sizes, stride) # Return the image {header, data} object. header: nifti: niftiHeader nrrd: nrrdHeader dicom: dicom data: data