From 1d4e2242cd7ac37799d2c448f2aafa67c1326e50 Mon Sep 17 00:00:00 2001 From: John Doe Date: Wed, 12 May 2021 08:28:06 +0200 Subject: [PATCH] refactor FloatImg class method to function imported in floatimg module. add tests for file dump / load : coverage of exixting python code is 100%, yeah --- README.md | 14 +++-- floatimg/__init__.py | 5 +- floatimg/image.py | 119 +++++++++++++++++++++++++------------------ floatimg/settings.py | 4 +- tests/basics.py | 86 +++++++++++++++++++++++++++---- tests/dumps.py | 50 ++++++++++++++++++ 6 files changed, 212 insertions(+), 66 deletions(-) create mode 100644 tests/dumps.py diff --git a/README.md b/README.md index dc793d7..fe2a653 100644 --- a/README.md +++ b/README.md @@ -15,7 +15,7 @@ The file `floatimg/settings.py` contains the path to the .so file. The FloatImg class encapsulate core and file functionnalities of FloatImg library. Import if from the main module: ```python -from floatimg import FloatImg +import floatimg ``` ### Image creation @@ -23,12 +23,12 @@ from floatimg import FloatImg `FloatImg` has two class methods to create an image. ```python -img = FloatImg.create(640, 480, FloatImg.RGB) +img = floatimg.create(640, 480, floatimg.RGB) ``` Or use the `create_rgb` shortcut: ```python -img = FloatImg.create_rgb(640, 480) +img = floatimg.create_rgb(640, 480) ``` Note that calling the `destroy` method of the instance is not necessary as it will be called automatically at instance destruction. @@ -36,7 +36,11 @@ Note that calling the `destroy` method of the instance is not necessary as it wi Or completely duplicate an existing image: ```python +# without copying data new_img = img.clone() + +# with copying data +new_img = img.clone(1) ``` ### Image manipulation @@ -82,12 +86,12 @@ img.load_from_dump("/tmp/image_dump") #### Create a new image from a dump file ```python -img = FloatImg.create_from_dump("/tmp/image_dump") +img = floatimg.create_from_dump("/tmp/image_dump") ``` #### Get dump metadata ```python -witdh, height, img_type = FloatImg.fileinfos("/tmp/image_dump") +witdh, height, img_type = floatimg.fileinfos("/tmp/image_dump") ``` diff --git a/floatimg/__init__.py b/floatimg/__init__.py index 86fe2da..248f2fb 100644 --- a/floatimg/__init__.py +++ b/floatimg/__init__.py @@ -1,5 +1,6 @@ +"""Python Binding to FloatImg """ - -from floatimg.image import FloatImg +# FloatImg class and static functions +from floatimg.image import FloatImg, create, create_rgb, create_from_dump, GRAY, RGB, fileinfos __version__ = "0.0.1" \ No newline at end of file diff --git a/floatimg/image.py b/floatimg/image.py index ac9ada2..5c89386 100644 --- a/floatimg/image.py +++ b/floatimg/image.py @@ -2,8 +2,18 @@ import ctypes as ct from floatimg.settings import LIB +############################################################################################################ +# type constants +GRAY = 1 +RGB = 3 +RGBA = 4 +RGBZ = 99 + +############################################################################################################ +# Type mapping class C_FloatImg(ct.Structure): """mapping to the C structure of FloatImg""" + pass @@ -66,16 +76,31 @@ c_fimg_plot_rgb.restype = ct.c_int # int fimg_get_rgb(FloatImg *head, int x, int y, float *rgb); c_fimg_get_rgb = LIB.fimg_get_rgb -c_fimg_get_rgb.argtypes = (ct.POINTER(C_FloatImg), ct.c_int, ct.c_int, ct.POINTER(ct.c_float * 3)) +c_fimg_get_rgb.argtypes = ( + ct.POINTER(C_FloatImg), + ct.c_int, + ct.c_int, + ct.POINTER(ct.c_float * 3), +) c_fimg_get_rgb.restype = ct.c_int # int fimg_put_rgb(FloatImg *head, int x, int y, float *rgb); c_fimg_put_rgb = LIB.fimg_put_rgb -c_fimg_put_rgb.argtypes = (ct.POINTER(C_FloatImg), ct.c_int, ct.c_int, ct.POINTER(ct.c_float * 3)) +c_fimg_put_rgb.argtypes = ( + ct.POINTER(C_FloatImg), + ct.c_int, + ct.c_int, + ct.POINTER(ct.c_float * 3), +) c_fimg_put_rgb.restype = ct.c_int c_fimg_rgb_constant = LIB.fimg_rgb_constant -c_fimg_rgb_constant.argtypes = (ct.POINTER(C_FloatImg), ct.c_float, ct.c_float, ct.c_float) +c_fimg_rgb_constant.argtypes = ( + ct.POINTER(C_FloatImg), + ct.c_float, + ct.c_float, + ct.c_float, +) c_fimg_rgb_constant.restype = ct.c_int c_fimg_dump_to_file = LIB.fimg_dump_to_file @@ -100,12 +125,6 @@ class FloatImg: :attr c_img: an pointer to the c_img structure """ - # type constants - GRAY = 1 - RGB = 3 - RGBA = 4 - RGBZ = 99 - # proxy attributes to the C_FloatImg structure magic = property(lambda self: self.c_img.magic) width = property(lambda self: self.c_img.width) @@ -129,21 +148,6 @@ class FloatImg: def __str__(self): return f"<{self.__class__.__name__} instance {id(self)} width={self.width} height={self.height} type={self.str_type}>" - ####################################################################################################### - @classmethod - def create(cls, witdh, height, type_id): - """create an new FloatImg instance""" - assert cls.type_is_valid(type_id) - img = C_FloatImg() - assert c_fimgcreate(ct.pointer(img), witdh, height, type_id) == 0 - return FloatImg(img) - - ####################################################################################################### - @classmethod - def create_rgb(cls, witdh, height): - """create a new rgb instance """ - return cls.create(witdh, height, cls.RGB) - ####################################################################################################### def destroy(self): """destroy the underlying structure. automattically called at instance destruction""" @@ -169,12 +173,6 @@ class FloatImg: def copy_data(self, to_img): assert c_fimg_copy_data(self.c_img_p, to_img.c_img_p) == 0 - ####################################################################################################### - @staticmethod - def type_is_valid(type_id): - """return True if type_id is a valid one""" - return c_fimg_type_is_valid(type_id) == 1 - ####################################################################################################### @property def str_type(self): @@ -216,26 +214,49 @@ class FloatImg: def load_from_dump(self, fname): """load data from a dump. size and type have to be compatible""" assert ( - c_fimg_load_from_dump(ct.c_char_p(bytes(fname, encoding="utf8")), self.c_img_p) + c_fimg_load_from_dump( + ct.c_char_p(bytes(fname, encoding="utf8")), self.c_img_p + ) == 0 ) - ####################################################################################################### - @classmethod - def fileinfos(cls, fname): - """return witdh, height, img_type triplet read from a dump file""" - datas = (ct.c_int * 3)() - assert ( - c_fimg_fileinfos(ct.c_char_p(bytes(fname, encoding="utf8")), ct.pointer(datas)) - == 0 - ) - return datas[:3] - ####################################################################################################### - @classmethod - def create_from_dump(cls, fname): - """Create a new instance from a dump file""" - witdh, height, img_type = cls.fileinfos(fname) - img = cls.create(witdh, height, img_type) - img.load_from_dump(fname) - return img +########################################################################################################### +def fileinfos(fname): + """return witdh, height, img_type triplet read from a dump file""" + datas = (ct.c_int * 3)() + assert ( + c_fimg_fileinfos(ct.c_char_p(bytes(fname, encoding="utf8")), ct.pointer(datas)) + == 0 + ) + return datas[:3] + + +########################################################################################################### +def create(witdh, height, type_id): + """create an new FloatImg instance""" + assert type_is_valid(type_id) + img = C_FloatImg() + assert c_fimgcreate(ct.pointer(img), witdh, height, type_id) == 0 + return FloatImg(img) + + +########################################################################################################### +def create_rgb(witdh, height): + """create a new rgb instance """ + return create(witdh, height, RGB) + + +########################################################################################################### +def create_from_dump(fname): + """Create a new instance from a dump file""" + witdh, height, img_type = fileinfos(fname) + img = create(witdh, height, img_type) + img.load_from_dump(fname) + return img + + +########################################################################################################### +def type_is_valid(type_id): + """return True if type_id is a valid one""" + return c_fimg_type_is_valid(type_id) == 1 diff --git a/floatimg/settings.py b/floatimg/settings.py index 58a4e2b..8bd20f5 100644 --- a/floatimg/settings.py +++ b/floatimg/settings.py @@ -1,3 +1,5 @@ import ctypes -LIB = ctypes.cdll.LoadLibrary("../FloatImg/libfloatimg.so") +# LIB = ctypes.cdll.LoadLibrary("../FloatImg/libfloatimg.so") + +LIB = ctypes.cdll.LoadLibrary("../FloatImg4PythonBinding/build/lib/libfloatimg.so") diff --git a/tests/basics.py b/tests/basics.py index 1d4d322..c046c84 100644 --- a/tests/basics.py +++ b/tests/basics.py @@ -1,37 +1,105 @@ -from floatimg import FloatImg +"""test of core functions """ + +import floatimg def test_create(): width = 640 height = 480 - img = FloatImg.create(width, height, FloatImg.RGB) + img = floatimg.create(width, height, floatimg.RGB) assert img.width == width assert img.height == height - assert img.type_id == FloatImg.RGB + assert img.type_id == floatimg.RGB - img = FloatImg.create_rgb(width, height) + img = floatimg.create_rgb(width, height) - assert img.type_id == FloatImg.RGB + assert img.type_id == floatimg.RGB # TODO inspect RVB def test_clone(): - width = 640 - height = 480 - img = FloatImg.create(width, height, FloatImg.RGB) + width = 5 + height = 5 + img = floatimg.create(width, height, floatimg.RGB) + img.rgb_constant(127.0, 127.0, 127.0) + + # clone without copying pixel values img2 = img.clone() assert img.width == img2.width assert img.height == img2.height assert img.type_id == img2.type_id # TODO inspect RVB and do pixel per pixel comparison + for y in range(height): + for x in range(width): + assert img.get_rgb(x, y) != img2.get_rgb(x, y) + + img2 = img.clone(1) + assert img.width == img2.width + assert img.height == img2.height + assert img.type_id == img2.type_id + # TODO inspect RVB and do pixel per pixel comparison + for y in range(height): + for x in range(width): + assert img.get_rgb(x, y) == img2.get_rgb(x, y) def test_rgb_constant(): width = 5 height = 5 color = [127.0, 127.0, 127.0] - img = FloatImg.create_rgb(width, height) + img = floatimg.create_rgb(width, height) img.rgb_constant(*color) for y in range(height): for x in range(width): assert img.get_rgb(x, y) == color + + +def test_copy_data(): + width = 5 + height = 5 + color = [127.0, 127.0, 127.0] + img = floatimg.create_rgb(width, height) + img.rgb_constant(*color) + + img2 = floatimg.create_rgb(width, height) + img2.rgb_constant(64.0, 64.0, 64.0) + + img.copy_data(img2) + + for y in range(height): + for x in range(width): + assert img.get_rgb(x, y) == img2.get_rgb(x, y) + + +def test_clear(): + width, height = 5, 5 + color = [127.0, 127.0, 127.0] + img = floatimg.create_rgb(width, height) + img.rgb_constant(*color) + img.clear() + for y in range(height): + for x in range(width): + assert img.get_rgb(x, y) == [0.0, 0.0, 0.0] + + +def test_str_type(): + width, height = 5, 5 + img = floatimg.create_rgb(width, height) + assert img.str_type == "rgb" + + +def test__str__(): + width, height = 5, 5 + img = floatimg.create_rgb(width, height) + img_str = str(img) + assert " width=5 " in img_str + assert " height=5 " in img_str + assert " type=rgb>" in img_str + + +def test_put_rgb(): + width, height = 5, 5 + img = floatimg.create_rgb(width, height) + assert img.get_rgb(0, 0) == [0.0, 0.0, 0.0] + img.put_rgb(0, 0, [127.0, 127.0, 127.0]) + assert img.get_rgb(0, 0) == [127.0, 127.0, 127.0] diff --git a/tests/dumps.py b/tests/dumps.py new file mode 100644 index 0000000..32e5de0 --- /dev/null +++ b/tests/dumps.py @@ -0,0 +1,50 @@ +"""test of file dump functions """ +import os + +import floatimg + +TEST_PATH = os.path.join(os.path.dirname(__file__), "test.dump") + +# TODO refactor to fixture +def create_dump(): + width, height = 5, 5 + img = floatimg.create_rgb(width, height) + # fill it to have something to compare + img.rgb_constant(64.0, 64.0, 64.0) + img.dump_to_file(TEST_PATH) + + +def test_dump_to_file(): + create_dump() + + assert os.path.exists(TEST_PATH) + assert os.path.isfile(TEST_PATH) + assert os.path.getsize(TEST_PATH) == 320 + + os.remove(TEST_PATH) + + +def test_infos(): + create_dump() + floatimg.fileinfos(TEST_PATH) == 5, 5, floatimg.RGB + os.remove(TEST_PATH) + + +def test_load(): + create_dump() + width, height = 5, 5 + img = floatimg.create_rgb(width, height) + img.load_from_dump(TEST_PATH) + for y in range(height): + for x in range(width): + assert img.get_rgb(x, y) == [64.0, 64.0, 64.0] + os.remove(TEST_PATH) + + +def test_create(): + create_dump() + img = floatimg.create_from_dump(TEST_PATH) + for y in range(img.height): + for x in range(img.width): + assert img.get_rgb(x, y) == [64.0, 64.0, 64.0] + os.remove(TEST_PATH)