Compare commits
8 Commits
1d4e2242cd
...
develop
| Author | SHA1 | Date | |
|---|---|---|---|
| 66b1c098f5 | |||
| 2342a9e2a8 | |||
| 4b57a41705 | |||
| fbea9488f0 | |||
| 95c1e6d9c6 | |||
| eaf039a83f | |||
| e57d63f562 | |||
| 6fccc1b6cf |
117
README.md
117
README.md
@@ -2,22 +2,28 @@
|
|||||||
|
|
||||||
A pythonesque 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).
|
python-FloatImg requires a Python 3 implementation (tested with 3.6.9).
|
||||||
|
|
||||||
|
Most function are wrapped into a similar api, sometime with a more consistent naming and an object-oriented approach.
|
||||||
|
|
||||||
|
Color values are specified using triplets of float values, like (128.5, 34.234, 242.23).
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
|
|
||||||
python-FloatImg requires a shared object file (.so) that is not yet officialy available from FloatImg.
|
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.
|
The file `floatimg/settings.py` contains the path to the .so file and the encoding of path characters.
|
||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
|
|
||||||
The FloatImg class encapsulate core and file functionnalities of FloatImg library. Import if from the main module:
|
|
||||||
|
|
||||||
```python
|
```python
|
||||||
import floatimg
|
import floatimg
|
||||||
```
|
```
|
||||||
|
|
||||||
|
The FloatImg class encapsulate core and file functionnalities of FloatImg library. Instances are created with functions from the module.
|
||||||
|
|
||||||
|
Colors are tuple of three elements `(r, g, b)`.
|
||||||
|
|
||||||
### Image creation
|
### Image creation
|
||||||
|
|
||||||
`FloatImg` has two class methods to create an image.
|
`FloatImg` has two class methods to create an image.
|
||||||
@@ -40,61 +46,101 @@ Or completely duplicate an existing image:
|
|||||||
new_img = img.clone()
|
new_img = img.clone()
|
||||||
|
|
||||||
# with copying data
|
# with copying data
|
||||||
new_img = img.clone(1)
|
new_img = img.clone(True)
|
||||||
```
|
```
|
||||||
|
|
||||||
### Image manipulation
|
### Basic pixel operations
|
||||||
|
|
||||||
#### Clear image
|
|
||||||
|
|
||||||
```python
|
```python
|
||||||
|
# Get pixel value
|
||||||
|
color = (r, g, b) = img.get(x, y)
|
||||||
|
|
||||||
|
# Set pixel value
|
||||||
|
img.put(x, y, (r, g, b))
|
||||||
|
|
||||||
|
# Reset all pixels to zero.
|
||||||
img.clear()
|
img.clear()
|
||||||
```
|
|
||||||
|
|
||||||
#### Copy pixels to another image
|
# Copy pixels to another image of the same format
|
||||||
|
|
||||||
```python
|
|
||||||
img.copy_data(another_img)
|
img.copy_data(another_img)
|
||||||
```
|
```
|
||||||
|
|
||||||
#### Set pixel value
|
### File operations
|
||||||
|
|
||||||
|
#### Raw dump files
|
||||||
|
|
||||||
```python
|
```python
|
||||||
img.put_rgb(x, y, (r, g, b))
|
# Dump image to file
|
||||||
|
img.dump(path)
|
||||||
|
|
||||||
|
# Restore image data from a dump file
|
||||||
|
img.load(path)
|
||||||
|
|
||||||
|
# Create a new image from a dump file
|
||||||
|
img = floatimg.create_from_dump(path)
|
||||||
|
|
||||||
|
# Get dump metadata (TODO create a FileInfo named tuple)
|
||||||
|
witdh, height, img_type = floatimg.fileinfos(path)
|
||||||
```
|
```
|
||||||
|
|
||||||
#### Get pixel value
|
#### External formats
|
||||||
|
|
||||||
```python
|
```python
|
||||||
r, g, b = img.get_rgb(x, y)
|
# export supports the following extentions : .fimg, .png, .tiff, .pnm, .fits
|
||||||
|
# save as png
|
||||||
|
img.export("test.png")
|
||||||
|
|
||||||
|
# create an image instance from png file
|
||||||
|
img = floatimg.create_from_png("test.png")
|
||||||
|
|
||||||
|
# load pixel data from png file in an existing instance. size and type have to be compatible
|
||||||
|
img.load_png("test.png")
|
||||||
```
|
```
|
||||||
|
|
||||||
### File manipulation
|
|
||||||
|
|
||||||
#### Dump image to file
|
## Operators
|
||||||
|
|
||||||
```python
|
```python
|
||||||
img.dump_to_file("/tmp/image_dump")
|
# addition
|
||||||
|
res_img = img + img2
|
||||||
|
|
||||||
|
# substraction
|
||||||
|
res_img = img - img2
|
||||||
|
|
||||||
|
# multiplication
|
||||||
|
res_img = img * img2
|
||||||
|
|
||||||
|
# min value per color channel
|
||||||
|
res_img = img.min(img2)
|
||||||
|
|
||||||
|
# max value per color channel
|
||||||
|
res_img = img.max(img2)
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
#### Restore image data from a dump file
|
## Contrast operations
|
||||||
|
|
||||||
```python
|
Contrast types are defined in the Contrast enumeration:
|
||||||
img.load_from_dump("/tmp/image_dump")
|
|
||||||
|
```
|
||||||
|
floatimg.Contrast.SQRT
|
||||||
|
floatimg.Contrast.POW2
|
||||||
|
floatimg.Contrast.COS01
|
||||||
|
floatimg.Contrast.COS010
|
||||||
```
|
```
|
||||||
|
|
||||||
#### Create a new image from a dump file
|
To apply contrast:
|
||||||
|
|
||||||
```python
|
|
||||||
img = floatimg.create_from_dump("/tmp/image_dump")
|
|
||||||
```
|
```
|
||||||
#### Get dump metadata
|
# Return a new image
|
||||||
|
res = img.contrast(value, floatimg.Contrast.SQRT)
|
||||||
|
|
||||||
```python
|
# set contrast in-place
|
||||||
witdh, height, img_type = floatimg.fileinfos("/tmp/image_dump")
|
img.contrast(value, floatimg.Contrast.SQRT, True)
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## Development
|
## Development
|
||||||
|
|
||||||
### Run tests
|
### Run tests
|
||||||
@@ -111,3 +157,18 @@ Ensure `floatimg` containing path is in the `PYTHONPATH`, this will run the test
|
|||||||
pytest --cov=floatimg tests/*
|
pytest --cov=floatimg tests/*
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Status
|
||||||
|
|
||||||
|
The goal is not to bind all the functions but essential ones too build a front-end with python-tkinter.
|
||||||
|
|
||||||
|
#### Done
|
||||||
|
|
||||||
|
- lib/fimg-core.c
|
||||||
|
- lib/fimg-file.c
|
||||||
|
- lib/operator.c
|
||||||
|
- funcs/exporter.c
|
||||||
|
- funcs/fimg-png.c
|
||||||
|
|
||||||
|
#### Partial
|
||||||
|
|
||||||
|
- lib/fimg-math.c
|
||||||
@@ -1,6 +1,16 @@
|
|||||||
"""Python Binding to FloatImg """
|
"""Python Binding to FloatImg """
|
||||||
|
|
||||||
# FloatImg class and static functions
|
# FloatImg class, constants & static functions
|
||||||
from floatimg.image import FloatImg, create, create_rgb, create_from_dump, GRAY, RGB, fileinfos
|
from floatimg.image import (
|
||||||
|
GRAY,
|
||||||
|
RGB,
|
||||||
|
FloatImg,
|
||||||
|
create,
|
||||||
|
create_rgb,
|
||||||
|
create_from_dump,
|
||||||
|
create_from_png,
|
||||||
|
fileinfos,
|
||||||
|
Contrast
|
||||||
|
)
|
||||||
|
|
||||||
__version__ = "0.0.1"
|
__version__ = "0.0.1"
|
||||||
|
|||||||
@@ -1,13 +1,26 @@
|
|||||||
|
"""Binding bases"""
|
||||||
import ctypes as ct
|
import ctypes as ct
|
||||||
|
from enum import Enum
|
||||||
|
import logging
|
||||||
|
|
||||||
from floatimg.settings import LIB
|
from floatimg.settings import LIB, PATH_ENCODING
|
||||||
|
|
||||||
############################################################################################################
|
############################################################################################################
|
||||||
# type constants
|
# type constants
|
||||||
GRAY = 1
|
GRAY = 1
|
||||||
RGB = 3
|
RGB = 3
|
||||||
RGBA = 4
|
RGBA = 4 # may not be used
|
||||||
RGBZ = 99
|
RGBZ = 99 # may not be used
|
||||||
|
|
||||||
|
############################################################################################################
|
||||||
|
# Contrast enumeration
|
||||||
|
class Contrast(Enum):
|
||||||
|
NONE = 0
|
||||||
|
SQRT = 1
|
||||||
|
POW2 = 2
|
||||||
|
COS01 = 3
|
||||||
|
COS010 = 4
|
||||||
|
|
||||||
|
|
||||||
############################################################################################################
|
############################################################################################################
|
||||||
# Type mapping
|
# Type mapping
|
||||||
@@ -32,7 +45,9 @@ C_FloatImg._fields_ = [
|
|||||||
]
|
]
|
||||||
|
|
||||||
############################################################################################################
|
############################################################################################################
|
||||||
# declaration of input / output types
|
# declaration of input / output types for C binding of core functions
|
||||||
|
|
||||||
|
# fimg-core.c
|
||||||
c_fimgcreate = LIB.fimg_create
|
c_fimgcreate = LIB.fimg_create
|
||||||
c_fimgcreate.argtypes = (ct.POINTER(C_FloatImg), ct.c_int, ct.c_int, ct.c_int)
|
c_fimgcreate.argtypes = (ct.POINTER(C_FloatImg), ct.c_int, ct.c_int, ct.c_int)
|
||||||
c_fimgcreate.restype = ct.c_int
|
c_fimgcreate.restype = ct.c_int
|
||||||
@@ -61,7 +76,6 @@ c_fimg_clear = LIB.fimg_clear
|
|||||||
c_fimg_clear.argtypes = (ct.POINTER(C_FloatImg),)
|
c_fimg_clear.argtypes = (ct.POINTER(C_FloatImg),)
|
||||||
c_fimg_clear.restype = ct.c_int
|
c_fimg_clear.restype = ct.c_int
|
||||||
|
|
||||||
|
|
||||||
c_fimg_plot_rgb = LIB.fimg_plot_rgb
|
c_fimg_plot_rgb = LIB.fimg_plot_rgb
|
||||||
c_fimg_plot_rgb.argtypes = (
|
c_fimg_plot_rgb.argtypes = (
|
||||||
ct.POINTER(C_FloatImg),
|
ct.POINTER(C_FloatImg),
|
||||||
@@ -73,8 +87,6 @@ c_fimg_plot_rgb.argtypes = (
|
|||||||
)
|
)
|
||||||
c_fimg_plot_rgb.restype = ct.c_int
|
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 = LIB.fimg_get_rgb
|
||||||
c_fimg_get_rgb.argtypes = (
|
c_fimg_get_rgb.argtypes = (
|
||||||
ct.POINTER(C_FloatImg),
|
ct.POINTER(C_FloatImg),
|
||||||
@@ -84,7 +96,6 @@ c_fimg_get_rgb.argtypes = (
|
|||||||
)
|
)
|
||||||
c_fimg_get_rgb.restype = ct.c_int
|
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 = LIB.fimg_put_rgb
|
||||||
c_fimg_put_rgb.argtypes = (
|
c_fimg_put_rgb.argtypes = (
|
||||||
ct.POINTER(C_FloatImg),
|
ct.POINTER(C_FloatImg),
|
||||||
@@ -103,6 +114,7 @@ c_fimg_rgb_constant.argtypes = (
|
|||||||
)
|
)
|
||||||
c_fimg_rgb_constant.restype = ct.c_int
|
c_fimg_rgb_constant.restype = ct.c_int
|
||||||
|
|
||||||
|
# fimg-file.c
|
||||||
c_fimg_dump_to_file = LIB.fimg_dump_to_file
|
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.argtypes = (ct.POINTER(C_FloatImg), ct.c_char_p, ct.c_int)
|
||||||
c_fimg_dump_to_file.restype = ct.c_int
|
c_fimg_dump_to_file.restype = ct.c_int
|
||||||
@@ -115,6 +127,104 @@ c_fimg_fileinfos = LIB.fimg_fileinfos
|
|||||||
c_fimg_fileinfos.argtypes = (ct.c_char_p, ct.POINTER(ct.c_int * 3))
|
c_fimg_fileinfos.argtypes = (ct.c_char_p, ct.POINTER(ct.c_int * 3))
|
||||||
c_fimg_fileinfos.restype = ct.c_int
|
c_fimg_fileinfos.restype = ct.c_int
|
||||||
|
|
||||||
|
c_fimg_add_rgb = LIB.fimg_add_rgb
|
||||||
|
c_fimg_add_rgb.argtypes = (
|
||||||
|
ct.POINTER(C_FloatImg),
|
||||||
|
ct.c_int,
|
||||||
|
ct.c_int,
|
||||||
|
ct.c_float,
|
||||||
|
ct.c_float,
|
||||||
|
ct.c_float,
|
||||||
|
)
|
||||||
|
c_fimg_add_rgb.restype = ct.c_int
|
||||||
|
|
||||||
|
# # fimg-operators.c
|
||||||
|
c_fimg_add_3 = LIB.fimg_add_3
|
||||||
|
c_fimg_add_3.argtypes = (
|
||||||
|
ct.POINTER(C_FloatImg),
|
||||||
|
ct.POINTER(C_FloatImg),
|
||||||
|
ct.POINTER(C_FloatImg),
|
||||||
|
)
|
||||||
|
c_fimg_add_3.restype = ct.c_int
|
||||||
|
|
||||||
|
c_fimg_sub_3 = LIB.fimg_sub_3
|
||||||
|
c_fimg_sub_3.argtypes = (
|
||||||
|
ct.POINTER(C_FloatImg),
|
||||||
|
ct.POINTER(C_FloatImg),
|
||||||
|
ct.POINTER(C_FloatImg),
|
||||||
|
)
|
||||||
|
c_fimg_sub_3.restype = ct.c_int
|
||||||
|
|
||||||
|
c_fimg_mul_3 = LIB.fimg_mul_3
|
||||||
|
c_fimg_mul_3.argtypes = (
|
||||||
|
ct.POINTER(C_FloatImg),
|
||||||
|
ct.POINTER(C_FloatImg),
|
||||||
|
ct.POINTER(C_FloatImg),
|
||||||
|
)
|
||||||
|
c_fimg_mul_3.restype = ct.c_int
|
||||||
|
|
||||||
|
c_fimg_minimum = LIB.fimg_minimum
|
||||||
|
c_fimg_minimum.argtypes = (
|
||||||
|
ct.POINTER(C_FloatImg),
|
||||||
|
ct.POINTER(C_FloatImg),
|
||||||
|
ct.POINTER(C_FloatImg),
|
||||||
|
)
|
||||||
|
c_fimg_minimum.restype = ct.c_int
|
||||||
|
|
||||||
|
c_fimg_maximum = LIB.fimg_maximum
|
||||||
|
c_fimg_maximum.argtypes = (
|
||||||
|
ct.POINTER(C_FloatImg),
|
||||||
|
ct.POINTER(C_FloatImg),
|
||||||
|
ct.POINTER(C_FloatImg),
|
||||||
|
)
|
||||||
|
c_fimg_maximum.restype = ct.c_int
|
||||||
|
|
||||||
|
# Files i/o
|
||||||
|
c_fimg_export_picture = LIB.fimg_export_picture
|
||||||
|
c_fimg_export_picture.argtypes = (ct.POINTER(C_FloatImg), ct.c_char_p)
|
||||||
|
c_fimg_export_picture.restype = ct.c_int
|
||||||
|
|
||||||
|
c_fimg_create_from_png = LIB.fimg_create_from_png
|
||||||
|
c_fimg_create_from_png.argtypes = (ct.c_char_p, ct.POINTER(C_FloatImg))
|
||||||
|
c_fimg_create_from_png.restype = ct.c_int
|
||||||
|
|
||||||
|
c_fimg_load_from_png = LIB.fimg_load_from_png
|
||||||
|
c_fimg_load_from_png.argtypes = (ct.c_char_p, ct.POINTER(C_FloatImg))
|
||||||
|
c_fimg_load_from_png.restype = ct.c_int
|
||||||
|
|
||||||
|
c_fimg_get_maxvalue = LIB.fimg_get_maxvalue
|
||||||
|
c_fimg_get_maxvalue.argtypes = (ct.POINTER(C_FloatImg),)
|
||||||
|
c_fimg_get_maxvalue.restype = ct.c_float
|
||||||
|
|
||||||
|
# contrast
|
||||||
|
c_fimg_square_root = LIB.fimg_square_root
|
||||||
|
c_fimg_square_root.argtypes = (
|
||||||
|
ct.POINTER(C_FloatImg),
|
||||||
|
ct.POINTER(C_FloatImg),
|
||||||
|
ct.c_double,
|
||||||
|
)
|
||||||
|
c_fimg_square_root.restype = ct.c_int
|
||||||
|
|
||||||
|
c_fimg_power_2 = LIB.fimg_power_2
|
||||||
|
c_fimg_power_2.argtypes = (ct.POINTER(C_FloatImg), ct.POINTER(C_FloatImg), ct.c_double)
|
||||||
|
c_fimg_power_2.restype = ct.c_int
|
||||||
|
|
||||||
|
c_fimg_cos_01 = LIB.fimg_cos_01
|
||||||
|
c_fimg_cos_01.argtypes = (ct.POINTER(C_FloatImg), ct.POINTER(C_FloatImg), ct.c_double)
|
||||||
|
c_fimg_cos_01.restype = ct.c_int
|
||||||
|
|
||||||
|
c_fimg_cos_010 = LIB.fimg_cos_010
|
||||||
|
c_fimg_cos_010.argtypes = (ct.POINTER(C_FloatImg), ct.POINTER(C_FloatImg), ct.c_double)
|
||||||
|
c_fimg_cos_010.restype = ct.c_int
|
||||||
|
|
||||||
|
# contrast function map
|
||||||
|
CONTRAST_FUNC_DICT = {
|
||||||
|
Contrast.SQRT: c_fimg_square_root,
|
||||||
|
Contrast.POW2: c_fimg_power_2,
|
||||||
|
Contrast.COS01: c_fimg_cos_01,
|
||||||
|
Contrast.COS010: c_fimg_cos_010,
|
||||||
|
}
|
||||||
|
|
||||||
############################################################################################################
|
############################################################################################################
|
||||||
class FloatImg:
|
class FloatImg:
|
||||||
"""
|
"""
|
||||||
@@ -126,22 +236,18 @@ class FloatImg:
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
# proxy attributes to the C_FloatImg structure
|
# proxy attributes to the C_FloatImg structure
|
||||||
|
# TODO: now more about them ;)
|
||||||
magic = property(lambda self: self.c_img.magic)
|
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)
|
fval = property(lambda self: self.c_img.fval)
|
||||||
count = property(lambda self: self.c_img.count)
|
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):
|
def __init__(self, c_img):
|
||||||
self.c_img = c_img
|
self.c_img = c_img
|
||||||
|
self.width = c_img.width
|
||||||
|
self.height = c_img.height
|
||||||
|
self.type_id = c_img.type
|
||||||
|
# convenient pointer for later calls to C library
|
||||||
self.c_img_p = ct.pointer(c_img)
|
self.c_img_p = ct.pointer(c_img)
|
||||||
|
|
||||||
#######################################################################################################
|
#######################################################################################################
|
||||||
@@ -151,6 +257,7 @@ class FloatImg:
|
|||||||
#######################################################################################################
|
#######################################################################################################
|
||||||
def destroy(self):
|
def destroy(self):
|
||||||
"""destroy the underlying structure. automattically called at instance destruction"""
|
"""destroy the underlying structure. automattically called at instance destruction"""
|
||||||
|
logging.debug("Destroy %s", self)
|
||||||
assert c_fimg_destroy(self.c_img) == 0
|
assert c_fimg_destroy(self.c_img) == 0
|
||||||
|
|
||||||
#######################################################################################################
|
#######################################################################################################
|
||||||
@@ -160,14 +267,14 @@ class FloatImg:
|
|||||||
#######################################################################################################
|
#######################################################################################################
|
||||||
def clear(self):
|
def clear(self):
|
||||||
"""clear data"""
|
"""clear data"""
|
||||||
return c_fimg_clear(self.c_img_p)
|
assert c_fimg_clear(self.c_img_p) == 0
|
||||||
|
|
||||||
#######################################################################################################
|
#######################################################################################################
|
||||||
def clone(self, flags=0):
|
def clone(self, copy_data=False):
|
||||||
"""return a clone of the current instance"""
|
"""return a clone of the current instance"""
|
||||||
new_pic = C_FloatImg()
|
c_img_p = C_FloatImg()
|
||||||
assert c_fimg_clone(self.c_img_p, ct.pointer(new_pic), flags) == 0
|
assert c_fimg_clone(self.c_img_p, ct.pointer(c_img_p), int(copy_data)) == 0
|
||||||
return FloatImg(new_pic)
|
return FloatImg(c_img_p)
|
||||||
|
|
||||||
#######################################################################################################
|
#######################################################################################################
|
||||||
def copy_data(self, to_img):
|
def copy_data(self, to_img):
|
||||||
@@ -180,53 +287,131 @@ class FloatImg:
|
|||||||
return c_fimg_str_type(self.c_img.type).decode("utf-8")
|
return c_fimg_str_type(self.c_img.type).decode("utf-8")
|
||||||
|
|
||||||
#######################################################################################################
|
#######################################################################################################
|
||||||
def rgb_constant(self, r, v, b):
|
def fill(self, color):
|
||||||
assert c_fimg_rgb_constant(self.c_img_p, r, v, b) == 0
|
assert c_fimg_rgb_constant(self.c_img_p, *color) == 0
|
||||||
|
|
||||||
#######################################################################################################
|
#######################################################################################################
|
||||||
def get_rgb(self, x, y):
|
def get(self, x, y):
|
||||||
"""get r,g,b triplet from a pixel"""
|
"""get r,g,b triplet from a pixel"""
|
||||||
rgb = (ct.c_float * 3)()
|
color = (ct.c_float * 3)()
|
||||||
assert c_fimg_get_rgb(self.c_img_p, x, y, ct.pointer(rgb)) == 0
|
assert c_fimg_get_rgb(self.c_img_p, x, y, ct.pointer(color)) == 0
|
||||||
return rgb[:3]
|
return color[:3]
|
||||||
|
|
||||||
#######################################################################################################
|
#######################################################################################################
|
||||||
def put_rgb(self, x, y, rgb):
|
def put(self, x, y, color):
|
||||||
"""put r,g,b triplet to a pixel"""
|
"""put color to a pixel"""
|
||||||
# TODO may be a better way to create the array rather than iterating
|
c_rgb = (ct.c_float * 3)(*color)
|
||||||
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
|
assert c_fimg_put_rgb(self.c_img_p, x, y, ct.pointer(c_rgb)) == 0
|
||||||
|
|
||||||
#######################################################################################################
|
#######################################################################################################
|
||||||
def dump_to_file(self, fname):
|
def add(self, x, y, color):
|
||||||
|
"""add color to a pixel """
|
||||||
|
assert c_fimg_add_rgb(self.c_img_p, x, y, *color) == 0
|
||||||
|
|
||||||
|
#######################################################################################################
|
||||||
|
def dump(self, path):
|
||||||
"""save data to a dump file"""
|
"""save data to a dump file"""
|
||||||
# TODO use system encoding instead of utf-8
|
# TODO use system encoding instead of utf-8
|
||||||
assert (
|
assert (
|
||||||
c_fimg_dump_to_file(
|
c_fimg_dump_to_file(
|
||||||
self.c_img_p, ct.c_char_p(bytes(fname, encoding="utf8")), 0
|
self.c_img_p, ct.c_char_p(bytes(path, encoding=PATH_ENCODING)), 0
|
||||||
)
|
)
|
||||||
== 0
|
== 0
|
||||||
)
|
)
|
||||||
|
|
||||||
#######################################################################################################
|
#######################################################################################################
|
||||||
def load_from_dump(self, fname):
|
def load(self, path):
|
||||||
"""load data from a dump. size and type have to be compatible"""
|
"""load data from a dump. size and type have to be compatible"""
|
||||||
assert (
|
assert (
|
||||||
c_fimg_load_from_dump(
|
c_fimg_load_from_dump(
|
||||||
ct.c_char_p(bytes(fname, encoding="utf8")), self.c_img_p
|
ct.c_char_p(bytes(path, encoding=PATH_ENCODING)), self.c_img_p
|
||||||
)
|
)
|
||||||
== 0
|
== 0
|
||||||
)
|
)
|
||||||
|
|
||||||
|
#######################################################################################################
|
||||||
|
def load_png(self, path):
|
||||||
|
"""load data from a png file. size and type have to be compatible"""
|
||||||
|
assert (
|
||||||
|
c_fimg_load_from_png(
|
||||||
|
ct.c_char_p(bytes(path, encoding=PATH_ENCODING)), self.c_img_p
|
||||||
|
)
|
||||||
|
== 0
|
||||||
|
)
|
||||||
|
|
||||||
|
#######################################################################################################
|
||||||
|
def __add__(self, img):
|
||||||
|
""" + operator overload"""
|
||||||
|
res = self.clone(False)
|
||||||
|
assert c_fimg_add_3(self.c_img_p, img.c_img_p, res.c_img_p) == 0
|
||||||
|
return res
|
||||||
|
|
||||||
|
#######################################################################################################
|
||||||
|
def __sub__(self, img):
|
||||||
|
""" - operator overload"""
|
||||||
|
res = self.clone(False)
|
||||||
|
assert c_fimg_sub_3(self.c_img_p, img.c_img_p, res.c_img_p) == 0
|
||||||
|
return res
|
||||||
|
|
||||||
|
#######################################################################################################
|
||||||
|
def __mul__(self, img):
|
||||||
|
""" * operator overload"""
|
||||||
|
res = self.clone(False)
|
||||||
|
assert c_fimg_mul_3(self.c_img_p, img.c_img_p, res.c_img_p) == 0
|
||||||
|
return res
|
||||||
|
|
||||||
|
#######################################################################################################
|
||||||
|
def min(self, img):
|
||||||
|
"""return a new image with minimum pixel values per channel between current and parameter"""
|
||||||
|
res = self.clone(False)
|
||||||
|
# C code inverts comparison
|
||||||
|
assert c_fimg_minimum(self.c_img_p, img.c_img_p, res.c_img_p) == 0
|
||||||
|
return res
|
||||||
|
|
||||||
|
#######################################################################################################
|
||||||
|
def max(self, img):
|
||||||
|
"""return a new image with maximum pixel values per channel between current and parameter"""
|
||||||
|
res = self.clone(False)
|
||||||
|
# C code inverts comparison
|
||||||
|
assert c_fimg_maximum(self.c_img_p, img.c_img_p, res.c_img_p) == 0
|
||||||
|
return res
|
||||||
|
|
||||||
|
#######################################################################################################
|
||||||
|
def export(self, path):
|
||||||
|
"""export image to path. allowed extension: .fimg, .png, .tiff, .pnm, .fits"""
|
||||||
|
assert (
|
||||||
|
c_fimg_export_picture(
|
||||||
|
self.c_img_p, ct.c_char_p(bytes(path, encoding=PATH_ENCODING)), 0
|
||||||
|
)
|
||||||
|
== 0
|
||||||
|
)
|
||||||
|
|
||||||
|
#######################################################################################################
|
||||||
|
def maxvalue(self):
|
||||||
|
"""return the maximum value for any channel in the image"""
|
||||||
|
return c_fimg_get_maxvalue(self.c_img_p)
|
||||||
|
|
||||||
|
#######################################################################################################
|
||||||
|
def contrast(self, max_value, contrast_id=Contrast.SQRT, in_place=False):
|
||||||
|
""""""
|
||||||
|
func = CONTRAST_FUNC_DICT[contrast_id]
|
||||||
|
dest = None
|
||||||
|
dest_c_img = None
|
||||||
|
if not in_place:
|
||||||
|
dest = self.clone(False)
|
||||||
|
dest_c_img = dest.c_img_p
|
||||||
|
assert (func(self.c_img, dest_c_img, max_value)) == 0
|
||||||
|
return dest
|
||||||
|
|
||||||
|
|
||||||
###########################################################################################################
|
###########################################################################################################
|
||||||
def fileinfos(fname):
|
def fileinfos(fname):
|
||||||
"""return witdh, height, img_type triplet read from a dump file"""
|
"""return witdh, height, img_type triplet read from a dump file"""
|
||||||
datas = (ct.c_int * 3)()
|
datas = (ct.c_int * 3)()
|
||||||
assert (
|
assert (
|
||||||
c_fimg_fileinfos(ct.c_char_p(bytes(fname, encoding="utf8")), ct.pointer(datas))
|
c_fimg_fileinfos(
|
||||||
|
ct.c_char_p(bytes(fname, encoding=PATH_ENCODING)), ct.pointer(datas)
|
||||||
|
)
|
||||||
== 0
|
== 0
|
||||||
)
|
)
|
||||||
return datas[:3]
|
return datas[:3]
|
||||||
@@ -248,14 +433,25 @@ def create_rgb(witdh, height):
|
|||||||
|
|
||||||
|
|
||||||
###########################################################################################################
|
###########################################################################################################
|
||||||
def create_from_dump(fname):
|
def create_from_dump(path):
|
||||||
"""Create a new instance from a dump file"""
|
"""Create a FloatImg instance from a dump file"""
|
||||||
witdh, height, img_type = fileinfos(fname)
|
witdh, height, img_type = fileinfos(path)
|
||||||
img = create(witdh, height, img_type)
|
img = create(witdh, height, img_type)
|
||||||
img.load_from_dump(fname)
|
img.load(path)
|
||||||
return img
|
return img
|
||||||
|
|
||||||
|
|
||||||
|
###########################################################################################################
|
||||||
|
def create_from_png(path):
|
||||||
|
"""Create a FloatImg instance from a png file"""
|
||||||
|
img = C_FloatImg()
|
||||||
|
assert (
|
||||||
|
c_fimg_create_from_png(ct.c_char_p(bytes(path, encoding=PATH_ENCODING)), img)
|
||||||
|
== 0
|
||||||
|
)
|
||||||
|
return FloatImg(img)
|
||||||
|
|
||||||
|
|
||||||
###########################################################################################################
|
###########################################################################################################
|
||||||
def type_is_valid(type_id):
|
def type_is_valid(type_id):
|
||||||
"""return True if type_id is a valid one"""
|
"""return True if type_id is a valid one"""
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import ctypes
|
import ctypes
|
||||||
|
|
||||||
# LIB = ctypes.cdll.LoadLibrary("../FloatImg/libfloatimg.so")
|
PATH_ENCODING = "utf8"
|
||||||
|
|
||||||
LIB = ctypes.cdll.LoadLibrary("../FloatImg4PythonBinding/build/lib/libfloatimg.so")
|
LIB = ctypes.cdll.LoadLibrary("../FloatImg4PythonBinding/build/lib/libfloatimg.so")
|
||||||
|
|||||||
103
tests/basics.py
103
tests/basics.py
@@ -21,26 +21,26 @@ def test_clone():
|
|||||||
width = 5
|
width = 5
|
||||||
height = 5
|
height = 5
|
||||||
img = floatimg.create(width, height, floatimg.RGB)
|
img = floatimg.create(width, height, floatimg.RGB)
|
||||||
img.rgb_constant(127.0, 127.0, 127.0)
|
img.fill((127.0, 127.0, 127.0))
|
||||||
|
|
||||||
# clone without copying pixel values
|
# clone without copying pixel values
|
||||||
img2 = img.clone()
|
img2 = img.clone(False)
|
||||||
assert img.width == img2.width
|
assert img.width == img2.width
|
||||||
assert img.height == img2.height
|
assert img.height == img2.height
|
||||||
assert img.type_id == img2.type_id
|
assert img.type_id == img2.type_id
|
||||||
# TODO inspect RVB and do pixel per pixel comparison
|
# TODO inspect RVB and do pixel per pixel comparison
|
||||||
for y in range(height):
|
for y in range(height):
|
||||||
for x in range(width):
|
for x in range(width):
|
||||||
assert img.get_rgb(x, y) != img2.get_rgb(x, y)
|
assert img.get(x, y) != img2.get(x, y)
|
||||||
|
|
||||||
img2 = img.clone(1)
|
img2 = img.clone(True)
|
||||||
assert img.width == img2.width
|
assert img.width == img2.width
|
||||||
assert img.height == img2.height
|
assert img.height == img2.height
|
||||||
assert img.type_id == img2.type_id
|
assert img.type_id == img2.type_id
|
||||||
# TODO inspect RVB and do pixel per pixel comparison
|
# TODO inspect RVB and do pixel per pixel comparison
|
||||||
for y in range(height):
|
for y in range(height):
|
||||||
for x in range(width):
|
for x in range(width):
|
||||||
assert img.get_rgb(x, y) == img2.get_rgb(x, y)
|
assert img.get(x, y) == img2.get(x, y)
|
||||||
|
|
||||||
|
|
||||||
def test_rgb_constant():
|
def test_rgb_constant():
|
||||||
@@ -48,10 +48,10 @@ def test_rgb_constant():
|
|||||||
height = 5
|
height = 5
|
||||||
color = [127.0, 127.0, 127.0]
|
color = [127.0, 127.0, 127.0]
|
||||||
img = floatimg.create_rgb(width, height)
|
img = floatimg.create_rgb(width, height)
|
||||||
img.rgb_constant(*color)
|
img.fill(color)
|
||||||
for y in range(height):
|
for y in range(height):
|
||||||
for x in range(width):
|
for x in range(width):
|
||||||
assert img.get_rgb(x, y) == color
|
assert img.get(x, y) == color
|
||||||
|
|
||||||
|
|
||||||
def test_copy_data():
|
def test_copy_data():
|
||||||
@@ -59,27 +59,27 @@ def test_copy_data():
|
|||||||
height = 5
|
height = 5
|
||||||
color = [127.0, 127.0, 127.0]
|
color = [127.0, 127.0, 127.0]
|
||||||
img = floatimg.create_rgb(width, height)
|
img = floatimg.create_rgb(width, height)
|
||||||
img.rgb_constant(*color)
|
img.fill(color)
|
||||||
|
|
||||||
img2 = floatimg.create_rgb(width, height)
|
img2 = floatimg.create_rgb(width, height)
|
||||||
img2.rgb_constant(64.0, 64.0, 64.0)
|
img2.fill((64.0, 64.0, 64.0))
|
||||||
|
|
||||||
img.copy_data(img2)
|
img.copy_data(img2)
|
||||||
|
|
||||||
for y in range(height):
|
for y in range(height):
|
||||||
for x in range(width):
|
for x in range(width):
|
||||||
assert img.get_rgb(x, y) == img2.get_rgb(x, y)
|
assert img.get(x, y) == img2.get(x, y)
|
||||||
|
|
||||||
|
|
||||||
def test_clear():
|
def test_clear():
|
||||||
width, height = 5, 5
|
width, height = 5, 5
|
||||||
color = [127.0, 127.0, 127.0]
|
color = [127.0, 127.0, 127.0]
|
||||||
img = floatimg.create_rgb(width, height)
|
img = floatimg.create_rgb(width, height)
|
||||||
img.rgb_constant(*color)
|
img.fill(color)
|
||||||
img.clear()
|
img.clear()
|
||||||
for y in range(height):
|
for y in range(height):
|
||||||
for x in range(width):
|
for x in range(width):
|
||||||
assert img.get_rgb(x, y) == [0.0, 0.0, 0.0]
|
assert img.get(x, y) == [0.0, 0.0, 0.0]
|
||||||
|
|
||||||
|
|
||||||
def test_str_type():
|
def test_str_type():
|
||||||
@@ -100,6 +100,79 @@ def test__str__():
|
|||||||
def test_put_rgb():
|
def test_put_rgb():
|
||||||
width, height = 5, 5
|
width, height = 5, 5
|
||||||
img = floatimg.create_rgb(width, height)
|
img = floatimg.create_rgb(width, height)
|
||||||
assert img.get_rgb(0, 0) == [0.0, 0.0, 0.0]
|
assert img.get(0, 0) == [0.0, 0.0, 0.0]
|
||||||
img.put_rgb(0, 0, [127.0, 127.0, 127.0])
|
img.put(0, 0, (127.0, 127.0, 127.0))
|
||||||
assert img.get_rgb(0, 0) == [127.0, 127.0, 127.0]
|
assert img.get(0, 0) == [127.0, 127.0, 127.0]
|
||||||
|
|
||||||
|
|
||||||
|
def test_add():
|
||||||
|
width, height = 5, 5
|
||||||
|
img = floatimg.create_rgb(width, height)
|
||||||
|
color = (32.0, 32.0, 32.0)
|
||||||
|
img.put(0, 0, color)
|
||||||
|
img.add(0, 0, color)
|
||||||
|
assert img.get(0, 0) == [64.0, 64.0, 64.0]
|
||||||
|
|
||||||
|
|
||||||
|
def test_op_add():
|
||||||
|
width, height = 5, 5
|
||||||
|
img = floatimg.create_rgb(width, height)
|
||||||
|
color = (32.0, 32.0, 32.0)
|
||||||
|
img.fill(color)
|
||||||
|
img2 = floatimg.create_rgb(width, height)
|
||||||
|
img2.fill(color)
|
||||||
|
img3 = img + img2
|
||||||
|
for y in range(height):
|
||||||
|
for x in range(width):
|
||||||
|
assert img3.get(x, y) == [64.0, 64.0, 64.0]
|
||||||
|
|
||||||
|
|
||||||
|
def test_op_sub():
|
||||||
|
width, height = 5, 5
|
||||||
|
img = floatimg.create_rgb(width, height)
|
||||||
|
color = (32.0, 32.0, 32.0)
|
||||||
|
img.fill(color)
|
||||||
|
img2 = floatimg.create_rgb(width, height)
|
||||||
|
img2.fill(color)
|
||||||
|
img3 = img - img2
|
||||||
|
for y in range(height):
|
||||||
|
for x in range(width):
|
||||||
|
assert img3.get(x, y) == [0.0, 0.0, 0.0]
|
||||||
|
|
||||||
|
|
||||||
|
def test_op_mul():
|
||||||
|
width, height = 5, 5
|
||||||
|
img = floatimg.create_rgb(width, height)
|
||||||
|
color = (32.0, 32.0, 32.0)
|
||||||
|
img.fill(color)
|
||||||
|
img2 = floatimg.create_rgb(width, height)
|
||||||
|
img2.fill((2.0, 2.0, 2.0))
|
||||||
|
img3 = img * img2
|
||||||
|
for y in range(height):
|
||||||
|
for x in range(width):
|
||||||
|
assert img3.get(x, y) == [64.0, 64.0, 64.0]
|
||||||
|
|
||||||
|
def test_op_min():
|
||||||
|
width, height = 5, 5
|
||||||
|
img = floatimg.create_rgb(width, height)
|
||||||
|
img.fill((32.0, 0.0, 32.0))
|
||||||
|
|
||||||
|
img2 = floatimg.create_rgb(width, height)
|
||||||
|
img2.fill((0.0, 32.0, 0.0))
|
||||||
|
img3 = img.min(img2)
|
||||||
|
for y in range(height):
|
||||||
|
for x in range(width):
|
||||||
|
assert img3.get(x, y) == [0.0, 0.0, 0.0]
|
||||||
|
|
||||||
|
|
||||||
|
def test_op_max():
|
||||||
|
width, height = 5, 5
|
||||||
|
img = floatimg.create_rgb(width, height)
|
||||||
|
img.fill((32.0, 0.0, 32.0))
|
||||||
|
|
||||||
|
img2 = floatimg.create_rgb(width, height)
|
||||||
|
img2.fill((0.0, 32.0, 0.0))
|
||||||
|
img3 = img.max(img2)
|
||||||
|
for y in range(height):
|
||||||
|
for x in range(width):
|
||||||
|
assert img3.get(x, y) == [32.0, 32.0, 32.0]
|
||||||
|
|||||||
35
tests/contrast.py
Normal file
35
tests/contrast.py
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
import floatimg
|
||||||
|
import logging
|
||||||
|
|
||||||
|
|
||||||
|
def test_square():
|
||||||
|
width, height = 5, 5
|
||||||
|
|
||||||
|
color = [127.0, 127.0, 127.0]
|
||||||
|
img = floatimg.create_rgb(width, height)
|
||||||
|
img.fill(color)
|
||||||
|
img.put(2, 2, [200, 200, 200])
|
||||||
|
|
||||||
|
result_dict = {
|
||||||
|
floatimg.Contrast.SQRT: (
|
||||||
|
[225.83180236816406, 225.83180236816406, 225.83180236816406],
|
||||||
|
[179.9583282470703, 179.9583282470703, 179.9583282470703],
|
||||||
|
),
|
||||||
|
floatimg.Contrast.POW2: (
|
||||||
|
[156.8627471923828, 156.8627471923828, 156.8627471923828],
|
||||||
|
[63.250980377197266, 63.250980377197266, 63.250980377197266],
|
||||||
|
),
|
||||||
|
floatimg.Contrast.COS01: (
|
||||||
|
[226.83277893066406, 226.83277893066406, 226.83277893066406],
|
||||||
|
[126.71460723876953, 126.71460723876953, 126.71460723876953],
|
||||||
|
),
|
||||||
|
floatimg.Contrast.COS010: (
|
||||||
|
[100.2235336303711, 100.2235336303711, 100.2235336303711],
|
||||||
|
[254.99032592773438, 254.99032592773438, 254.99032592773438],
|
||||||
|
),
|
||||||
|
}
|
||||||
|
|
||||||
|
for contrast_type, (center, outer) in result_dict.items():
|
||||||
|
res = img.contrast(255, contrast_type)
|
||||||
|
assert res.get(2, 2) == center
|
||||||
|
assert res.get(2, 3) == outer
|
||||||
@@ -10,8 +10,8 @@ def create_dump():
|
|||||||
width, height = 5, 5
|
width, height = 5, 5
|
||||||
img = floatimg.create_rgb(width, height)
|
img = floatimg.create_rgb(width, height)
|
||||||
# fill it to have something to compare
|
# fill it to have something to compare
|
||||||
img.rgb_constant(64.0, 64.0, 64.0)
|
img.fill((64.0, 64.0, 64.0))
|
||||||
img.dump_to_file(TEST_PATH)
|
img.dump(TEST_PATH)
|
||||||
|
|
||||||
|
|
||||||
def test_dump_to_file():
|
def test_dump_to_file():
|
||||||
@@ -34,10 +34,11 @@ def test_load():
|
|||||||
create_dump()
|
create_dump()
|
||||||
width, height = 5, 5
|
width, height = 5, 5
|
||||||
img = floatimg.create_rgb(width, height)
|
img = floatimg.create_rgb(width, height)
|
||||||
img.load_from_dump(TEST_PATH)
|
|
||||||
|
img.load(TEST_PATH)
|
||||||
for y in range(height):
|
for y in range(height):
|
||||||
for x in range(width):
|
for x in range(width):
|
||||||
assert img.get_rgb(x, y) == [64.0, 64.0, 64.0]
|
assert img.get(x, y) == [64.0, 64.0, 64.0]
|
||||||
os.remove(TEST_PATH)
|
os.remove(TEST_PATH)
|
||||||
|
|
||||||
|
|
||||||
@@ -46,5 +47,5 @@ def test_create():
|
|||||||
img = floatimg.create_from_dump(TEST_PATH)
|
img = floatimg.create_from_dump(TEST_PATH)
|
||||||
for y in range(img.height):
|
for y in range(img.height):
|
||||||
for x in range(img.width):
|
for x in range(img.width):
|
||||||
assert img.get_rgb(x, y) == [64.0, 64.0, 64.0]
|
assert img.get(x, y) == [64.0, 64.0, 64.0]
|
||||||
os.remove(TEST_PATH)
|
os.remove(TEST_PATH)
|
||||||
|
|||||||
28
tests/files.py
Normal file
28
tests/files.py
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
import os
|
||||||
|
|
||||||
|
import floatimg
|
||||||
|
|
||||||
|
def create_img():
|
||||||
|
width, height = 5, 5
|
||||||
|
img = floatimg.create_rgb(width, height)
|
||||||
|
img.fill((255.0, 0.0, 0.0))
|
||||||
|
return img
|
||||||
|
|
||||||
|
def test_png():
|
||||||
|
width, height = 5, 5
|
||||||
|
path = "test.png"
|
||||||
|
img = create_img()
|
||||||
|
img.export(path)
|
||||||
|
assert os.path.getsize(path) == 74
|
||||||
|
img2 = floatimg.create_from_png(path)
|
||||||
|
for y in range(height):
|
||||||
|
for x in range(width):
|
||||||
|
assert img.get(x, y) == img2.get(x, y)
|
||||||
|
|
||||||
|
img3 = img2.clone(False)
|
||||||
|
img3.load_png(path)
|
||||||
|
for y in range(height):
|
||||||
|
for x in range(width):
|
||||||
|
assert img.get(x, y) == img3.get(x, y)
|
||||||
|
|
||||||
|
|
||||||
12
tests/maths.py
Normal file
12
tests/maths.py
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
"""test of core functions """
|
||||||
|
|
||||||
|
import floatimg
|
||||||
|
|
||||||
|
def test_maxvaalue():
|
||||||
|
width = 5
|
||||||
|
height = 5
|
||||||
|
|
||||||
|
img = floatimg.create_rgb(width, height)
|
||||||
|
color = (255.0, 255.0, 255.0)
|
||||||
|
img.put(2,2, color)
|
||||||
|
assert img.maxvalue() == 255.0
|
||||||
Reference in New Issue
Block a user