@ -1,11 +0,0 @@ | |||||
.gitignore | |||||
.git | |||||
*.md | |||||
*.txt | |||||
Dockerfile | |||||
Makefile | |||||
setup.cfg | |||||
env | |||||
*.egg-info | |||||
build | |||||
dist |
@ -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,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 |
@ -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 |
@ -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() |
@ -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) |
@ -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() |
@ -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,3 +1,3 @@ | |||||
from nd2reader.main import Nd2 | |||||
from nd2reader.nd2reader import ND2Reader | |||||
__version__ = '2.1.3' | __version__ = '2.1.3' |
@ -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 |
@ -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 |