Browse Source

Start porting to PIMS framework, cleanup unnecessary files

feature/load_slices
Ruben Verweij 8 years ago
parent
commit
7b7bf1ac25
16 changed files with 79 additions and 766 deletions
  1. +0
    -11
      .dockerignore
  2. +0
    -79
      CHANGELOG.md
  3. +1
    -17
      CONTRIBUTING.md
  4. +0
    -44
      Dockerfile
  5. +0
    -37
      Makefile
  6. +9
    -114
      README.md
  7. +0
    -7
      ftest.py
  8. +0
    -240
      functional_tests/FYLM141111001.py
  9. +0
    -0
      functional_tests/__init__.py
  10. +0
    -146
      functional_tests/monocycle.py
  11. +0
    -68
      functional_tests/single.py
  12. +1
    -1
      nd2reader/__init__.py
  13. +0
    -0
      nd2reader/legacy.py
  14. +64
    -0
      nd2reader/nd2reader.py
  15. +2
    -1
      requirements.txt
  16. +2
    -1
      setup.py

+ 0
- 11
.dockerignore View File

@ -1,11 +0,0 @@
.gitignore
.git
*.md
*.txt
Dockerfile
Makefile
setup.cfg
env
*.egg-info
build
dist

+ 0
- 79
CHANGELOG.md View File

@ -1,79 +0,0 @@
## [2.1.1] - 2016-02-15
### FIXED
- `Image` objects behave properly when passed to numpy functions. Things like `np.mean` will now return scalar values as expected, instead of `Image` objects
## [2.1.0] - 2016-01-16
### ADDED
- `select` now supports `start` and `stop` keyword arguments to put bounds on images
## [2.0.2] - 2016-01-06
### ADDED
- `Nd2.pixel_microns` gives the width of a pixel
## [2.0.1] - 2016-01-06
### FIXED
- Channel name parsing issue
- `select` method works for files with a single frame
## [2.0.0] - 2015-12-20
### ADDED
- `select()` method to rapidly iterate over a subset of images matching certain criteria
- We parse metadata relating to the physical camera used to produce the images
- Raw metadata can be accessed conveniently, to allow contributors to find more interesting things to add
- An XML parsing library was added since the raw metadata contains some XML blocks
- The version number is now available in the nd2reader module
- Created a DOI to allow citation of the code
### FIXED
- Channel names were not always being parsed properly
### REMOVED
- The `ImageGroup` container object
- The `data` attribute on Images. Images now inherit from ndarray, making this redundant
- The `image_sets` iterator
## [1.1.4] - 2015-10-27
### FIXED
- Implemented missing get_image_by_attributes method
## [1.1.3] - 2015-10-16
### FIXED
- ND2s with a single image can now be parsed
## [1.1.2] - 2015-10-09
### ADDED
- `Image` objects now have a `frame_number` attribute.
- `Nd2` can be used as a context manager
- More unit tests and functional tests
### CHANGED
- `Image` objects now directly subclass Numpy arrays
- Refactored code to permit parsing of different versions of ND2s, which will allow us to add support for NIS Elements 3.x.
### DEPRECATED
- The `data` attribute is no longer needed since `Image` is now a Numpy array
- The `image_sets` iterator will be removed in the near future. You should implement this yourself
## [1.1.1] - 2015-09-02
### FIXED
- Images returned by indexing would sometimes be skipped when the file contained multiple channels
### CHANGED
- Dockerfile now installs scikit-image to make visual testing possible
## [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!

+ 1
- 17
CONTRIBUTING.md View File

@ -15,20 +15,4 @@ system so everyone can benefit, but for now, they will just be manually run by t
## Contributing Your ND2 files ## Contributing Your ND2 files
We always appreciate more ND2s, as they help us find corner cases. Please get in touch using any of the means listed at the top of this page if you'd like to send one in.
## Docker and Makefile Commands
A Dockerfile is included to allow testing in a consistent environment. `make build` will build the image for you. Due to the large number of packages that it installs, it often
fails due to a problem with the Debian servers - just try again if this happens. Once that's complete, you can run `make py2` or `make py3` to enter into a Python interpreter in
the container to test things out manually. This assumes you have the directory `~/nd2s` - any ND2 files placed there will be available in the container at `/var/nds2`. You can
view images with scikit-image with something like the code below:
```
from nd2reader import Nd2
from skimage import io
n = Nd2("/var/nd2s/my.nd2")
io.imshow(n[37])
io.show()
```
We always appreciate more ND2s, as they help us find corner cases. Please get in touch using any of the means listed at the top of this page if you'd like to send one in.

