Browse Source

identified some metadata to extract

master
Jim Rybarski 10 years ago
parent
commit
d0401fd8e4
2 changed files with 172 additions and 4 deletions
  1. +155
    -4
      nd2reader/__init__.py
  2. +17
    -0
      run.py

+ 155
- 4
nd2reader/__init__.py View File

@ -1,9 +1,59 @@
import array import array
import numpy as np import numpy as np
import struct import struct
from collections import namedtuple
from StringIO import StringIO
chunk = namedtuple('Chunk', ['location', 'length'])
field_of_view = namedtuple('FOV', ['x', 'y', 'z', 'pfs_offset'])
channel = namedtuple('Channel', ['name', 'camera', 'exposure_time'])
class Nd2Reader(object):
class Nd2(object):
def __init__(self, filename):
self._parser = Nd2Parser(filename)
@property
def height(self):
return self._parser.metadata['ImageAttributes']['SLxImageAttributes']['uiHeight']
@property
def width(self):
return self._parser.metadata['ImageAttributes']['SLxImageAttributes']['uiWidth']
@property
def fields_of_view(self):
for fov in self.metadata['ImageMetadata']['SLxExperiment']['ppNextLevelEx'][''][0]['uLoopPars']['Points']['']:
yield field_of_view(x=fov['dPosX'], y=fov['dPosY'], z=fov['dPosZ'], pfs_offset=fov['dPFSOffset'])
@property
def fov_count(self):
return len(list(self.fields_of_view))
@property
def channels(self):
metadata = self.metadata['ImageMetadataSeq']['SLxPictureMetadata']['sPicturePlanes']
for label, chan in metadata['sPlaneNew'].items():
name = chan['sDescription']
exposure_time = metadata['sSampleSetting'][label]['dExposureTime']
camera = metadata['sSampleSetting'][label]['pCameraSetting']['CameraUserName']
yield channel(name=name, exposure_time=exposure_time, camera=camera)
@property
def metadata(self):
return self._parser.metadata
class Nd2Parser(object):
""" """
Reads .nd2 files, provides an interface to the metadata, and generates numpy arrays from the image data. Reads .nd2 files, provides an interface to the metadata, and generates numpy arrays from the image data.
@ -13,7 +63,9 @@ class Nd2Reader(object):
self._file_handler = None self._file_handler = None
self._chunk_map_start_location = None self._chunk_map_start_location = None
self._label_map = {} self._label_map = {}
self._metadata = {}
self._read_map() self._read_map()
self._parse_dict_data()
@property @property
def fh(self): def fh(self):
@ -21,6 +73,25 @@ class Nd2Reader(object):
self._file_handler = open(self._filename, "rb") self._file_handler = open(self._filename, "rb")
return self._file_handler return self._file_handler
def _parse_dict_data(self):
# TODO: Don't like this name
for label in self._top_level_dict_labels:
chunk_location = self._label_map[label].location
data = self._read_chunk(chunk_location)
stop = label.index("LV")
self._metadata[label[:stop]] = self.read_lv_encoding(data, 1)
@property
def metadata(self):
return self._metadata
@property
def _top_level_dict_labels(self):
# TODO: I don't like this name either
for label in self._label_map.keys():
if label.endswith("LV!") or "LV|" in label:
yield label
def _read_map(self): def _read_map(self):
""" """
Every label ends with an exclamation point, however, we can't directly search for those to find all the labels Every label ends with an exclamation point, however, we can't directly search for those to find all the labels
@ -36,6 +107,7 @@ class Nd2Reader(object):
if label == "ND2 CHUNK MAP SIGNATURE 0000001!": if label == "ND2 CHUNK MAP SIGNATURE 0000001!":
# We've reached the end of the chunk map # We've reached the end of the chunk map
break break
self._label_map[label] = value self._label_map[label] = value
label_start = data_start + 16 label_start = data_start + 16
@ -65,8 +137,8 @@ class Nd2Reader(object):
""" """
key = raw_text[label_start: data_start] key = raw_text[label_start: data_start]
value = struct.unpack("QQ", raw_text[data_start: data_start + 16])
return key, value
location, length = struct.unpack("QQ", raw_text[data_start: data_start + 16])
return key, chunk(location=location, length=length)
@property @property
def chunk_map_start_location(self): def chunk_map_start_location(self):
@ -131,4 +203,83 @@ class Nd2Reader(object):
@staticmethod @staticmethod
def as_numpy_array(arr): def as_numpy_array(arr):
return np.frombuffer(arr)
return np.frombuffer(arr)
def read_lv_encoding(self, data, count):
data = StringIO(data)
res = {}
for c in range(count):
lastpos = data.tell()
hdr = data.read(2)
if not hdr:
break
typ = ord(hdr[0])
bname = data.read(2*ord(hdr[1]))
name = bname.decode("utf16")[:-1].encode("utf8")
if typ == 1:
value, = struct.unpack("B", data.read(1))
elif typ in [2, 3]:
value, = struct.unpack("I", data.read(4))
elif typ == 5:
value, = struct.unpack("Q", data.read(8))
elif typ == 6:
value, = struct.unpack("d", data.read(8))
elif typ == 8:
value = data.read(2)
while value[-2:] != "\x00\x00":
value += data.read(2)
value = value.decode("utf16")[:-1].encode("utf8")
elif typ == 9:
cnt, = struct.unpack("Q", data.read(8))
value = array.array("B", data.read(cnt))
elif typ == 11:
newcount, length = struct.unpack("<IQ", data.read(12))
length -= data.tell()-lastpos
nextdata = data.read(length)
value = self.read_lv_encoding(nextdata, newcount)
# XXX do not know for what these offsets? are
unknown = array.array("I", data.read(newcount*8))
else:
assert 0, "%s hdr %x:%x unknown" % (name, ord(hdr[0]), ord(hdr[1]))
if not name in res:
res[name] = value
else:
if type(res[name]) != type([]):
res[name] = [res[name]]
res[name].append(value)
x = data.read()
assert not x, "skip %d %s" % (len(x), repr(x[:30]))
return res
class LVLine(object):
def __init__(self, line):
self._line = line
self._extract()
def _extract(self):
if self._type == 11:
count, length = struct.unpack("<IQ", self._line[self._name_end: self._name_end + 12])
newline = LVLine(self._line[self._name_end + 12:])
@property
def name(self):
return self._line[2: self._name_end].decode("utf16").encode("utf8")
@property
def _type(self):
return ord(self._line[0])
@property
def _name_end(self):
"""
Length is given as number of characters, but since it's unicode (which is two-bytes per character) we return
twice the number.
"""
return ord(self._line[1]) * 2
class LVData(object):
def __init__(self, data):
self._extracted_data = LVLine(data)

+ 17
- 0
run.py View File

@ -0,0 +1,17 @@
from nd2reader import Nd2
from pprint import pprint
n = Nd2("/home/jim/code/nd2reader/FYLM-140804-001.nd2")
# print("Height: ", n.height)
# print("Width: ", n.width)
# for fov in n.fields_of_view:
# print(fov.x, fov.y, fov.z, fov.pfs_offset)
# pprint(n.metadata['ImageMetadataSeq']['SLxPictureMetadata']['sPicturePlanes'])
# for label, channel in n.metadata['ImageMetadataSeq']['SLxPictureMetadata']['sPicturePlanes']['sPlaneNew'].items():
# print(channel['sDescription'])
# print(n.metadata['ImageMetadataSeq']['SLxPictureMetadata']['sPicturePlanes']['sSampleSetting'][label]['dExposureTime'])
# print(n.metadata['ImageMetadataSeq']['SLxPictureMetadata']['sPicturePlanes']['sSampleSetting'][label]['pCameraSetting']['CameraUserName'])
for channel in n.channels:
print(channel)

Loading…
Cancel
Save