Browse Source

Merge pull request #124 from jimrybarski/123-expose-label-map

123 expose label map
master
Jim Rybarski 9 years ago
parent
commit
947c7c136e
4 changed files with 169 additions and 30 deletions
  1. +7
    -0
      functional_tests/single.py
  2. +1
    -1
      nd2reader/driver/v3.py
  3. +120
    -0
      nd2reader/model/label.py
  4. +41
    -29
      nd2reader/parser/v3.py

+ 7
- 0
functional_tests/single.py View File

@ -25,6 +25,13 @@ class FunctionalTests(unittest.TestCase):
def test_length(self):
self.assertEqual(len(self.nd2), 1)
def test_actual_length(self):
count = 0
for image in self.nd2:
if image is not None:
count += 1
self.assertEqual(len(self.nd2), count)
def test_frames(self):
self.assertEqual(len(self.nd2.frames), 1)


+ 1
- 1
nd2reader/driver/v3.py View File

@ -130,7 +130,7 @@ class V3Driver(object):
:raises: NoImageError
"""
chunk = self._label_map[six.b("ImageDataSeq|%d!" % image_group_number)]
chunk = self._label_map.get_image_data_location(image_group_number)
data = read_chunk(self._file_handle, chunk)
# All images in the same image group share the same timestamp! So if you have complicated image data,
# your timestamps may not be entirely accurate. Practically speaking though, they'll only be off by a few


+ 120
- 0
nd2reader/model/label.py View File

@ -0,0 +1,120 @@
import six
import struct
import re
class LabelMap(object):
"""
"""
def __init__(self, raw_binary_data):
self._data = raw_binary_data
self._image_data = {}
def _get_location(self, label):
try:
label_location = self._data.index(label) + len(label)
return self._parse_data_location(label_location)
except ValueError:
return None
def _parse_data_location(self, label_location):
location, length = struct.unpack("QQ", self._data[label_location: label_location + 16])
return location
@property
def image_text_info(self):
return self._get_location(six.b("ImageTextInfoLV!"))
@property
def image_metadata(self):
return self._get_location(six.b("ImageMetadataLV!"))
@property
def image_metadata_sequence(self):
# there is always only one of these, even though it has a pipe followed by a zero, which is how they do indexes
return self._get_location(six.b("ImageMetadataSeqLV|0!"))
def get_image_data_location(self, index):
if not self._image_data:
regex = re.compile(six.b("""ImageDataSeq\|(\d+)!"""))
for match in regex.finditer(self._data):
if match:
location = self._parse_data_location(match.end())
self._image_data[int(match.group(1))] = location
return self._image_data[index]
@property
def image_calibration(self):
return self._get_location(six.b("ImageCalibrationLV|0!"))
@property
def image_attributes(self):
return self._get_location(six.b("ImageAttributesLV!"))
@property
def x_data(self):
return self._get_location(six.b("CustomData|X!"))
@property
def y_data(self):
return self._get_location(six.b("CustomData|Y!"))
@property
def z_data(self):
return self._get_location(six.b("CustomData|Z!"))
@property
def roi_metadata(self):
return self._get_location(six.b("CustomData|RoiMetadata_v1!"))
@property
def pfs_status(self):
return self._get_location(six.b("CustomData|PFS_STATUS!"))
@property
def pfs_offset(self):
return self._get_location(six.b("CustomData|PFS_OFFSET!"))
@property
def guid(self):
return self._get_location(six.b("CustomData|GUIDStore!"))
@property
def description(self):
return self._get_location(six.b("CustomData|CustomDescriptionV1_0!"))
@property
def camera_exposure_time(self):
return self._get_location(six.b("CustomData|Camera_ExposureTime1!"))
@property
def camera_temp(self):
return self._get_location(six.b("CustomData|CameraTemp1!"))
@property
def acquisition_times(self):
return self._get_location(six.b("CustomData|AcqTimesCache!"))
@property
def acquisition_times_2(self):
return self._get_location(six.b("CustomData|AcqTimes2Cache!"))
@property
def acquisition_frames(self):
return self._get_location(six.b("CustomData|AcqFramesCache!"))
@property
def lut_data(self):
return self._get_location(six.b("CustomDataVar|LUTDataV1_0!"))
@property
def grabber_settings(self):
return self._get_location(six.b("CustomDataVar|GrabberCameraSettingsV1_0!"))
@property
def custom_data(self):
return self._get_location(six.b("CustomDataVar|CustomDataV2_0!"))
@property
def app_info(self):
return self._get_location(six.b("CustomDataVar|AppInfo_V1_0!"))

+ 41
- 29
nd2reader/parser/v3.py View File

@ -3,6 +3,7 @@
import array
from datetime import datetime
from nd2reader.model.metadata import Metadata
from nd2reader.model.label import LabelMap
from nd2reader.parser.base import BaseParser
from nd2reader.driver.v3 import V3Driver
from nd2reader.common.v3 import read_chunk
@ -40,21 +41,44 @@ class V3Parser(BaseParser):
def driver(self):
return V3Driver(self.metadata, self._label_map, self._fh)
def _build_metadata_dict(self):
self._label_map = self._build_label_map()
raw_data = {"image_text_info": read_chunk(self._fh, self._label_map.image_text_info),
"image_metadata_sequence": read_chunk(self._fh, self._label_map.image_metadata_sequence),
# "image_data": read_chunk(self._fh, self._label_map.image_data),
"image_calibration": read_chunk(self._fh, self._label_map.image_calibration),
"image_attributes": read_chunk(self._fh, self._label_map.image_attributes),
# "x_data": read_chunk(self._fh, self._label_map.x_data),
# "y_data": read_chunk(self._fh, self._label_map.y_data),
# "z_data": read_chunk(self._fh, self._label_map.z_data),
# "roi_metadata": read_chunk(self._fh, self._label_map.roi_metadata),
# "pfs_status": read_chunk(self._fh, self._label_map.pfs_status),
# "pfs_offset": read_chunk(self._fh, self._label_map.pfs_offset),
# "guid": read_chunk(self._fh, self._label_map.guid),
# "description": read_chunk(self._fh, self._label_map.description),
# "camera_exposure_time": read_chunk(self._fh, self._label_map.camera_exposure_time),
# "camera_temp": read_chunk(self._fh, self._label_map.camera_temp),
# "acquisition_times": read_chunk(self._fh, self._label_map.acquisition_times),
# "acquisition_times_2": read_chunk(self._fh, self._label_map.acquisition_times_2),
# "acquisition_frames": read_chunk(self._fh, self._label_map.acquisition_frames),
# "lut_data": read_chunk(self._fh, self._label_map.lut_data),
# "grabber_settings": read_chunk(self._fh, self._label_map.grabber_settings),
# "custom_data": read_chunk(self._fh, self._label_map.custom_data),
# "app_info": read_chunk(self._fh, self._label_map.app_info)
}
if self._label_map.image_metadata:
raw_data["image_metadata"] = read_chunk(self._fh, self._label_map.image_metadata)
return {key: self._read_metadata(data, 1) for key, data in raw_data.items()}
def _parse_metadata(self):
"""
Reads all metadata and instantiates the Metadata object.
"""
metadata_dict = {}
self._label_map = self._build_label_map()
for label in self._label_map.keys():
if label.endswith(six.b("LV!")) or six.b("LV|") in label:
data = read_chunk(self._fh, self._label_map[label])
stop = label.index(six.b("LV"))
metadata_dict[label[:stop]] = self._read_metadata(data, 1)
height = metadata_dict[six.b('ImageAttributes')][six.b('SLxImageAttributes')][six.b('uiHeight')]
width = metadata_dict[six.b('ImageAttributes')][six.b('SLxImageAttributes')][six.b('uiWidth')]
metadata_dict = self._build_metadata_dict()
height = metadata_dict['image_attributes'][six.b('SLxImageAttributes')][six.b('uiHeight')]
width = metadata_dict['image_attributes'][six.b('SLxImageAttributes')][six.b('uiWidth')]
channels = self._parse_channels(metadata_dict)
date = self._parse_date(metadata_dict)
fields_of_view = self._parse_fields_of_view(metadata_dict)
@ -71,7 +95,7 @@ class V3Parser(BaseParser):
:rtype: datetime.datetime() or None
"""
for line in metadata_dict[six.b('ImageTextInfo')][six.b('SLxImageTextInfo')].values():
for line in metadata_dict['image_text_info'][six.b('SLxImageTextInfo')].values():
line = line.decode("utf8")
absolute_start_12 = None
absolute_start_24 = None
@ -99,9 +123,9 @@ class V3Parser(BaseParser):
"""
channels = []
metadata = metadata_dict[six.b('ImageMetadataSeq')][six.b('SLxPictureMetadata')][six.b('sPicturePlanes')]
metadata = metadata_dict['image_metadata_sequence'][six.b('SLxPictureMetadata')][six.b('sPicturePlanes')]
try:
validity = metadata_dict[six.b('ImageMetadata')][six.b('SLxExperiment')][six.b('ppNextLevelEx')][six.b('')][0][six.b('ppNextLevelEx')][six.b('')][0][six.b('pItemValid')]
validity = metadata_dict['image_metadata'][six.b('SLxExperiment')][six.b('ppNextLevelEx')][six.b('')][0][six.b('ppNextLevelEx')][six.b('')][0][six.b('pItemValid')]
except KeyError:
# If none of the channels have been deleted, there is no validity list, so we just make one
validity = [True for _ in metadata]
@ -157,7 +181,7 @@ class V3Parser(BaseParser):
:rtype: str
"""
for line in metadata_dict[six.b('ImageTextInfo')][six.b('SLxImageTextInfo')].values():
for line in metadata_dict['image_text_info'][six.b('SLxImageTextInfo')].values():
if six.b("Dimensions:") in line:
metadata = line
break
@ -197,7 +221,7 @@ class V3Parser(BaseParser):
:rtype: int
"""
return metadata_dict[six.b('ImageAttributes')][six.b('SLxImageAttributes')][six.b('uiSequenceCount')]
return metadata_dict['image_attributes'][six.b('SLxImageAttributes')][six.b('uiSequenceCount')]
def _build_label_map(self):
"""
@ -205,26 +229,14 @@ class V3Parser(BaseParser):
as some of the bytes contain the value 33, which is the ASCII code for "!". So we iteratively find each label,
grab the subsequent data (always 16 bytes long), advance to the next label and repeat.
:rtype: dict
:rtype: LabelMap
"""
label_map = {}
self._fh.seek(-8, 2)
chunk_map_start_location = struct.unpack("Q", self._fh.read(8))[0]
self._fh.seek(chunk_map_start_location)
raw_text = self._fh.read(-1)
label_start = raw_text.index(V3Parser.CHUNK_MAP_START) + 32
while True:
data_start = raw_text.index(six.b("!"), label_start) + 1
key = raw_text[label_start: data_start]
location, length = struct.unpack("QQ", raw_text[data_start: data_start + 16])
if key == V3Parser.CHUNK_MAP_END:
# We've reached the end of the chunk map
break
label_map[key] = location
label_start = data_start + 16
return label_map
return LabelMap(raw_text)
def _parse_unsigned_char(self, data):
return struct.unpack("B", data.read(1))[0]


Loading…
Cancel
Save