+ 0
- 44
Dockerfile View File

@ -1,44 +0,0 @@
# This is just for functional testing. We install scikit-image just as a convenient way to view images. Many other
# packages could easily accomplish this.
FROM debian:latest
MAINTAINER Jim Rybarski <jim@rybarski.com>
RUN mkdir -p /var/nds2
RUN apt-get update && apt-get install -y --no-install-recommends \
build-essential \
liblapack-dev \
libblas-dev \
libatlas3-base \
python \
python3 \
python-dev \
python3-dev \
python-pip \
python3-pip \
python-numpy \
python3-numpy \
libfreetype6-dev \
python-matplotlib \
python3-matplotlib \
libfreetype6-dev \
libpng-dev \
libjpeg-dev \
pkg-config \
python-skimage \
python3-skimage \
tk \
tk-dev \
python-tk \
python3-tk \
&& pip install -U \
cython \
scikit-image \
xmltodict \
&& pip3 install -U \
cython \
scikit-image \
xmltodict \
&& rm -rf /var/lib/apt/lists/*
WORKDIR /opt/nd2reader

+ 0
- 37
Makefile View File

@ -1,37 +0,0 @@
.PHONY: info build shell py2 py3 test ftest publish
info:
@echo ""
@echo "Available Make Commands"
@echo ""
@echo "build: builds the image"
@echo "shell: starts a bash shell in the container
@echo "py2: maps ~/Documents/nd2s to /var/nd2s and runs a Python 2.7 interpreter"
@echo "py3: maps ~/Documents/nd2s to /var/nd2s and runs a Python 3.4 interpreter"
@echo "test: runs all unit tests (in Python 3.4)"
@echo "ftest: runs all functional tests (requires specific ND2 files that are not publicly available"
@echo "publish: publishes the code base to PyPI (maintainers only)"
@echo ""
build:
docker build -t jimrybarski/nd2reader .
shell:
xhost local:root; docker run --rm -v $(CURDIR):/opt/nd2reader -v ~/nd2s:/var/nd2s -v /tmp/.X11-unix:/tmp/.X11-unix -e DISPLAY=unix$(DISPLAY) -it jimrybarski/nd2reader bash
py2:
xhost local:root; docker run --rm -v $(CURDIR):/opt/nd2reader -v ~/nd2s:/var/nd2s -v /tmp/.X11-unix:/tmp/.X11-unix -e DISPLAY=unix$(DISPLAY) -it jimrybarski/nd2reader python2.7
py3:
xhost local:root; docker run --rm -v $(CURDIR):/opt/nd2reader -v ~/nd2s:/var/nd2s -v /tmp/.X11-unix:/tmp/.X11-unix -e DISPLAY=unix$(DISPLAY) -it jimrybarski/nd2reader python3.4
test: build
docker run --rm -v $(CURDIR):/opt/nd2reader -it jimrybarski/nd2reader python3.4 test.py
docker run --rm -v $(CURDIR):/opt/nd2reader -it jimrybarski/nd2reader python2.7 test.py
ftest: build
xhost local:root; docker run --rm -v /tmp/.X11-unix:/tmp/.X11-unix -e DISPLAY=unix$(DISPLAY) -v $(CURDIR):/opt/nd2reader -v ~/nd2s:/var/nd2s -it jimrybarski/nd2reader python3.4 /opt/nd2reader/ftest.py
xhost local:root; docker run --rm -v /tmp/.X11-unix:/tmp/.X11-unix -e DISPLAY=unix$(DISPLAY) -v $(CURDIR):/opt/nd2reader -v ~/nd2s:/var/nd2s -it jimrybarski/nd2reader python2.7 /opt/nd2reader/ftest.py
publish:
python setup.py sdist upload -r pypi

+ 9
- 114
README.md View File

@ -3,121 +3,23 @@
### About ### About
`nd2reader` is a pure-Python package that reads images produced by NIS Elements 4.0+. It has only been definitively tested on NIS Elements 4.30.02 Build 1053. Support for older versions is being actively worked on. `nd2reader` is a pure-Python package that reads images produced by NIS Elements 4.0+. It has only been definitively tested on NIS Elements 4.30.02 Build 1053. Support for older versions is being actively worked on.
.nd2 files contain images and metadata, which can be split along multiple dimensions: time, fields of view (xy-plane), focus (z-plane), and filter channel.
`nd2reader` loads images as Numpy arrays, which makes it trivial to use with the image analysis packages such as `scikit-image` and `OpenCV`.
The reader is written in the [pims](https://github.com/soft-matter/pims) framework, enabling easy access to multidimensional files, lazy slicing, and nice display in IPython.
### Installation ### Installation
If you don't already have the packages `numpy`, `six` and `xmltodict`, they will be installed automatically:
`pip3 install nd2reader` for Python 3.x
`pip install nd2reader` for Python 2.x
If you don't already have the packages `numpy`, `pims`, `six` and `xmltodict`, they will be installed automatically if you use the `setup.py` script.
`nd2reader` is an order of magnitude faster in Python 3. I recommend using it unless you have no other choice. `nd2reader` is an order of magnitude faster in Python 3. I recommend using it unless you have no other choice.
### ND2s ### ND2s
A quick summary of ND2 metadata can be obtained as shown below.
```python
>>> import nd2reader
>>> nd2 = nd2reader.Nd2("/path/to/my_images.nd2")
>>> nd2
<ND2 /path/to/my_images.nd2>
Created: 2014-11-11 15:59:19
Image size: 1280x800 (HxW)
Image cycles: 636
Channels: 'brightfield', 'GFP'
Fields of View: 8
Z-Levels: 3
```
You can iterate over each image in the order they were acquired:
```python
import nd2reader
nd2 = nd2reader.Nd2("/path/to/my_images.nd2")
for image in nd2:
do_something(image)
```
`Image` objects are just Numpy arrays with some extra metadata bolted on:
```python
>>> image = nd2[20]
>>> image
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)
>>> image.timestamp
10.1241241248
>>> image.frame_number
11
>>> image.field_of_view
6
>>> image.channel
'GFP'
>>> image.z_level
0
```
If you only want to view images that meet certain criteria, you can use `select()`. It's much faster than iterating
and checking attributes of images manually. You can specify scalars or lists of values. Criteria that aren't specified
default to every possible value. Currently, slicing and selecting can't be done at the same time, but you can
set a range with the `start` and `stop` arguments:
```python
for image in nd2.select(channels="GFP", fields_of_view=(1, 2, 7)):
# gets all GFP images in fields of view 1, 2 and 7, regardless of z-level or frame
do_something(image)
for image in nd2.select(z_levels=(0, 1), start=12, stop=3000):
# gets images of any channel or field of view, with z-level 0 or 1, between images 12 and 3000
do_something(image)
```
Slicing is also supported and is extremely memory efficient, as images are only read when directly accessed:
```python
for image in nd2[50:433]:
do_something(image)
# get every other image in the first 100 images
for image in nd2[:100:2]:
do_something(image)
# iterate backwards over every image
for image in nd2[::-1]:
do_something(image)
```
You can also just index a single image:
`nd2reader` follows the [pims](https://github.com/soft-matter/pims) framework. To open a file:
```python ```python
# gets the 18th image
my_important_image = nd2[17]
from nd2reader import ND2Reader
images = ND2Reader('my_directory/example.nd2')
``` ```
The `Nd2` object has some programmatically-accessible metadata:
```python
>>> nd2.height # in pixels
1280
>>> nd2.width # in pixels
800
>>> len(nd2) # the number of images
30528
>>> nd2.pixel_microns # the width of a pixel in microns
0.22
```
After opening the file, all `pims` features are supported. Please refer to the [documentation](http://soft-matter.github.io/pims/).
### Contributing ### Contributing
@ -129,15 +31,8 @@ for more information.
If this fails to work exactly as expected, please open an [issue](https://github.com/rbnvrw/nd2reader/issues). If this fails to work exactly as expected, please open an [issue](https://github.com/rbnvrw/nd2reader/issues).
If you get an unhandled exception, please paste the entire stack trace into the issue as well. If you get an unhandled exception, please paste the entire stack trace into the issue as well.
### Citation
You can cite nd2reader in your research if you want:
```
Rybarski, Jim (2015): nd2reader. figshare.
http://dx.doi.org/10.6084/m9.figshare.1619960
```
### Acknowledgments ### Acknowledgments
Original version by Jim Rybarski. Support for the development of this package was provided by the [Finkelstein Laboratory](http://finkelsteinlab.org/).
PIMS modified version by Ruben Verweij.
Original version by Jim Rybarski. Support for the development of this package was partially provided by the [Finkelstein Laboratory](http://finkelsteinlab.org/).

+ 0
- 7
ftest.py View File

@ -1,7 +0,0 @@
import unittest
from functional_tests.FYLM141111001 import FYLM141111Tests
from functional_tests.single import SingleTests
from functional_tests.monocycle import Monocycle1Tests, Monocycle2Tests, OneTests
if __name__ == '__main__':
unittest.main()

+ 0
- 240
functional_tests/FYLM141111001.py View File

@ -1,240 +0,0 @@
"""
These tests require that you have a specific ND2 file created by the developer of nd2reader. You will never need to
run them unless you're Jim Rybarski.
"""
from nd2reader import Nd2
from skimage import io
import numpy as np
from datetime import datetime
import unittest
import time
class FYLM141111Tests(unittest.TestCase):
def setUp(self):
self.nd2 = Nd2("/var/nd2s/FYLM-141111-001.nd2")
def tearDown(self):
self.nd2.close()
def test_shape(self):
self.assertEqual(self.nd2.height, 1280)
self.assertEqual(self.nd2.width, 800)
def test_date(self):
self.assertEqual(self.nd2.date, datetime(2014, 11, 11, 15, 59, 19))
@unittest.skip("This will fail until we address issue #59")
def test_length(self):
self.assertEqual(len(self.nd2), 17808)
def test_frames(self):
self.assertEqual(len(self.nd2.frames), 636)
def test_fovs(self):
self.assertEqual(len(self.nd2.fields_of_view), 8)
def test_channels(self):
self.assertTupleEqual(tuple(sorted(self.nd2.channels)), ('', 'GFP'))
def test_z_levels(self):
self.assertTupleEqual(tuple(self.nd2.z_levels), (0, 1, 2))
def test_pixel_size(self):
self.assertGreater(self.nd2.pixel_microns, 0.0)
def test_image(self):
image = self.nd2[14]
self.assertEqual(image.field_of_view, 2)
self.assertEqual(image.frame_number, 0)
self.assertAlmostEqual(image.timestamp, 19.0340758)
self.assertEqual(image.channel, '')
self.assertEqual(image.z_level, 1)
self.assertEqual(image.height, self.nd2.height)
self.assertEqual(image.width, self.nd2.width)
def test_last_image(self):
image = self.nd2[30526]
self.assertEqual(image.frame_number, 635)
def test_bad_image(self):
image = self.nd2[13]
self.assertIsNone(image)
def test_iteration(self):
images = [image for image in self.nd2[:10]]
self.assertEqual(len(images), 10)
def test_iteration_step(self):
images = [image for image in self.nd2[:10:2]]
self.assertEqual(len(images), 5)
def test_iteration_backwards(self):
images = [image for image in self.nd2[:10:-1]]
self.assertEqual(len(images), 10)
def test_get_image_by_attribute_ok(self):
image = self.nd2.get_image(4, 0, 'GFP', 1)
self.assertIsNotNone(image)
image = self.nd2.get_image(4, 0, '', 0)
self.assertIsNotNone(image)
image = self.nd2.get_image(4, 0, '', 1)
self.assertIsNotNone(image)
def test_images(self):
self.assertTupleEqual((self.nd2[0].z_level, self.nd2[0].channel), (0, ''))
self.assertIsNone(self.nd2[1])
self.assertTupleEqual((self.nd2[2].z_level, self.nd2[2].channel), (1, ''))
self.assertTupleEqual((self.nd2[3].z_level, self.nd2[3].channel), (1, 'GFP'))
self.assertTupleEqual((self.nd2[4].z_level, self.nd2[4].channel), (2, ''))
self.assertIsNone(self.nd2[5])
self.assertTupleEqual((self.nd2[6].z_level, self.nd2[6].channel), (0, ''))
self.assertIsNone(self.nd2[7])
self.assertTupleEqual((self.nd2[8].z_level, self.nd2[8].channel), (1, ''))
self.assertTupleEqual((self.nd2[9].z_level, self.nd2[9].channel), (1, 'GFP'))
self.assertTupleEqual((self.nd2[10].z_level, self.nd2[10].channel), (2, ''))
self.assertIsNone(self.nd2[11])
self.assertTupleEqual((self.nd2[12].z_level, self.nd2[12].channel), (0, ''))
self.assertIsNone(self.nd2[13])
self.assertTupleEqual((self.nd2[14].z_level, self.nd2[14].channel), (1, ''))
self.assertTupleEqual((self.nd2[15].z_level, self.nd2[15].channel), (1, 'GFP'))
self.assertTupleEqual((self.nd2[16].z_level, self.nd2[16].channel), (2, ''))
self.assertIsNone(self.nd2[17])
self.assertTupleEqual((self.nd2[18].z_level, self.nd2[18].channel), (0, ''))
self.assertIsNone(self.nd2[19])
self.assertIsNone(self.nd2[47])
self.assertTupleEqual((self.nd2[48].z_level, self.nd2[48].channel), (0, ''))
self.assertIsNone(self.nd2[49])
self.assertTupleEqual((self.nd2[50].z_level, self.nd2[50].channel), (1, ''))
self.assertIsNone(self.nd2[51])
self.assertTupleEqual((self.nd2[52].z_level, self.nd2[52].channel), (2, ''))
self.assertIsNone(self.nd2[53])
self.assertTupleEqual((self.nd2[54].z_level, self.nd2[54].channel), (0, ''))
def test_get_image_by_attribute_none(self):
# Should handle missing images without an exception
image = self.nd2.get_image(4, 0, 'GFP', 0)
self.assertIsNone(image)
def test_index(self):
# Do indexes get added to images properly?
for n, image in enumerate(self.nd2):
if image is not None:
self.assertEqual(n, image.index)
if n > 50:
break
def test_select(self):
# If we take the first 20 GFP images, they should be identical to the first 20 items iterated from select()
# if we set our criteria to just 'GFP'
manual_images = []
for _, image in zip(range(20), self.nd2):
if image is not None and image.channel == 'GFP':
manual_images.append(image)
filter_images = []
for image in self.nd2.select(channels='GFP'):
filter_images.append(image)
if len(filter_images) == len(manual_images):
break
self.assertEqual(len(manual_images), len(filter_images))
self.assertGreater(len(manual_images), 0)
for a, b in zip(manual_images, filter_images):
self.assertTrue(np.array_equal(a, b))
self.assertEqual(a.index, b.index)
self.assertEqual(a.field_of_view, b.field_of_view)
self.assertEqual(a.channel, b.channel)
def test_select_order_all(self):
# If we select every possible image using select(), we should just get every image in order
n = 0
for image in self.nd2.select(channels=['', 'GFP'], z_levels=[0, 1, 2], fields_of_view=list(range(8))):
while True:
indexed_image = self.nd2[n]
if indexed_image is not None:
break
n += 1
self.assertTrue(np.array_equal(image, indexed_image))
n += 1
if n > 100:
break
def test_select_order_subset(self):
# Test that images are always yielded in increasing order. This guarantees that no matter what subset of images
# we're filtering, we still get them in the chronological order they were acquired
n = -1
for image in self.nd2.select(channels='', z_levels=[0, 1], fields_of_view=[1, 2, 4]):
self.assertGreater(image.index, n)
self.assertEqual(image.channel, '')
self.assertIn(image.field_of_view, (1, 2, 4))
self.assertIn(image.z_level, (0, 1))
n = image.index
if n > 100:
break
def test_select_start(self):
count = 0
for _ in self.nd2.select(channels='GFP', start=29000):
count += 1
self.assertEqual(127, count)
def test_select_stop(self):
count = 0
for _ in self.nd2.select(channels='GFP', stop=20):
count += 1
self.assertEqual(count, 3)
def test_select_start_stop(self):
count = 0
for _ in self.nd2.select(channels='GFP', start=10, stop=20):
count += 1
self.assertEqual(count, 1)
def test_select_start_stop_brightfield(self):
count = 0
for _ in self.nd2.select(channels='', start=10, stop=20):
count += 1
self.assertEqual(count, 5)
def test_select_faster(self):
select_count = 0
select_start = time.time()
for i in self.nd2.select(channels='GFP', start=10, stop=50):
if i is not None and i.channel == 'GFP':
select_count += 1
select_duration = time.time() - select_start
direct_count = 0
direct_start = time.time()
for i in self.nd2[10:50]:
if i is not None and i.channel == 'GFP':
direct_count += 1
direct_duration = time.time() - direct_start
self.assertEqual(select_count, direct_count)
self.assertGreater(direct_duration, select_duration)
def test_pixel_microns(self):
self.assertEqual(round(self.nd2.pixel_microns, 2), 0.22)
def test_numpy_operations(self):
# just to make sure we can do this kind of thing and get scalars
self.assertTrue(0 < np.mean(self.nd2[0]) < np.sum(self.nd2[0]))
def test_numpy_mean(self):
# make sure we get the right value and type
expected_mean = 17513.053581054686
mean = np.mean(self.nd2[0])
self.assertEqual(type(mean), np.float64)
self.assertAlmostEqual(expected_mean, mean)
def test_subtract_images(self):
# just to prove we can really treat Image like an array
diff = self.nd2[0] - self.nd2[2]
self.assertTrue(np.any(diff))
def test_show(self):
io.imshow(self.nd2[0])
io.show()
self.assertTrue(True)

+ 0
- 0
functional_tests/__init__.py View File


+ 0
- 146
functional_tests/monocycle.py View File

@ -1,146 +0,0 @@
"""
Tests on ND2s that have 1 or 2 cycles only. This is unlike the ND2s I work with typically, which are all done over very long periods of time.
"""
from nd2reader import Nd2
import numpy as np
import unittest
class Monocycle1Tests(unittest.TestCase):
def setUp(self):
self.nd2 = Nd2("/var/nd2s/simone1.nd2")
def tearDown(self):
self.nd2.close()
def test_channels(self):
self.assertListEqual(self.nd2.channels, ['Cy3Narrow', 'TxRed-modified', 'FITC', 'DAPI'])
def test_pixel_size(self):
self.assertGreater(self.nd2.pixel_microns, 0.0)
def test_select(self):
manual_images = []
for _, image in zip(range(20), self.nd2):
if image is not None and image.channel == 'FITC':
manual_images.append(image)
filter_images = []
for image in self.nd2.select(channels='FITC'):
filter_images.append(image)
if len(filter_images) == len(manual_images):
break
self.assertEqual(len(manual_images), len(filter_images))
self.assertGreater(len(manual_images), 0)
for a, b in zip(manual_images, filter_images):
self.assertTrue(np.array_equal(a, b))
self.assertEqual(a.index, b.index)
self.assertEqual(a.field_of_view, b.field_of_view)
self.assertEqual(a.channel, b.channel)
def test_select_order_all(self):
# If we select every possible image using select(), we should just get every image in order
n = 0
for image in self.nd2.select(channels=['Cy3Narrow', 'DAPI', 'FITC', 'TxRed-modified'],
z_levels=list(range(35)),
fields_of_view=list(range(5))):
while True:
indexed_image = self.nd2[n]
if indexed_image is not None:
break
n += 1
self.assertTrue(np.array_equal(image, indexed_image))
n += 1
if n > 100:
# Quit after the first hundred images just to save time.
# If there's a problem, we'll have seen it by now.
break
def test_select_order_subset(self):
# Test that images are always yielded in increasing order. This guarantees that no matter what subset of images
# we're filtering, we still get them in the chronological order they were acquired
n = -1
for image in self.nd2.select(channels='FITC',
z_levels=[0, 1],
fields_of_view=[1, 2, 4]):
self.assertGreater(image.index, n)
self.assertEqual(image.channel, 'FITC')
self.assertIn(image.field_of_view, (1, 2, 4))
self.assertIn(image.z_level, (0, 1))
n = image.index
if n > 100:
break
class Monocycle2Tests(unittest.TestCase):
def setUp(self):
self.nd2 = Nd2("/var/nd2s/hawkjo.nd2")
def tearDown(self):
self.nd2.close()
def test_pixel_size(self):
self.assertGreater(round(self.nd2.pixel_microns, 2), 0.26)
def test_select(self):
manual_images = []
for _, image in zip(range(20), self.nd2):
if image is not None and image.channel == 'HHQ 500 LP 1':
manual_images.append(image)
filter_images = []
for image in self.nd2.select(channels='HHQ 500 LP 1'):
filter_images.append(image)
if len(filter_images) == len(manual_images):
break
self.assertEqual(len(manual_images), len(filter_images))
self.assertGreater(len(manual_images), 0)
for a, b in zip(manual_images, filter_images):
self.assertTrue(np.array_equal(a, b))
self.assertEqual(a.index, b.index)
self.assertEqual(a.field_of_view, b.field_of_view)
self.assertEqual(a.channel, b.channel)
def test_select_order_all(self):
# If we select every possible image using select(), we should just get every image in order
n = 0
for image in self.nd2.select(channels=['HHQ 500 LP 1', 'HHQ 500 LP 2'],
z_levels=[0],
fields_of_view=list(range(100))):
while True:
indexed_image = self.nd2[n]
if indexed_image is not None:
break
n += 1
self.assertTrue(np.array_equal(image, indexed_image))
n += 1
if n > 100:
# Quit after the first hundred images just to save time.
# If there's a problem, we'll have seen it by now.
break
def test_select_order_subset(self):
# Test that images are always yielded in increasing order. This guarantees that no matter what subset of images
# we're filtering, we still get them in the chronological order they were acquired
n = -1
for image in self.nd2.select(channels='HHQ 500 LP 2',
z_levels=[0],
fields_of_view=[1, 2, 4]):
self.assertGreater(image.index, n)
self.assertEqual(image.channel, 'HHQ 500 LP 2')
self.assertIn(image.field_of_view, (1, 2, 4))
self.assertEqual(image.z_level, 0)
n = image.index
if n > 100:
break
class OneTests(unittest.TestCase):
def test_opens(self):
# just testing that this doesn't throw an exception
nd2 = Nd2("/var/nd2s/001.nd2")
self.assertIsNotNone(nd2)
nd2.close()

+ 0
- 68
functional_tests/single.py View File

@ -1,68 +0,0 @@
"""
These tests require that you have a specific ND2 file created by the developer of nd2reader. You will never need to
run them unless you're Jim Rybarski.
"""
from nd2reader import Nd2
from datetime import datetime
import unittest
class SingleTests(unittest.TestCase):
def setUp(self):
self.nd2 = Nd2("/var/nd2s/single.nd2")
def tearDown(self):
self.nd2.close()
def test_shape(self):
self.assertEqual(self.nd2.height, 512)
self.assertEqual(self.nd2.width, 512)
def test_date(self):
self.assertEqual(self.nd2.date, datetime(2015, 10, 15, 9, 33, 5))
def test_length(self):
self.assertEqual(len(self.nd2), 1)
def test_channels(self):
self.assertEqual(self.nd2.channels, ['Quad Band 2'])
def test_pixel_size(self):
self.assertGreater(self.nd2.pixel_microns, 0.0)
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)
def test_fovs(self):
self.assertEqual(len(self.nd2.fields_of_view), 1)
def test_z_levels(self):
self.assertTupleEqual(tuple(self.nd2.z_levels), (0,))
def test_image(self):
image = self.nd2[0]
self.assertIsNotNone(image)
def test_iteration(self):
images = [image for image in self.nd2]
self.assertEqual(len(images), 1)
def test_iteration_step(self):
images = [image for image in self.nd2[::2]]
self.assertEqual(len(images), 1)
def test_iteration_backwards(self):
images = [image for image in self.nd2[::-1]]
self.assertEqual(len(images), 1)
def test_select_bounds_wrong(self):
images = [i for i in self.nd2.select(start=0, stop=12481247)]
self.assertEqual(len(images), 1)

+ 1
- 1
nd2reader/__init__.py View File

@ -1,3 +1,3 @@
from nd2reader.main import Nd2
from nd2reader.nd2reader import ND2Reader
__version__ = '2.1.3' __version__ = '2.1.3'

nd2reader/main.py → nd2reader/legacy.py View File


+ 64
- 0
nd2reader/nd2reader.py View File

@ -0,0 +1,64 @@
from pims import FramesSequenceND, Frame
import numpy as np
from nd2reader.exc import NoImageError
from nd2reader.parser import get_parser
from nd2reader.version import get_version
import six
class ND2Reader(FramesSequenceND):
"""
PIMS wrapper for the ND2 reader
"""
def __init__(self, filename):
self.filename = filename
# first use the parser to parse the file
self._fh = open(filename, "rb")
major_version, minor_version = get_version(self._fh)
self._parser = get_parser(self._fh, major_version, minor_version)
self._metadata = self._parser.metadata
self._roi_metadata = self._parser.roi_metadata
# Set data type
bit_depth = self._parser.raw_metadata.image_attributes[six.b('SLxImageAttributes')][six.b('uiBpcInMemory')]
if bit_depth <= 16:
self._dtype = np.float16
elif bit_depth <= 32:
self._dtype = np.float32
else:
self._dtype = np.float64
# Setup the axes
self._init_axis('x', self._metadata.width)
self._init_axis('y', self._metadata.height)
self._init_axis('c', len(self._metadata.channels))
self._init_axis('t', len(self._metadata.frames))
self._init_axis('z', len(self._metadata.z_levels))
def close(self):
if self._fh is not None:
self._fh.close()
def get_frame_2D(self, c, t, z):
"""
Gets a given frame using the parser
:param c:
:param t:
:param z:
:return:
"""
c_name = self._metadata.channels[c]
try:
image = self._parser.driver.get_image_by_attributes(t, 0, c_name, z, self._metadata.width,
self._metadata.height)
except (TypeError, NoImageError):
return Frame([])
else:
return Frame(image, frame_no=image.frame_number)
@property
def pixel_type(self):
return self._dtype

+ 2
- 1
requirements.txt View File

@ -1,3 +1,4 @@
numpy>=1.9.2 numpy>=1.9.2
six>=1.4 six>=1.4
xmltodict>=0.9.2
xmltodict>=0.9.2
pims>=0.3.0

+ 2
- 1
setup.py View File

@ -9,7 +9,8 @@ if __name__ == '__main__':
install_requires=[ install_requires=[
'numpy>=1.6.2, <2.0', 'numpy>=1.6.2, <2.0',
'six>=1.4, <2.0', 'six>=1.4, <2.0',
'xmltodict>=0.9.2, <1.0'
'xmltodict>=0.9.2, <1.0',
'pims>=0.3.0'
], ],
version=VERSION, version=VERSION,
description='A tool for reading ND2 files produced by NIS Elements', description='A tool for reading ND2 files produced by NIS Elements',


Loading…
Cancel
Save