Browse Source

figured out timepoint counts, updated license

feature/load_slices
jim 10 years ago
parent
commit
31e53aae9f
5 changed files with 117 additions and 88 deletions
  1. +12
    -21
      LICENSE
  2. +11
    -1
      nd2reader/__init__.py
  3. +3
    -6
      nd2reader/model/__init__.py
  4. +78
    -56
      nd2reader/service/__init__.py
  5. +13
    -4
      run.py

+ 12
- 21
LICENSE View File

@ -1,21 +1,12 @@
The MIT License (MIT)
Copyright (c) 2014 Jim Rybarski
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.

+ 11
- 1
nd2reader/__init__.py View File

@ -1,5 +1,6 @@
import logging import logging
from nd2reader.service import BaseNd2 from nd2reader.service import BaseNd2
from nd2reader.model import Image
log = logging.getLogger("nd2reader") log = logging.getLogger("nd2reader")
log.addHandler(logging.StreamHandler()) log.addHandler(logging.StreamHandler())
@ -8,4 +9,13 @@ log.setLevel(logging.DEBUG)
class Nd2(BaseNd2): class Nd2(BaseNd2):
def __init__(self, filename): def __init__(self, filename):
super(Nd2, self).__init__(filename)
super(Nd2, self).__init__(filename)
def get_image(self, timepoint, fov, channel_name, z_level):
"""
Everything here is zero-indexed.
"""
image_set_number = self._calculate_image_set_number(timepoint, fov, z_level)
timestamp, raw_image_data = self._reader.get_raw_image_data(image_set_number, self.channel_offset[channel_name])
return Image(timestamp, raw_image_data, self.height, self.width)

+ 3
- 6
nd2reader/model/__init__.py View File

@ -10,9 +10,7 @@ class Channel(object):
@property @property
def name(self): def name(self):
if self._name is not None and self._name != "":
return self._name
return "UnnamedChannel"
return self._name
@property @property
def camera(self): def camera(self):
@ -48,8 +46,7 @@ class Image(object):
def __init__(self, timestamp, raw_array, height, width): def __init__(self, timestamp, raw_array, height, width):
self._timestamp = timestamp self._timestamp = timestamp
self._raw_data = raw_array self._raw_data = raw_array
self._height = height
self._width = width
self._data = np.reshape(self._raw_data, (height, width))
@property @property
def timestamp(self): def timestamp(self):
@ -58,7 +55,7 @@ class Image(object):
@property @property
def data(self): def data(self):
return np.reshape(self._raw_data, (self._height, self._width))
return self._data
def show(self): def show(self):
skimage.io.imshow(self.data) skimage.io.imshow(self.data)

+ 78
- 56
nd2reader/service/__init__.py View File

