diff --git a/nd2reader/reader.py b/nd2reader/reader.py index fecf17f..1b1401d 100644 --- a/nd2reader/reader.py +++ b/nd2reader/reader.py @@ -13,6 +13,10 @@ field_of_view = namedtuple('FOV', ['number', 'x', 'y', 'z', 'pfs_offset']) class Nd2FileReader(object): + CHUNK_HEADER = 0xabeceda + CHUNK_MAP_START = "ND2 FILEMAP SIGNATURE NAME 0001!" + CHUNK_MAP_END = "ND2 CHUNK MAP SIGNATURE 0000001!" + """ Reads .nd2 files, provides an interface to the metadata, and generates numpy arrays from the image data. @@ -23,12 +27,17 @@ class Nd2FileReader(object): self._file_handler = None self._channel_offset = None self._chunk_map_start_location = None + self._cursor_position = None self._label_map = {} self._metadata = {} self._read_map() - self._parse_dict_data() + self._parse_metadata() self.__dimensions = None + @staticmethod + def as_numpy_array(arr): + return np.frombuffer(arr) + def get_image(self, time_index, fov, channel_name, z_level): image_set_number = self._calculate_image_set_number(time_index, fov, z_level) timestamp, raw_image_data = self.get_raw_image_data(image_set_number, self.channel_offset[channel_name]) @@ -168,14 +177,13 @@ class Nd2FileReader(object): image_data_start = 4 + channel_offset return timestamp, image_data[image_data_start::self.channel_count] - def _parse_dict_data(self): - # TODO: Don't like this name + def _parse_metadata(self): for label in self._label_map.keys(): if not label.endswith("LV!") or "LV|" in label: continue data = self._read_chunk(self._label_map[label]) stop = label.index("LV") - self._metadata[label[:stop]] = self.read_lv_encoding(data, 1) + self._metadata[label[:stop]] = self._read_file(data, 1) def _read_map(self): """ @@ -188,13 +196,13 @@ class Nd2FileReader(object): 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("ND2 FILEMAP SIGNATURE NAME 0001!") + 32 + label_start = raw_text.index(Nd2FileReader.CHUNK_MAP_START) + 32 while True: data_start = raw_text.index("!", label_start) + 1 key = raw_text[label_start: data_start] location, length = struct.unpack("QQ", raw_text[data_start: data_start + 16]) - if key == "ND2 CHUNK MAP SIGNATURE 0000001!": + if key == Nd2FileReader.CHUNK_MAP_END: # We've reached the end of the chunk map break self._label_map[key] = location @@ -209,58 +217,73 @@ class Nd2FileReader(object): # The chunk metadata is always 16 bytes long chunk_metadata = self.fh.read(16) header, relative_offset, data_length = struct.unpack("IIQ", chunk_metadata) - if header != 0xabeceda: + if header != Nd2FileReader.CHUNK_HEADER: raise ValueError("The ND2 file seems to be corrupted.") # We start at the location of the chunk metadata, skip over the metadata, and then proceed to the # start of the actual data field, which is at some arbitrary place after the metadata. self.fh.seek(chunk_location + 16 + relative_offset) return self.fh.read(data_length) - @staticmethod - def as_numpy_array(arr): - return np.frombuffer(arr) - def _z_level_count(self): name = "CustomData|Z!" st = self._read_chunk(self._label_map[name]) - res = array.array("d", st) - return len(res) - - def read_lv_encoding(self, data, count): + return len(array.array("d", st)) + + def _parse_unsigned_char(self, data): + return struct.unpack("B", data.read(1))[0] + + def _parse_unsigned_int(self, data): + return struct.unpack("I", data.read(4))[0] + + def _parse_unsigned_long(self, data): + return struct.unpack("Q", data.read(8))[0] + + def _parse_double(self, data): + return struct.unpack("d", data.read(8))[0] + + def _parse_string(self, data): + value = data.read(2) + while not value.endswith("\x00\x00"): + # the string ends at the first instance of \x00\x00 + value += data.read(2) + return value.decode("utf16")[:-1].encode("utf8") + + def _parse_char_array(self, data): + array_length = struct.unpack("Q", data.read(8))[0] + return array.array("B", data.read(array_length)) + + def _parse_metadata_item(self, args): + data, cursor_position = args + new_count, length = struct.unpack("