diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..cdd0791 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,16 @@ +## [1.1.0] - 2015-06-03 +### ADDED +- Indexing and slicing of images +- Python 3 support +- Dockerfile support for Python 3.4 +- Makefile commands for convenient testing in Docker +- Unit tests + +### CHANGED +- Switched to setuptools to automatically install missing dependencies +- Made the interface for most metadata public +- Refactored some poorly-named things + +## [1.0.0] - 2015-05-23 +### Added +- First stable release! diff --git a/Dockerfile b/Dockerfile index 8f656a9..969c5b1 100644 --- a/Dockerfile +++ b/Dockerfile @@ -3,14 +3,17 @@ MAINTAINER Jim Rybarski RUN mkdir -p /var/nds2 RUN apt-get update && apt-get install -y --no-install-recommends \ - python-numpy \ - python3-numpy \ + build-essential \ + libatlas3-base \ + liblapack-dev \ + libblas-dev \ + python \ + python3 \ + python-dev \ + python3-dev \ python-pip \ python3-pip -RUN pip install six -RUN pip3 install six - COPY . /opt/nd2reader WORKDIR /opt/nd2reader RUN python setup.py install diff --git a/Makefile b/Makefile index 87b2d38..8a6a193 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,4 @@ -.PHONY: build shell +.PHONY: build py2shell py3shell test build: docker build -t jimrybarski/nd2reader . diff --git a/README.md b/README.md index d17653f..fa0dc03 100644 --- a/README.md +++ b/README.md @@ -10,16 +10,19 @@ ### Installation -Just use pip (`numpy` is required): +Dependencies will automatically be installed if you don't have them. That said, for optimal performance, you should +install the following packages before installing nd2reader: -`pip install numpy nd2reader` +#### Ubuntu +`apt-get install python-numpy python-six` (Python 2.x) +`apt-get install python3-numpy python3-six` (Python 3.x) -If you want to install via git, clone the repo and run: +#### Other operating systems +These have not been tested yet. -``` -pip install numpy -python setup.py install -``` +nd2reader is compatible with both Python 2.x and 3.x. I recommend installing using pip: + +`pip install nd2reader` (Python 2.x) or `pip3 install nd2reader` (Python 3.x) ### ND2s @@ -37,9 +40,58 @@ Fields of View: 8 Z-Levels: 3 ``` -### Simple Iteration +You can also get some metadata about the nd2 programatically: -For most cases, you'll just want to iterate over each image: +```python +>>> nd2.height +1280 +>>> nd2.width +800 +>>> len(nd2) +30528 +``` + +### Images + +Every method returns an `Image` object, which contains some metadata about the image as well as the +raw pixel data itself. Images are always a 16-bit grayscale image. The `data` attribute holds the numpy array +with the image data: + +```python +>>> image = nd2[20] +>>> print(image.data) +array([[1894, 1949, 1941, ..., 2104, 2135, 2114], + [1825, 1846, 1848, ..., 1994, 2149, 2064], + [1909, 1820, 1821, ..., 1995, 1952, 2062], + ..., + [3487, 3512, 3594, ..., 3603, 3643, 3492], + [3642, 3475, 3525, ..., 3712, 3682, 3609], + [3687, 3777, 3738, ..., 3784, 3870, 4008]], dtype=uint16) +``` + +You can get a quick summary of image data by examining the `Image` object: + +```python +>>> image + +1280x800 (HxW) +Timestamp: 1699.79478134 +Field of View: 2 +Channel: GFP +Z-Level: 1 +``` + +Or you can access it programmatically: + +```python +image = nd2[0] +print(image.timestamp) +print(image.field_of_view) +print(image.channel) +print(image.z_level) +``` + +Often, you may want to just iterate over each image: ```python import nd2reader @@ -48,10 +100,42 @@ for image in nd2: do_something(image.data) ``` +You can also get an image directly by indexing. Here, we look at the 38th image: + +```python +>>> nd2[37] + +1280x800 (HxW) +Timestamp: 1699.79478134 +Field of View: 2 +Channel: GFP +Z-Level: 1 +``` + +Slicing is also supported and is extremely memory efficient, as images are only read when directly accessed: + +```python +my_subset = nd2[50:433] +for image in my_subset: + do_something(image.data) +``` + +Step sizes are also accepted: + +```python +for image in nd2[:100:2]: + # gets every other image in the first 100 images + do_something(image.data) + +for image in nd2[::-1]: + # iterate backwards over every image, if you're into that kind of thing + do_something_image.data) +``` + ### Image Sets If you have complicated hierarchical data, it may be easier to use image sets, which groups images together if they -share the same time index and field of view: +share the same time index (not timestamp!) and field of view: ```python import nd2reader @@ -59,55 +143,32 @@ nd2 = nd2reader.Nd2("/path/to/my_complicated_images.nd2") for image_set in nd2.image_sets: # you can select images by channel gfp_image = image_set.get("GFP") - do_something_gfp_related(gfp_image) + do_something_gfp_related(gfp_image.data) # you can also specify the z-level. this defaults to 0 if not given out_of_focus_image = image_set.get("Bright Field", z_level=1) - do_something_out_of_focus_related(out_of_focus_image) + do_something_out_of_focus_related(out_of_focus_image.data) ``` -### Direct Image Access - -There is a method, `get_image`, which allows random access to images. This might not always return an image, however, -if you acquired different numbers of images in each cycle of a program. For example, if you acquire GFP images every -other minute, but acquire bright field images every minute, `get_image` will return `None` at certain time indexes. +To get an image from an image set, you must specify a channel. It defaults to the 0th z-level, so if you have +more than one z-level you will need to specify it when using `get`: -### Images +```python +image = image_set.get("YFP") +image = image_set.get("YFP", z_level=2) +``` -`Image` objects provide several pieces of useful data. +You can also see how many images are in your image set: ```python ->>> import nd2reader ->>> nd2 = nd2reader.Nd2("/path/to/my_images.nd2") ->>> image = nd2.get_image(14, 2, "GFP", 1) ->>> image.data -array([[1809, 1783, 1830, ..., 1923, 1920, 1914], - [1687, 1855, 1792, ..., 1986, 1903, 1889], - [1758, 1901, 1849, ..., 1911, 2010, 1954], - ..., - [3363, 3370, 3570, ..., 3565, 3601, 3459], - [3480, 3428, 3328, ..., 3542, 3461, 3575], - [3497, 3666, 3635, ..., 3817, 3867, 3779]]) ->>> image.channel -'GFP' ->>> image.timestamp -1699.7947813408175 ->>> image.field_of_view -2 ->>> image.z_level -1 - -# You can also get a quick summary of image data: - ->>> image - -1280x800 (HxW) -Timestamp: 1699.79478134 -Field of View: 2 -Channel: GFP -Z-Level: 1 +>>> len(image_set) +7 ``` +### Protips + +nd2reader is about 14 times faster under Python 3.4 compared to Python 2.7. If you know why, please get in touch! + ### Bug Reports and Features If this fails to work exactly as expected, please open a Github issue. If you get an unhandled exception, please diff --git a/nd2reader/__init__.py b/nd2reader/__init__.py index 979f5ec..2337034 100644 --- a/nd2reader/__init__.py +++ b/nd2reader/__init__.py @@ -32,7 +32,7 @@ class Nd2(Nd2Parser): :rtype: int """ - return self._total_images_per_channel * self._channel_count + return self._total_images_per_channel * len(self.channels) def __getitem__(self, item): """ @@ -128,8 +128,7 @@ class Nd2(Nd2Parser): def get_image(self, time_index, field_of_view, channel_name, z_level): """ Returns an Image if data exists for the given parameters, otherwise returns None. In general, you should avoid - using this method unless you're very familiar with the structure of ND2 files. If you have a use case that - cannot be met by the `__iter__` or `image_sets` methods above, please create an issue on Github. + using this method unless you're very familiar with the structure of ND2 files. :param time_index: the frame number :type time_index: int diff --git a/requirements.txt b/requirements.txt index ed8da63..b8e18f9 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,2 +1,2 @@ numpy>=1.9.2 -six +six>=1.4 diff --git a/setup.py b/setup.py index ee88e1d..ee1b087 100644 --- a/setup.py +++ b/setup.py @@ -1,10 +1,14 @@ -from distutils.core import setup +from setuptools import setup VERSION = "1.1.0" setup( name="nd2reader", packages=['nd2reader', 'nd2reader.model'], + install_requires=[ + 'numpy>=1.6.2, <2.0', + 'six>=1.4, <2.0' + ], version=VERSION, description='A tool for reading ND2 files produced by NIS Elements', author='Jim Rybarski',