@ -7,49 +7,64 @@ import logging
from nd2reader.model import Channel, ImageSet, Image from nd2reader.model import Channel, ImageSet, Image
log = logging.getLogger("nd2reader") log = logging.getLogger("nd2reader")
log.setLevel(logging.DEBUG)
chunk = namedtuple('Chunk', ['location', 'length']) chunk = namedtuple('Chunk', ['location', 'length'])
field_of_view = namedtuple('FOV', ['number', 'x', 'y', 'z', 'pfs_offset']) field_of_view = namedtuple('FOV', ['number', 'x', 'y', 'z', 'pfs_offset'])
class BaseNd2(object): class BaseNd2(object):
def __init__(self, filename): def __init__(self, filename):
self._parser = Nd2Reader(filename)
self._reader = Nd2Reader(filename)
self._channel_offset = None
@property @property
def height(self): def height(self):
return self._parser.metadata['ImageAttributes']['SLxImageAttributes']['uiHeight']
return self._metadata['ImageAttributes']['SLxImageAttributes']['uiHeight']
@property @property
def width(self): def width(self):
return self._parser.metadata['ImageAttributes']['SLxImageAttributes']['uiWidth']
return self._metadata['ImageAttributes']['SLxImageAttributes']['uiWidth']
def _get_timepoint_count(self):
return len(self._parser.metadata['ImageEvents']['RLxExperimentRecord']['pEvents'][''])
@property
def _image_count(self):
return self._metadata['ImageAttributes']['SLxImageAttributes']['uiSequenceCount']
@property @property
def _fields_of_view(self):
"""
Fields of view are the various places in the xy-plane where images were taken.
def _sequence_count(self):
return self._metadata['ImageEvents']['RLxExperimentRecord']['uiCount']
"""
# Grab all the metadata about fields of view
fov_metadata = self._metadata['ImageMetadata']['SLxExperiment']['ppNextLevelEx']['']
# The attributes include x, y, and z coordinates, and perfect focus (PFS) offset
fov_attributes = fov_metadata['uLoopPars']['Points']['']
# If you crop fields of view from your ND2 file, the metadata is retained and only this list is
# updated to indicate that the fields of view have been deleted.
fov_validity = fov_metadata['pItemValid']
# We only yield valid (i.e. uncropped) fields of view
for number, (fov, valid) in enumerate(zip(fov_attributes, fov_validity)):
if valid:
yield field_of_view(number=number + 1,
x=fov['dPosX'],
y=fov['dPosY'],
z=fov['dPosZ'],
pfs_offset=fov['dPFSOffset'])
@property
def _timepoint_count(self):
return self._image_count / self._field_of_view_count / self._z_level_count
# @property
# def _fields_of_view(self):
# """
# Fields of view are the various places in the xy-plane where images were taken.
#
# """
# # Grab all the metadata about fields of view
# fov_metadata = self._metadata['ImageMetadata']['SLxExperiment']['ppNextLevelEx']['']
# # The attributes include x, y, and z coordinates, and perfect focus (PFS) offset
# fov_attributes = fov_metadata['uLoopPars']['Points']['']
# # If you crop fields of view from your ND2 file, the metadata is retained and only this list is
# # updated to indicate that the fields of view have been deleted.
# fov_validity = fov_metadata['pItemValid']
# # We only yield valid (i.e. uncropped) fields of view
# for number, (fov, valid) in enumerate(zip(fov_attributes, fov_validity)):
# if valid:
# yield field_of_view(number=number + 1,
# x=fov['dPosX'],
# y=fov['dPosY'],
# z=fov['dPosZ'],
# pfs_offset=fov['dPFSOffset'])
@property
def _z_level_count(self):
return self._image_count / self._sequence_count
@property @property
def _fov_count(self):
def _field_of_view_count(self):
""" """
The metadata contains information about fields of view, but it contains it even if some fields The metadata contains information about fields of view, but it contains it even if some fields
of view were cropped. We can't find anything that states which fields of view are actually of view were cropped. We can't find anything that states which fields of view are actually
@ -60,9 +75,13 @@ class BaseNd2(object):
return sum(self._metadata['ImageMetadata']['SLxExperiment']['ppNextLevelEx']['']['pItemValid']) return sum(self._metadata['ImageMetadata']['SLxExperiment']['ppNextLevelEx']['']['pItemValid'])
@property @property
def _channels(self):
def channels(self):
metadata = self._metadata['ImageMetadataSeq']['SLxPictureMetadata']['sPicturePlanes'] metadata = self._metadata['ImageMetadataSeq']['SLxPictureMetadata']['sPicturePlanes']
validity = self._metadata['ImageMetadata']['SLxExperiment']['ppNextLevelEx']['']['ppNextLevelEx']['']['pItemValid']
try:
validity = self._metadata['ImageMetadata']['SLxExperiment']['ppNextLevelEx']['']['ppNextLevelEx']['']['pItemValid']
except KeyError:
# If none of the channels have been deleted, there is no validity list, so we just make one
validity = [True for i in metadata]
# Channel information is contained in dictionaries with the keys a0, a1...an where the number # Channel information is contained in dictionaries with the keys a0, a1...an where the number
# indicates the order in which the channel is stored. So by sorting the dicts alphabetically # indicates the order in which the channel is stored. So by sorting the dicts alphabetically
# we get the correct order. # we get the correct order.
@ -75,37 +94,19 @@ class BaseNd2(object):
yield Channel(name, camera, exposure_time) yield Channel(name, camera, exposure_time)
@property @property
def _channel_count(self):
return self._metadata['ImageAttributes']["SLxImageAttributes"]["uiComp"]
# @property
# def _z_levels(self):
# for i in self._metadata['ImageMetadata']['SLxExperiment']['ppNextLevelEx']['']['ppNextLevelEx'][''].items():
# yield i
# @property
# def _z_level_count(self):
# """
# The number of different z-axis levels.
#
# """
# return 1
def channel_offset(self):
if self._channel_offset is None:
self._channel_offset = {}
for n, channel in enumerate(self.channels):
self._channel_offset[channel.name] = n
return self._channel_offset
@property @property
def _metadata(self): def _metadata(self):
return self._parser.metadata
return self._reader.metadata
def _get_image_set(self, nr):
chunk = self._parser._label_map["ImageDataSeq|%d!" % nr]
d = self._parser._read_chunk(chunk.location)
timestamp = struct.unpack("d", d[:8])[0]
image_set = ImageSet()
# The images for the various channels are interleaved within each other.
for i in range(self._channel_count):
image_data = array.array("H", d)
image = Image(timestamp, image_data[4+i::self._channel_count], self.height, self.width)
image_set.add(image)
return image_set
def _calculate_image_set_number(self, timepoint, fov, z_level):
return timepoint * self._field_of_view_count * self._z_level_count + (fov * self._z_level_count + z_level)
class Nd2Reader(object): class Nd2Reader(object):
@ -128,6 +129,20 @@ 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
@property
def channel_count(self):
return self._metadata['ImageAttributes']["SLxImageAttributes"]["uiComp"]
def get_raw_image_data(self, image_set_number, channel_offset):
chunk = self._label_map["ImageDataSeq|%d!" % image_set_number]
data = self._read_chunk(chunk.location)
timestamp = struct.unpack("d", data[:8])[0]
# The images for the various channels are interleaved within each other. Yes, this is an incredibly unintuitive and nonsensical way
# to store data.
image_data = array.array("H", data)
image_data_start = 4 + channel_offset
return timestamp, image_data[image_data_start::self.channel_count]
def _parse_dict_data(self): def _parse_dict_data(self):
# TODO: Don't like this name # TODO: Don't like this name
for label in self._top_level_dict_labels: for label in self._top_level_dict_labels:
@ -260,6 +275,15 @@ class Nd2Reader(object):
def as_numpy_array(arr): def as_numpy_array(arr):
return np.frombuffer(arr) return np.frombuffer(arr)
def _z_level_count(self):
"""read the microscope coordinates and temperatures
Missing: get chunknames and types from xml metadata"""
res = {}
name = "CustomData|Z!"
st = self._read_chunk(self._label_map[name].location)
res = array.array("d", st)
return len(res)
def read_lv_encoding(self, data, count): def read_lv_encoding(self, data, count):
data = StringIO(data) data = StringIO(data)
res = {} res = {}
@ -267,14 +291,12 @@ class Nd2Reader(object):
for c in range(count): for c in range(count):
lastpos = data.tell() lastpos = data.tell()
total_count += 1 total_count += 1
# log.debug("%s: %s" % (total_count, lastpos))
hdr = data.read(2) hdr = data.read(2)
if not hdr: if not hdr:
break break
typ = ord(hdr[0]) typ = ord(hdr[0])
bname = data.read(2*ord(hdr[1])) bname = data.read(2*ord(hdr[1]))
name = bname.decode("utf16")[:-1].encode("utf8") name = bname.decode("utf16")[:-1].encode("utf8")
log.debug(name)
if typ == 1: if typ == 1:
value, = struct.unpack("B", data.read(1)) value, = struct.unpack("B", data.read(1))
elif typ in [2, 3]: elif typ in [2, 3]:


+ 13
- 4
run.py View File

@ -3,8 +3,17 @@ from pprint import pprint
import numpy as np import numpy as np
from skimage import io from skimage import io
# n = Nd2("/home/jim/Desktop/nd2hacking/test-141111.nd2")
n = Nd2("/home/jim/Desktop/nd2hacking/YFP-dsRed-GFP-BF.nd2") n = Nd2("/home/jim/Desktop/nd2hacking/YFP-dsRed-GFP-BF.nd2")
image_set = n._get_image_set(3)
for image in image_set:
image.show()
# n = Nd2("/home/jim/Desktop/nd2hacking/test-141111.nd2")
# for chan in n.channels:
# print(chan.name)
print(n._reader.channel_count)
print(n._z_level_count)
print(n._field_of_view_count)
print(n._timepoint_count)
# z = n._reader.read_coordinates()
# print(z)
# pprint(n._metadata)
image = n._reader.get_raw_image_data(71, 0)

Loading…
Cancel
Save