FloatImg class definition and some basic operations
This commit is contained in:
parent
c9c58dcaff
commit
f5200d6fba
108
README.md
108
README.md
@ -1,3 +1,109 @@
|
|||||||
# python-FloatImg
|
# python-FloatImg
|
||||||
|
|
||||||
A python binding to FloatImg library available at https://git.tetalab.org/tTh/FloatImg
|
A pythonesque binding to FloatImg library available at https://git.tetalab.org/tTh/FloatImg
|
||||||
|
|
||||||
|
python-FloatImg requires a quite recent Python 3 implementation (tested with 3.6.9).
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
python-FloatImg requires a shared object file (.so) that is not yet officialy available from FloatImg.
|
||||||
|
|
||||||
|
The file `floatimg/settings.py` contains the path to the .so file.
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
The FloatImg class encapsulate core and file functionnalities of FloatImg library. Import if from the main module:
|
||||||
|
|
||||||
|
```python
|
||||||
|
from floatimg import FloatImg
|
||||||
|
```
|
||||||
|
|
||||||
|
### Image creation
|
||||||
|
|
||||||
|
`FloatImg` has two class methods to create an image.
|
||||||
|
|
||||||
|
```python
|
||||||
|
img = FloatImg.create(640, 480, FloatImg.RGB)
|
||||||
|
```
|
||||||
|
Or use the `create_rgb` shortcut:
|
||||||
|
|
||||||
|
```python
|
||||||
|
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.
|
||||||
|
|
||||||
|
Or completely duplicate an existing image:
|
||||||
|
|
||||||
|
```python
|
||||||
|
new_img = img.clone()
|
||||||
|
```
|
||||||
|
|
||||||
|
### Image manipulation
|
||||||
|
|
||||||
|
#### Clear image
|
||||||
|
|
||||||
|
```python
|
||||||
|
img.clear()
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Copy pixels to another image
|
||||||
|
|
||||||
|
```python
|
||||||
|
img.copy_data(another_img)
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Set pixel value
|
||||||
|
|
||||||
|
```python
|
||||||
|
img.put_rgb(x, y, (r, g, b))
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Get pixel value
|
||||||
|
|
||||||
|
```python
|
||||||
|
r, g, b = img.get_rgb(x, y)
|
||||||
|
```
|
||||||
|
|
||||||
|
### File manipulation
|
||||||
|
|
||||||
|
#### Dump image to file
|
||||||
|
|
||||||
|
```python
|
||||||
|
img.dump_to_file("/tmp/image_dump")
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Restore image data from a dump file
|
||||||
|
|
||||||
|
```python
|
||||||
|
img.load_from_dump("/tmp/image_dump")
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Create a new image from a dump file
|
||||||
|
|
||||||
|
```python
|
||||||
|
img = FloatImg.create_from_dump("/tmp/image_dump")
|
||||||
|
```
|
||||||
|
#### Get dump metadata
|
||||||
|
|
||||||
|
```python
|
||||||
|
witdh, height, img_type = FloatImg.fileinfos("/tmp/image_dump")
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
## Development
|
||||||
|
|
||||||
|
### Run tests
|
||||||
|
|
||||||
|
Install development requirements (you may add --user if not in a virtual environment or not in system-wide install):
|
||||||
|
|
||||||
|
```
|
||||||
|
pip install -r requirements-devel.txt
|
||||||
|
```
|
||||||
|
|
||||||
|
Ensure `floatimg` containing path is in the `PYTHONPATH`, this will run the tests and show a coverage report:
|
||||||
|
|
||||||
|
```
|
||||||
|
pytest --cov=floatimg tests/*
|
||||||
|
```
|
||||||
|
|
||||||
|
5
floatimg/__init__.py
Normal file
5
floatimg/__init__.py
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
|
||||||
|
|
||||||
|
from floatimg.image import FloatImg
|
||||||
|
|
||||||
|
__version__ = "0.0.1"
|
241
floatimg/image.py
Normal file
241
floatimg/image.py
Normal file
@ -0,0 +1,241 @@
|
|||||||
|
import ctypes as ct
|
||||||
|
|
||||||
|
from floatimg.settings import LIB
|
||||||
|
|
||||||
|
class C_FloatImg(ct.Structure):
|
||||||
|
"""mapping to the C structure of FloatImg"""
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
C_FloatImg._fields_ = [
|
||||||
|
("magic", ct.c_ulong),
|
||||||
|
("width", ct.c_int),
|
||||||
|
("height", ct.c_int),
|
||||||
|
("type", ct.c_int),
|
||||||
|
("fval", ct.c_float),
|
||||||
|
("count", ct.c_int),
|
||||||
|
("R", ct.POINTER(ct.c_float)),
|
||||||
|
("G", ct.POINTER(ct.c_float)),
|
||||||
|
("B", ct.POINTER(ct.c_float)),
|
||||||
|
("A", ct.POINTER(ct.c_float)),
|
||||||
|
("reserved", ct.c_int),
|
||||||
|
]
|
||||||
|
|
||||||
|
############################################################################################################
|
||||||
|
# declaration of input / output types
|
||||||
|
c_fimgcreate = LIB.fimg_create
|
||||||
|
c_fimgcreate.argtypes = (ct.POINTER(C_FloatImg), ct.c_int, ct.c_int, ct.c_int)
|
||||||
|
c_fimgcreate.restype = ct.c_int
|
||||||
|
|
||||||
|
c_fimg_destroy = LIB.fimg_destroy
|
||||||
|
c_fimg_destroy.argtypes = (ct.POINTER(C_FloatImg),)
|
||||||
|
c_fimg_destroy.restype = ct.c_int
|
||||||
|
|
||||||
|
c_fimg_clone = LIB.fimg_clone
|
||||||
|
c_fimg_clone.argtypes = (ct.POINTER(C_FloatImg), ct.POINTER(C_FloatImg))
|
||||||
|
c_fimg_clone.restype = ct.c_int
|
||||||
|
|
||||||
|
c_fimg_copy_data = LIB.fimg_copy_data
|
||||||
|
c_fimg_copy_data.argtypes = (ct.POINTER(C_FloatImg), ct.POINTER(C_FloatImg))
|
||||||
|
c_fimg_copy_data.restype = ct.c_int
|
||||||
|
|
||||||
|
c_fimg_type_is_valid = LIB.fimg_type_is_valid
|
||||||
|
c_fimg_type_is_valid.argtypes = (ct.c_int,)
|
||||||
|
c_fimg_copy_data.restype = ct.c_int
|
||||||
|
|
||||||
|
c_fimg_str_type = LIB.fimg_str_type
|
||||||
|
c_fimg_str_type.argtypes = (ct.c_int,)
|
||||||
|
c_fimg_str_type.restype = ct.c_char_p
|
||||||
|
|
||||||
|
c_fimg_clear = LIB.fimg_clear
|
||||||
|
c_fimg_clear.argtypes = (ct.POINTER(C_FloatImg),)
|
||||||
|
c_fimg_clear.restype = ct.c_int
|
||||||
|
|
||||||
|
|
||||||
|
c_fimg_plot_rgb = LIB.fimg_plot_rgb
|
||||||
|
c_fimg_plot_rgb.argtypes = (
|
||||||
|
ct.POINTER(C_FloatImg),
|
||||||
|
ct.c_int,
|
||||||
|
ct.c_int,
|
||||||
|
ct.c_float,
|
||||||
|
ct.c_float,
|
||||||
|
ct.c_float,
|
||||||
|
)
|
||||||
|
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.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.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.restype = ct.c_int
|
||||||
|
|
||||||
|
c_fimg_dump_to_file = LIB.fimg_dump_to_file
|
||||||
|
c_fimg_dump_to_file.argtypes = (ct.POINTER(C_FloatImg), ct.c_char_p, ct.c_int)
|
||||||
|
c_fimg_dump_to_file.restype = ct.c_int
|
||||||
|
|
||||||
|
c_fimg_load_from_dump = LIB.fimg_load_from_dump
|
||||||
|
c_fimg_load_from_dump.argtypes = (ct.c_char_p, ct.POINTER(C_FloatImg))
|
||||||
|
c_fimg_load_from_dump.restype = ct.c_int
|
||||||
|
|
||||||
|
c_fimg_fileinfos = LIB.fimg_fileinfos
|
||||||
|
c_fimg_fileinfos.argtypes = (ct.c_char_p, ct.POINTER(ct.c_int * 3))
|
||||||
|
c_fimg_fileinfos.restype = ct.c_int
|
||||||
|
|
||||||
|
############################################################################################################
|
||||||
|
class FloatImg:
|
||||||
|
"""
|
||||||
|
|
||||||
|
Pythonic Object-Oriented encapsulation of floatimg library
|
||||||
|
|
||||||
|
:attr c_img: an instance of C_FloatImg structure
|
||||||
|
: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)
|
||||||
|
height = property(lambda self: self.c_img.height)
|
||||||
|
type_id = property(lambda self: self.c_img.type)
|
||||||
|
fval = property(lambda self: self.c_img.fval)
|
||||||
|
count = property(lambda self: self.c_img.count)
|
||||||
|
|
||||||
|
# oh yeah, really really sluggish access to pixels img.R[0].contents
|
||||||
|
# however, pixel data are not designed to be accessed this way
|
||||||
|
R = property(
|
||||||
|
lambda self: pointer(self.c_img.contents.R)[: self.width * self.height]
|
||||||
|
)
|
||||||
|
|
||||||
|
#######################################################################################################
|
||||||
|
def __init__(self, c_img):
|
||||||
|
self.c_img = c_img
|
||||||
|
self.c_img_p = ct.pointer(c_img)
|
||||||
|
|
||||||
|
#######################################################################################################
|
||||||
|
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"""
|
||||||
|
assert c_fimg_destroy(self.c_img) == 0
|
||||||
|
|
||||||
|
#######################################################################################################
|
||||||
|
def __del__(self):
|
||||||
|
self.destroy()
|
||||||
|
|
||||||
|
#######################################################################################################
|
||||||
|
def clear(self):
|
||||||
|
"""clear data"""
|
||||||
|
return c_fimg_clear(self.c_img_p)
|
||||||
|
|
||||||
|
#######################################################################################################
|
||||||
|
def clone(self, flags=0):
|
||||||
|
"""return a clone of the current instance"""
|
||||||
|
new_pic = C_FloatImg()
|
||||||
|
assert c_fimg_clone(self.c_img_p, ct.pointer(new_pic), flags) == 0
|
||||||
|
return FloatImg(new_pic)
|
||||||
|
|
||||||
|
#######################################################################################################
|
||||||
|
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):
|
||||||
|
"""return the type of the image as a string"""
|
||||||
|
return c_fimg_str_type(self.c_img.type).decode("utf-8")
|
||||||
|
|
||||||
|
#######################################################################################################
|
||||||
|
def rgb_constant(self, r, v, b):
|
||||||
|
assert c_fimg_rgb_constant(self.c_img_p, r, v, b) == 0
|
||||||
|
|
||||||
|
#######################################################################################################
|
||||||
|
def get_rgb(self, x, y):
|
||||||
|
"""get r,g,b triplet from a pixel"""
|
||||||
|
rgb = (ct.c_float * 3)()
|
||||||
|
assert c_fimg_get_rgb(self.c_img_p, x, y, ct.pointer(rgb)) == 0
|
||||||
|
return rgb[:3]
|
||||||
|
|
||||||
|
#######################################################################################################
|
||||||
|
def put_rgb(self, x, y, rgb):
|
||||||
|
"""put r,g,b triplet to a pixel"""
|
||||||
|
# TODO may be a better way to create the array rather than iterating
|
||||||
|
c_rgb = (ct.c_float * 3)()
|
||||||
|
for i, c in enumerate(rgb):
|
||||||
|
c_rgb[i] = c
|
||||||
|
assert c_fimg_put_rgb(self.c_img_p, x, y, ct.pointer(c_rgb)) == 0
|
||||||
|
|
||||||
|
#######################################################################################################
|
||||||
|
def dump_to_file(self, fname):
|
||||||
|
"""save data to a dump file"""
|
||||||
|
# TODO use system encoding instead of utf-8
|
||||||
|
assert (
|
||||||
|
c_fimg_dump_to_file(
|
||||||
|
self.c_img_p, ct.c_char_p(bytes(fname, encoding="utf8")), 0
|
||||||
|
)
|
||||||
|
== 0
|
||||||
|
)
|
||||||
|
|
||||||
|
#######################################################################################################
|
||||||
|
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)
|
||||||
|
== 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
|
0
floatimg/operators.c
Normal file
0
floatimg/operators.c
Normal file
3
floatimg/settings.py
Normal file
3
floatimg/settings.py
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
import ctypes
|
||||||
|
|
||||||
|
LIB = ctypes.cdll.LoadLibrary("../FloatImg/libfloatimg.so")
|
2
requirements-devel.txt
Normal file
2
requirements-devel.txt
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
pytest
|
||||||
|
pytest-cov
|
37
tests/basics.py
Normal file
37
tests/basics.py
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
from floatimg import FloatImg
|
||||||
|
|
||||||
|
|
||||||
|
def test_create():
|
||||||
|
width = 640
|
||||||
|
height = 480
|
||||||
|
img = FloatImg.create(width, height, FloatImg.RGB)
|
||||||
|
assert img.width == width
|
||||||
|
assert img.height == height
|
||||||
|
assert img.type_id == FloatImg.RGB
|
||||||
|
|
||||||
|
img = FloatImg.create_rgb(width, height)
|
||||||
|
|
||||||
|
assert img.type_id == FloatImg.RGB
|
||||||
|
# TODO inspect RVB
|
||||||
|
|
||||||
|
|
||||||
|
def test_clone():
|
||||||
|
width = 640
|
||||||
|
height = 480
|
||||||
|
img = FloatImg.create(width, height, FloatImg.RGB)
|
||||||
|
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
|
||||||
|
|
||||||
|
|
||||||
|
def test_rgb_constant():
|
||||||
|
width = 5
|
||||||
|
height = 5
|
||||||
|
color = [127.0, 127.0, 127.0]
|
||||||
|
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
|
Loading…
Reference in New Issue
Block a user