From 4fb7d39303bf19faa8edc59b0b44bc17a4c292d6 Mon Sep 17 00:00:00 2001 From: teogale Date: Wed, 11 Sep 2019 10:26:12 +0200 Subject: [PATCH] - start of documentation process - convert tif rgb into grayscale using .convert('l') / fonction of pillow - made change in the test_imagesequence and / test_regionofinterest (coverage 92% and 100%) --- doc/source/api_reference.rst | 2 +- doc/source/core.rst | 2 +- neo/core/__init__.py | 5 +- neo/core/imagesequence.py | 85 +++++++++++++++++++++- neo/io/asciiimageio.py | 3 +- neo/io/blkio.py | 2 - neo/io/tiffio.py | 10 ++- neo/test/coretest/test_imagesequence.py | 9 ++- neo/test/coretest/test_regionofinterest.py | 8 +- 9 files changed, 106 insertions(+), 20 deletions(-) diff --git a/doc/source/api_reference.rst b/doc/source/api_reference.rst index 6f0379a54..d88e3a81d 100644 --- a/doc/source/api_reference.rst +++ b/doc/source/api_reference.rst @@ -6,4 +6,4 @@ API Reference .. testsetup:: * from neo import SpikeTrain - import quantities as pq \ No newline at end of file + import quantities as pq diff --git a/doc/source/core.rst b/doc/source/core.rst index 28d95e8aa..26d5b4523 100644 --- a/doc/source/core.rst +++ b/doc/source/core.rst @@ -24,7 +24,7 @@ associated metadata (units, sampling frequency, etc.). * :py:class:`SpikeTrain`: A set of action potentials (spikes) emitted by the same unit in a period of time (with optional waveforms). * :py:class:`Event`: An array of time points representing one or more events in the data. * :py:class:`Epoch`: An array of time intervals representing one or more periods of time in the data. - + * :py:class:`ImageSequence`: A 3 dimension array representing multiple images. Container objects ----------------- diff --git a/neo/core/__init__.py b/neo/core/__init__.py index 8850f4b9b..2644e9424 100644 --- a/neo/core/__init__.py +++ b/neo/core/__init__.py @@ -22,6 +22,7 @@ .. autoclass:: Epoch .. autoclass:: SpikeTrain +.. autoclass:: ImageSequence """ @@ -39,11 +40,11 @@ from neo.core.event import Event from neo.core.epoch import Epoch +from neo.core.spiketrain import SpikeTrain + from neo.core.imagesequence import ImageSequence from neo.core.regionofinterest import RectangularRegionOfInterest, CircularRegionOfInterest, PolygonRegionOfInterest -from neo.core.spiketrain import SpikeTrain - # Block should always be first in this list objectlist = [Block, Segment, ChannelIndex, AnalogSignal, IrregularlySampledSignal, diff --git a/neo/core/imagesequence.py b/neo/core/imagesequence.py index c89d75384..5724f4d69 100644 --- a/neo/core/imagesequence.py +++ b/neo/core/imagesequence.py @@ -1,7 +1,26 @@ # -*- coding: utf-8 -*- +""" +This module implements :class:`ImageSequence`, a 3D array. + +:class:`ImageSequence` inherits from :class:`basesignal.BaseSignal` which +derives from :class:`BaseNeo`, and from :class:`quantites.Quantity`which +in turn inherits from :class:`numpy.array`. + +Inheritance from :class:`numpy.array` is explained here: +http://docs.scipy.org/doc/numpy/user/basics.subclassing.html + +In brief: +* Initialization of a new object from constructor happens in :meth:`__new__`. +This is where user-specified attributes are set. + +* :meth:`__array_finalize__` is called for all new objects, including those +created by slicing. This is where attributes are copied over from +the old object. + +""" + from neo.core.regionofinterest import RegionOfInterest from neo.core.analogsignal import AnalogSignal, _get_sampling_rate -from neo.core.dataobject import DataObject import quantities as pq import numpy as np @@ -10,6 +29,56 @@ class ImageSequence(BaseSignal): + """ + Array of three dimension organize as [frame][row][column]. + + Inherits from :class:`quantities.Quantity`, which in turn inherits from + :class:`numpy.ndarray`. + + *usage*:: + + >>> from neo.core import ImageSequence + >>> import quantities as pq + >>> + >>> img_sequence_array = [[[column for column in range(20)]for row in range(20)]for frame in range(10)] + >>> image_sequence = ImageSequence(img_sequence_array, units='V', + ... sampling_rate=1*pq.Hz, spatial_scale=1*pq.micrometer) + >>> image_sequence.all() + ImageSequence + + *Required attributes/properties*: + :image_data: (numpy array 3D, or list[frame][row][column] + The data itself + :units: (quantity units) + :sampling_rate: *or* **sampling_period** (quantity scalar) Number of + samples per unit time or + interval beween to samples. + If both are specified, they are + checked for consistency. + :spatial_scale: (quantity scalar) size for a pixel. + + *Recommended attributes/properties*: + :name: (str) A label for the dataset. + :description: (str) Text description. + :file_origin: (str) Filesystem path or URL of the original data file. + + *Optional attributes/properties*: + :dtype: (numpy dtype or str) Override the dtype of the signal array. + :copy: (bool) True by default. + :array_annotations: (dict) Dict mapping strings to numpy + arrays containing annotations for all data points + + Note: Any other additional arguments are assumed to be user-specific + metadata and stored in :attr:`annotations`. + + *Properties available on this object*: + :sampling_rate: (quantity scalar) Number of samples per unit time. + (1/:attr:`sampling_period`) + :sampling_period: (quantity scalar) Interval between two samples. + (1/:attr:`quantity scalar`) + :spatial_scales: size of a pixel + + """ # format ImageSequence subclass dataobject # should be a 3d numerical array # format data[image_index][y][x] @@ -32,6 +101,16 @@ def __new__(cls, image_data, units=None, dtype=None, copy=True, spatial_scale=No sampling_rate=None, name=None, description=None, file_origin=None, array_annotations=None, **annotations): + """ + Constructs new :class:`ImageSequence` from data. + + This is called whenever a new class:`ImageSequence` is created from + the constructor, but not when slicing. + + __array_finalize__ is called on the new object. + + """ + if spatial_scale is None: raise ValueError('spatial_scale is required') if units == None: @@ -50,7 +129,6 @@ def __new__(cls, image_data, units=None, dtype=None, copy=True, spatial_scale=No return obj - def __array_finalize__spec(self, obj): self.sampling_rate = getattr(obj, 'sampling_rate', None) @@ -61,11 +139,12 @@ def __array_finalize__spec(self, obj): def signal_from_region(self, *region): + if len(region) == 0: raise ValueError('no region of interest have been given') region_pixel = [] - for i,b in enumerate(region): + for i, b in enumerate(region): r = region[i].return_list_pixel() if r == []: raise ValueError('region '+str(i)+'is empty') diff --git a/neo/io/asciiimageio.py b/neo/io/asciiimageio.py index 1fffe4160..fb2ea6799 100644 --- a/neo/io/asciiimageio.py +++ b/neo/io/asciiimageio.py @@ -67,7 +67,6 @@ def read_block(self, lazy=False, nb_frame=None, nb_row=None, nb_column=None, uni data[i][y].append(liste_value[nb]) nb += 1 - image_sequence = ImageSequence(np.array(data, dtype='float'), units=units, sampling_rate=sampling_rate, spatial_scale=spatial_scale) print("creating segment") @@ -79,4 +78,4 @@ def read_block(self, lazy=False, nb_frame=None, nb_row=None, nb_column=None, uni block.segments.append(segment) print("returning block") - return block \ No newline at end of file + return block diff --git a/neo/io/blkio.py b/neo/io/blkio.py index b5df02ea6..3450c8e0f 100644 --- a/neo/io/blkio.py +++ b/neo/io/blkio.py @@ -251,5 +251,3 @@ def read_header(file_name): block.segments.append(segment) return block - - diff --git a/neo/io/tiffio.py b/neo/io/tiffio.py index c1f4d62c6..8d2267ba4 100644 --- a/neo/io/tiffio.py +++ b/neo/io/tiffio.py @@ -54,7 +54,6 @@ def natural_sort(l): return sorted(l, key=alphanum_key) # find all the images in the given directory - # (done) TODO: only load files with the extension .tif or .tiff (see the glob module) file_name_list = [] # name of extensions to track types = ["*.tif", "*.tiff"] @@ -69,7 +68,12 @@ def natural_sort(l): list_data_image = [] for file_name in file_name_list: list_data_image.append(np.array(Image.open(self.filename + "/" + file_name), dtype=np.float)) - # todo: find out if this works for colour TIFFs, not just monochrome + list_data_image = np.array(list_data_image) + if len(list_data_image.shape) == 4: + list_data_image = [] + for file_name in file_name_list: + list_data_image.append(np.array(Image.open(self.filename + "/" + file_name).convert('L'), dtype=np.float)) + print("read block") image_sequence = ImageSequence(np.stack(list_data_image), units=units, @@ -84,4 +88,4 @@ def natural_sort(l): segment.block = block block.segments.append(segment) print("returning block") - return block \ No newline at end of file + return block diff --git a/neo/test/coretest/test_imagesequence.py b/neo/test/coretest/test_imagesequence.py index 5b30af5fc..393217f7c 100644 --- a/neo/test/coretest/test_imagesequence.py +++ b/neo/test/coretest/test_imagesequence.py @@ -3,6 +3,7 @@ from neo.core.regionofinterest import CircularRegionOfInterest, RectangularRegionOfInterest, PolygonRegionOfInterest import quantities as pq import numpy as np +from neo.core import Block,Segment class TestImageSequence(unittest.TestCase): @@ -34,7 +35,7 @@ def test_error_spatial_scale(self): ImageSequence(self.data, units='V', sampling_rate=500 * pq.Hz) def test_units(self): - with self.assertRaises(TypeError): + with self.assertRaises(ValueError): ImageSequence(self.data, sampling_rate=500 * pq.Hz, spatial_scale='m') # test method will be remove are rename @@ -69,7 +70,13 @@ def test_signal_from_region(self): self.assertIsInstance(l, list) for i in range(len(l)): self.assertIsInstance(l[i], object) + with self.assertRaises(ValueError): + ImageSequence(self.data, units='V', sampling_rate=500 * pq.Hz, spatial_scale='m').signal_from_region(RectangularRegionOfInterest(1, 1, 1, 1)) + with self.assertRaises(ValueError): + ImageSequence(self.data, units='V', sampling_rate=500 * pq.Hz, spatial_scale='m').signal_from_region() + m = ImageSequence(self.data, units='V', sampling_rate=500 * pq.Hz, spatial_scale='m').view() + print(m.sampling_rate) if __name__ == "__main__": unittest.main() diff --git a/neo/test/coretest/test_regionofinterest.py b/neo/test/coretest/test_regionofinterest.py index 845517193..56fd278cd 100644 --- a/neo/test/coretest/test_regionofinterest.py +++ b/neo/test/coretest/test_regionofinterest.py @@ -14,16 +14,14 @@ def test_result(self): class Test_RectangularRegionOfInterest(unittest.TestCase): def test_result(self): - self.assertEqual(RectangularRegionOfInterest(5,5,2,2).return_list_pixel(), + self.assertEqual(RectangularRegionOfInterest(5, 5, 2, 2).return_list_pixel(), [[4, 4], [5, 4], [4, 5], [5, 5]]) - - class Test_PolygonRegionOfInterest(unittest.TestCase): def test_result(self): - self.assertEqual(PolygonRegionOfInterest((1,1),(1,4),(2,1),(4,1)).return_list_pixel(), - [(1, 1), (1, 2), (1, 3)]) + self.assertEqual(PolygonRegionOfInterest((3, 3), (2, 5), (5, 5), (5, 1), (1, 1)).return_list_pixel(), + [(1, 1), (2, 1), (3, 1), (4, 1), (2, 2), (3, 2), (4, 2), (3, 3), (4, 3), (3, 4), (4, 4)])