optiklear 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- optiklear-0.1.0/.gitignore +23 -0
- optiklear-0.1.0/PKG-INFO +87 -0
- optiklear-0.1.0/README.md +79 -0
- optiklear-0.1.0/optiklear/__init__.py +0 -0
- optiklear-0.1.0/optiklear/conv.py +103 -0
- optiklear-0.1.0/optiklear/mat_trans.py +418 -0
- optiklear-0.1.0/optiklear/mat_vec.py +73 -0
- optiklear-0.1.0/optiklear/noise_gen.py +45 -0
- optiklear-0.1.0/optiklear/non_linear.py +83 -0
- optiklear-0.1.0/optiklear/vec_operations.py +87 -0
- optiklear-0.1.0/pyproject.toml +16 -0
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# Virtual environment
|
|
2
|
+
.venv/
|
|
3
|
+
|
|
4
|
+
# Python cache
|
|
5
|
+
__pycache__/
|
|
6
|
+
*.py[cod]
|
|
7
|
+
|
|
8
|
+
# Distribution/build files
|
|
9
|
+
build/
|
|
10
|
+
dist/
|
|
11
|
+
*.egg-info/
|
|
12
|
+
|
|
13
|
+
# Testing and coverage
|
|
14
|
+
.pytest_cache/
|
|
15
|
+
.coverage
|
|
16
|
+
htmlcov/
|
|
17
|
+
|
|
18
|
+
# IDE
|
|
19
|
+
.vscode/
|
|
20
|
+
|
|
21
|
+
# OS files
|
|
22
|
+
.DS_Store
|
|
23
|
+
Thumbs.db
|
optiklear-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: optiklear
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: A Python library for mathematical operations and image processing
|
|
5
|
+
Requires-Python: >=3.10
|
|
6
|
+
Requires-Dist: numpy
|
|
7
|
+
Description-Content-Type: text/markdown
|
|
8
|
+
|
|
9
|
+
# OptiKlear
|
|
10
|
+
|
|
11
|
+
**OptiKlear** is a Python library for mathematical operations and digital image processing.
|
|
12
|
+
|
|
13
|
+
It provides reusable functions for vector operations, matrix-vector transformations, image transformations, noise generation and spatial filtering.
|
|
14
|
+
|
|
15
|
+
## Features
|
|
16
|
+
|
|
17
|
+
Current functionality includes:
|
|
18
|
+
|
|
19
|
+
- **Convolution:** Spatial convolution for image filtering.
|
|
20
|
+
- **Nonlinear filtering:** Median filtering.
|
|
21
|
+
- **Noise generation:** Uniform, Gaussian and salt-and-pepper noise.
|
|
22
|
+
- **Geometric transformations:** Scaling, rotation, shearing, flipping and translation.
|
|
23
|
+
- **Matrix-vector operations:** Matrix multiplication and 2D vector transformations.
|
|
24
|
+
- **Vector operations:** Vector addition, subtraction, magnitude, distance, dot product, cross product and rotation.
|
|
25
|
+
|
|
26
|
+
## Installation
|
|
27
|
+
|
|
28
|
+
Install OptiKlear using pip:
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
pip install optiklear
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
**Note:** OptiKlear is currently under development. The package will be available on PyPI following its initial release.
|
|
35
|
+
|
|
36
|
+
## Requirements
|
|
37
|
+
|
|
38
|
+
- Python 3.10 or later
|
|
39
|
+
- NumPy
|
|
40
|
+
|
|
41
|
+
## Package Structure
|
|
42
|
+
|
|
43
|
+
| Module | Description |
|
|
44
|
+
|---|---|
|
|
45
|
+
| `conv` | Convolution operations |
|
|
46
|
+
| `non_linear` | Nonlinear image filtering |
|
|
47
|
+
| `noise_gen` | Image noise generation |
|
|
48
|
+
| `mat_trans` | 2D image transformations |
|
|
49
|
+
| `mat_vec` | Matrix-vector operations and transformations |
|
|
50
|
+
| `vec_operations` | Mathematical vector operations |
|
|
51
|
+
|
|
52
|
+
## Usage
|
|
53
|
+
|
|
54
|
+
Import individual functions from their respective modules.
|
|
55
|
+
|
|
56
|
+
For example, to use vector operations:
|
|
57
|
+
|
|
58
|
+
```python
|
|
59
|
+
from optiklear.vec_operations import vec_add2D, mag_v2D
|
|
60
|
+
|
|
61
|
+
u = [3, 4]
|
|
62
|
+
v = [2, 5]
|
|
63
|
+
|
|
64
|
+
result = vec_add2D(u, v)
|
|
65
|
+
magnitude = mag_v2D(u)
|
|
66
|
+
|
|
67
|
+
print(result)
|
|
68
|
+
print(magnitude)
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
To use image noise generation:
|
|
72
|
+
|
|
73
|
+
```python
|
|
74
|
+
from optiklear.noise_gen import gauss_noise
|
|
75
|
+
|
|
76
|
+
noisy_image = gauss_noise(image, 9)
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
The image-processing functions currently use NumPy arrays and/or Python lists according to their individual implementations. Check each function's input requirements before use.
|
|
80
|
+
|
|
81
|
+
## Development Roadmap
|
|
82
|
+
|
|
83
|
+
Planned additions for future versions include:
|
|
84
|
+
|
|
85
|
+
- Point operations
|
|
86
|
+
- Edge detection
|
|
87
|
+
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
# OptiKlear
|
|
2
|
+
|
|
3
|
+
**OptiKlear** is a Python library for mathematical operations and digital image processing.
|
|
4
|
+
|
|
5
|
+
It provides reusable functions for vector operations, matrix-vector transformations, image transformations, noise generation and spatial filtering.
|
|
6
|
+
|
|
7
|
+
## Features
|
|
8
|
+
|
|
9
|
+
Current functionality includes:
|
|
10
|
+
|
|
11
|
+
- **Convolution:** Spatial convolution for image filtering.
|
|
12
|
+
- **Nonlinear filtering:** Median filtering.
|
|
13
|
+
- **Noise generation:** Uniform, Gaussian and salt-and-pepper noise.
|
|
14
|
+
- **Geometric transformations:** Scaling, rotation, shearing, flipping and translation.
|
|
15
|
+
- **Matrix-vector operations:** Matrix multiplication and 2D vector transformations.
|
|
16
|
+
- **Vector operations:** Vector addition, subtraction, magnitude, distance, dot product, cross product and rotation.
|
|
17
|
+
|
|
18
|
+
## Installation
|
|
19
|
+
|
|
20
|
+
Install OptiKlear using pip:
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
pip install optiklear
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
**Note:** OptiKlear is currently under development. The package will be available on PyPI following its initial release.
|
|
27
|
+
|
|
28
|
+
## Requirements
|
|
29
|
+
|
|
30
|
+
- Python 3.10 or later
|
|
31
|
+
- NumPy
|
|
32
|
+
|
|
33
|
+
## Package Structure
|
|
34
|
+
|
|
35
|
+
| Module | Description |
|
|
36
|
+
|---|---|
|
|
37
|
+
| `conv` | Convolution operations |
|
|
38
|
+
| `non_linear` | Nonlinear image filtering |
|
|
39
|
+
| `noise_gen` | Image noise generation |
|
|
40
|
+
| `mat_trans` | 2D image transformations |
|
|
41
|
+
| `mat_vec` | Matrix-vector operations and transformations |
|
|
42
|
+
| `vec_operations` | Mathematical vector operations |
|
|
43
|
+
|
|
44
|
+
## Usage
|
|
45
|
+
|
|
46
|
+
Import individual functions from their respective modules.
|
|
47
|
+
|
|
48
|
+
For example, to use vector operations:
|
|
49
|
+
|
|
50
|
+
```python
|
|
51
|
+
from optiklear.vec_operations import vec_add2D, mag_v2D
|
|
52
|
+
|
|
53
|
+
u = [3, 4]
|
|
54
|
+
v = [2, 5]
|
|
55
|
+
|
|
56
|
+
result = vec_add2D(u, v)
|
|
57
|
+
magnitude = mag_v2D(u)
|
|
58
|
+
|
|
59
|
+
print(result)
|
|
60
|
+
print(magnitude)
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
To use image noise generation:
|
|
64
|
+
|
|
65
|
+
```python
|
|
66
|
+
from optiklear.noise_gen import gauss_noise
|
|
67
|
+
|
|
68
|
+
noisy_image = gauss_noise(image, 9)
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
The image-processing functions currently use NumPy arrays and/or Python lists according to their individual implementations. Check each function's input requirements before use.
|
|
72
|
+
|
|
73
|
+
## Development Roadmap
|
|
74
|
+
|
|
75
|
+
Planned additions for future versions include:
|
|
76
|
+
|
|
77
|
+
- Point operations
|
|
78
|
+
- Edge detection
|
|
79
|
+
|
|
File without changes
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import math
|
|
2
|
+
|
|
3
|
+
def rotate_kernel(h):
|
|
4
|
+
|
|
5
|
+
for i in h:
|
|
6
|
+
i.reverse()
|
|
7
|
+
|
|
8
|
+
h.reverse()
|
|
9
|
+
return h
|
|
10
|
+
|
|
11
|
+
def get_kernel_mid(h):
|
|
12
|
+
|
|
13
|
+
h_length = len(h)
|
|
14
|
+
|
|
15
|
+
h_mid_arr = math.floor(h_length / 2)
|
|
16
|
+
mid_idx = math.floor(len(h[h_mid_arr]) / 2)
|
|
17
|
+
|
|
18
|
+
return h_mid_arr, mid_idx
|
|
19
|
+
|
|
20
|
+
def pad_matrix(h, B):
|
|
21
|
+
|
|
22
|
+
h = rotate_kernel(h)
|
|
23
|
+
|
|
24
|
+
row_count = len(h)
|
|
25
|
+
col_count = len(h[0])
|
|
26
|
+
|
|
27
|
+
side_pad = (col_count - 1) // 2
|
|
28
|
+
across_pad = (row_count - 1) // 2
|
|
29
|
+
|
|
30
|
+
# Side Padding
|
|
31
|
+
for i in range(len(B)):
|
|
32
|
+
for j in range(side_pad):
|
|
33
|
+
B[i].append([0, 0, 0])
|
|
34
|
+
B[i].insert(0, [0, 0, 0])
|
|
35
|
+
|
|
36
|
+
# Create top/bottom padding row
|
|
37
|
+
my_pad = [[0, 0, 0] for i in range(len(B[0]))]
|
|
38
|
+
|
|
39
|
+
# Top and bottom padding
|
|
40
|
+
for i in range(across_pad):
|
|
41
|
+
B.insert(0, my_pad.copy())
|
|
42
|
+
B.append(my_pad.copy())
|
|
43
|
+
|
|
44
|
+
return h, B
|
|
45
|
+
|
|
46
|
+
def convolution(h, B):
|
|
47
|
+
|
|
48
|
+
# Rotate kernel and pad B
|
|
49
|
+
h, B = pad_matrix(h, B)
|
|
50
|
+
|
|
51
|
+
# Get kernel midpoint indexes
|
|
52
|
+
h_mid_arr, mid_idx = get_kernel_mid(h)
|
|
53
|
+
|
|
54
|
+
output = []
|
|
55
|
+
|
|
56
|
+
# Move the kernel across B
|
|
57
|
+
for i in range(h_mid_arr, len(B) - h_mid_arr):
|
|
58
|
+
|
|
59
|
+
row = []
|
|
60
|
+
|
|
61
|
+
for j in range(mid_idx, len(B[i]) - mid_idx):
|
|
62
|
+
|
|
63
|
+
total_B = 0
|
|
64
|
+
total_G = 0
|
|
65
|
+
total_R = 0
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
# Multiply kernel elements with B elements
|
|
69
|
+
for k in range(len(h)):
|
|
70
|
+
|
|
71
|
+
for l in range(len(h[k])):
|
|
72
|
+
|
|
73
|
+
pixel = B[i - h_mid_arr + k][j - mid_idx + l]
|
|
74
|
+
|
|
75
|
+
total_B += pixel[0] * h[k][l]
|
|
76
|
+
total_G += pixel[1] * h[k][l]
|
|
77
|
+
total_R += pixel[2] * h[k][l]
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
row.append([total_B, total_G, total_R])
|
|
81
|
+
|
|
82
|
+
output.append(row)
|
|
83
|
+
|
|
84
|
+
return output
|
|
85
|
+
|
|
86
|
+
# Client Code
|
|
87
|
+
'''
|
|
88
|
+
result = convolution(
|
|
89
|
+
[
|
|
90
|
+
[1/9, 1/9, 1/9],
|
|
91
|
+
[1/9, 1/9, 1/9],
|
|
92
|
+
[1/9, 1/9, 1/9]
|
|
93
|
+
],
|
|
94
|
+
[
|
|
95
|
+
[1, 0, 1, 2, 2],
|
|
96
|
+
[1, 1, 2, 2, 3],
|
|
97
|
+
[1, 2, 2, 6, 3],
|
|
98
|
+
[1, 1, 2, 2, 3]
|
|
99
|
+
]
|
|
100
|
+
)
|
|
101
|
+
|
|
102
|
+
print(result)
|
|
103
|
+
'''
|
|
@@ -0,0 +1,418 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
from math import *
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def matmax_multiply(A, B):
|
|
8
|
+
|
|
9
|
+
C = [
|
|
10
|
+
[
|
|
11
|
+
sum(A[i][j] * B[j][k] for j in range(len(B)))
|
|
12
|
+
for k in range(len(B[0]))
|
|
13
|
+
]
|
|
14
|
+
for i in range(len(A))
|
|
15
|
+
]
|
|
16
|
+
|
|
17
|
+
return C
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
# --------------------------------------------------
|
|
21
|
+
# HORIZONTAL SHEAR
|
|
22
|
+
# --------------------------------------------------
|
|
23
|
+
|
|
24
|
+
def horizontal_shear2D(image, k):
|
|
25
|
+
|
|
26
|
+
A = [
|
|
27
|
+
[1, k],
|
|
28
|
+
[0, 1]
|
|
29
|
+
]
|
|
30
|
+
|
|
31
|
+
height = image.shape[0]
|
|
32
|
+
width = image.shape[1]
|
|
33
|
+
|
|
34
|
+
new_width = width + int(abs(k) * height)
|
|
35
|
+
new_height = height
|
|
36
|
+
|
|
37
|
+
transformed_image = np.zeros(
|
|
38
|
+
(new_height, new_width, image.shape[2]),
|
|
39
|
+
dtype=image.dtype
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
for row in range(height):
|
|
43
|
+
|
|
44
|
+
for col in range(width):
|
|
45
|
+
|
|
46
|
+
coordinate = [
|
|
47
|
+
[col],
|
|
48
|
+
[row]
|
|
49
|
+
]
|
|
50
|
+
|
|
51
|
+
new_coordinate = matmax_multiply(A, coordinate)
|
|
52
|
+
|
|
53
|
+
new_col = int(new_coordinate[0][0])
|
|
54
|
+
new_row = int(new_coordinate[1][0])
|
|
55
|
+
|
|
56
|
+
if 0 <= new_col < new_width and 0 <= new_row < new_height:
|
|
57
|
+
|
|
58
|
+
transformed_image[new_row, new_col] = image[row, col]
|
|
59
|
+
|
|
60
|
+
return transformed_image
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
# --------------------------------------------------
|
|
64
|
+
# VERTICAL SHEAR
|
|
65
|
+
# --------------------------------------------------
|
|
66
|
+
|
|
67
|
+
def vertical_shear2D(image, k):
|
|
68
|
+
|
|
69
|
+
A = [
|
|
70
|
+
[1, 0],
|
|
71
|
+
[k, 1]
|
|
72
|
+
]
|
|
73
|
+
|
|
74
|
+
height = image.shape[0]
|
|
75
|
+
width = image.shape[1]
|
|
76
|
+
|
|
77
|
+
new_width = width
|
|
78
|
+
new_height = height + int(abs(k) * width)
|
|
79
|
+
|
|
80
|
+
transformed_image = np.zeros(
|
|
81
|
+
(new_height, new_width, image.shape[2]),
|
|
82
|
+
dtype=image.dtype
|
|
83
|
+
)
|
|
84
|
+
|
|
85
|
+
for row in range(height):
|
|
86
|
+
|
|
87
|
+
for col in range(width):
|
|
88
|
+
|
|
89
|
+
coordinate = [
|
|
90
|
+
[col],
|
|
91
|
+
[row]
|
|
92
|
+
]
|
|
93
|
+
|
|
94
|
+
new_coordinate = matmax_multiply(A, coordinate)
|
|
95
|
+
|
|
96
|
+
new_col = int(new_coordinate[0][0])
|
|
97
|
+
new_row = int(new_coordinate[1][0])
|
|
98
|
+
|
|
99
|
+
if 0 <= new_col < new_width and 0 <= new_row < new_height:
|
|
100
|
+
|
|
101
|
+
transformed_image[new_row, new_col] = image[row, col]
|
|
102
|
+
|
|
103
|
+
return transformed_image
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
# --------------------------------------------------
|
|
107
|
+
# SCALING
|
|
108
|
+
# --------------------------------------------------
|
|
109
|
+
|
|
110
|
+
def scale2D(image, Sx, Sy):
|
|
111
|
+
|
|
112
|
+
A = [
|
|
113
|
+
[Sx, 0],
|
|
114
|
+
[0, Sy]
|
|
115
|
+
]
|
|
116
|
+
|
|
117
|
+
height = image.shape[0]
|
|
118
|
+
width = image.shape[1]
|
|
119
|
+
|
|
120
|
+
new_width = int(width * abs(Sx))
|
|
121
|
+
new_height = int(height * abs(Sy))
|
|
122
|
+
|
|
123
|
+
transformed_image = np.zeros(
|
|
124
|
+
(new_height, new_width, image.shape[2]),
|
|
125
|
+
dtype=image.dtype
|
|
126
|
+
)
|
|
127
|
+
|
|
128
|
+
for row in range(height):
|
|
129
|
+
|
|
130
|
+
for col in range(width):
|
|
131
|
+
|
|
132
|
+
coordinate = [
|
|
133
|
+
[col],
|
|
134
|
+
[row]
|
|
135
|
+
]
|
|
136
|
+
|
|
137
|
+
new_coordinate = matmax_multiply(A, coordinate)
|
|
138
|
+
|
|
139
|
+
new_col = int(new_coordinate[0][0])
|
|
140
|
+
new_row = int(new_coordinate[1][0])
|
|
141
|
+
|
|
142
|
+
if 0 <= new_col < new_width and 0 <= new_row < new_height:
|
|
143
|
+
|
|
144
|
+
transformed_image[new_row, new_col] = image[row, col]
|
|
145
|
+
|
|
146
|
+
return transformed_image
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
def clockwise_rotate2D(image, ang_deg):
|
|
151
|
+
|
|
152
|
+
theta = radians(ang_deg)
|
|
153
|
+
|
|
154
|
+
c = cos(theta)
|
|
155
|
+
s = sin(theta)
|
|
156
|
+
|
|
157
|
+
A = [
|
|
158
|
+
[c, -s],
|
|
159
|
+
[s, c]
|
|
160
|
+
]
|
|
161
|
+
|
|
162
|
+
height = image.shape[0]
|
|
163
|
+
width = image.shape[1]
|
|
164
|
+
|
|
165
|
+
# Center of the original image
|
|
166
|
+
center_col = (width - 1) / 2
|
|
167
|
+
center_row = (height - 1) / 2
|
|
168
|
+
|
|
169
|
+
# Size of the rotated image
|
|
170
|
+
new_width = int(
|
|
171
|
+
abs(width * c) + abs(height * s)
|
|
172
|
+
)
|
|
173
|
+
|
|
174
|
+
new_height = int(
|
|
175
|
+
abs(height * c) + abs(width * s)
|
|
176
|
+
)
|
|
177
|
+
|
|
178
|
+
transformed_image = np.zeros(
|
|
179
|
+
(new_height, new_width, image.shape[2]),
|
|
180
|
+
dtype=image.dtype
|
|
181
|
+
)
|
|
182
|
+
|
|
183
|
+
# Center of the new image
|
|
184
|
+
new_center_col = (new_width - 1) / 2
|
|
185
|
+
new_center_row = (new_height - 1) / 2
|
|
186
|
+
|
|
187
|
+
for row in range(height):
|
|
188
|
+
|
|
189
|
+
for col in range(width):
|
|
190
|
+
|
|
191
|
+
# Move the pixel coordinate so that
|
|
192
|
+
# the image center becomes the origin
|
|
193
|
+
coordinate = [
|
|
194
|
+
[col - center_col],
|
|
195
|
+
[row - center_row]
|
|
196
|
+
]
|
|
197
|
+
|
|
198
|
+
new_coordinate = matmax_multiply(A, coordinate)
|
|
199
|
+
|
|
200
|
+
rotated_col = new_coordinate[0][0]
|
|
201
|
+
rotated_row = new_coordinate[1][0]
|
|
202
|
+
|
|
203
|
+
# Move the coordinate back to the image
|
|
204
|
+
# and place it relative to the new image center
|
|
205
|
+
new_col = int(round(rotated_col + new_center_col))
|
|
206
|
+
new_row = int(round(rotated_row + new_center_row))
|
|
207
|
+
|
|
208
|
+
if 0 <= new_col < new_width and 0 <= new_row < new_height:
|
|
209
|
+
|
|
210
|
+
transformed_image[new_row, new_col] = image[row, col]
|
|
211
|
+
|
|
212
|
+
return transformed_image
|
|
213
|
+
|
|
214
|
+
|
|
215
|
+
def antiClockwise_rotate2D(image, ang_deg):
|
|
216
|
+
|
|
217
|
+
theta = radians(ang_deg)
|
|
218
|
+
|
|
219
|
+
c = cos(theta)
|
|
220
|
+
s = sin(theta)
|
|
221
|
+
|
|
222
|
+
A = [
|
|
223
|
+
[c, s],
|
|
224
|
+
[-s, c]
|
|
225
|
+
]
|
|
226
|
+
|
|
227
|
+
height = image.shape[0]
|
|
228
|
+
width = image.shape[1]
|
|
229
|
+
|
|
230
|
+
# Center of the original image
|
|
231
|
+
center_col = (width - 1) / 2
|
|
232
|
+
center_row = (height - 1) / 2
|
|
233
|
+
|
|
234
|
+
# Size of the rotated image
|
|
235
|
+
new_width = int(
|
|
236
|
+
abs(width * c) + abs(height * s)
|
|
237
|
+
)
|
|
238
|
+
|
|
239
|
+
new_height = int(
|
|
240
|
+
abs(height * c) + abs(width * s)
|
|
241
|
+
)
|
|
242
|
+
|
|
243
|
+
transformed_image = np.zeros(
|
|
244
|
+
(new_height, new_width, image.shape[2]),
|
|
245
|
+
dtype=image.dtype
|
|
246
|
+
)
|
|
247
|
+
|
|
248
|
+
# Center of the new image
|
|
249
|
+
new_center_col = (new_width - 1) / 2
|
|
250
|
+
new_center_row = (new_height - 1) / 2
|
|
251
|
+
|
|
252
|
+
for row in range(height):
|
|
253
|
+
|
|
254
|
+
for col in range(width):
|
|
255
|
+
|
|
256
|
+
# Move the pixel coordinate so that
|
|
257
|
+
# the image center becomes the origin
|
|
258
|
+
coordinate = [
|
|
259
|
+
[col - center_col],
|
|
260
|
+
[row - center_row]
|
|
261
|
+
]
|
|
262
|
+
|
|
263
|
+
new_coordinate = matmax_multiply(A, coordinate)
|
|
264
|
+
|
|
265
|
+
rotated_col = new_coordinate[0][0]
|
|
266
|
+
rotated_row = new_coordinate[1][0]
|
|
267
|
+
|
|
268
|
+
# Move the coordinate back to the image
|
|
269
|
+
# and place it relative to the new image center
|
|
270
|
+
new_col = int(round(rotated_col + new_center_col))
|
|
271
|
+
new_row = int(round(rotated_row + new_center_row))
|
|
272
|
+
|
|
273
|
+
if 0 <= new_col < new_width and 0 <= new_row < new_height:
|
|
274
|
+
|
|
275
|
+
transformed_image[new_row, new_col] = image[row, col]
|
|
276
|
+
|
|
277
|
+
return transformed_image
|
|
278
|
+
|
|
279
|
+
|
|
280
|
+
|
|
281
|
+
# --------------------------------------------------
|
|
282
|
+
# FLIP LEFT ↔ RIGHT
|
|
283
|
+
# Reflection about the Y-axis
|
|
284
|
+
# --------------------------------------------------
|
|
285
|
+
|
|
286
|
+
def flipX2D(image):
|
|
287
|
+
|
|
288
|
+
A = [
|
|
289
|
+
[-1, 0],
|
|
290
|
+
[0, 1]
|
|
291
|
+
]
|
|
292
|
+
|
|
293
|
+
height = image.shape[0]
|
|
294
|
+
width = image.shape[1]
|
|
295
|
+
|
|
296
|
+
transformed_image = np.zeros(
|
|
297
|
+
image.shape,
|
|
298
|
+
dtype=image.dtype
|
|
299
|
+
)
|
|
300
|
+
|
|
301
|
+
for row in range(height):
|
|
302
|
+
|
|
303
|
+
for col in range(width):
|
|
304
|
+
|
|
305
|
+
coordinate = [
|
|
306
|
+
[col],
|
|
307
|
+
[row]
|
|
308
|
+
]
|
|
309
|
+
|
|
310
|
+
new_coordinate = matmax_multiply(A, coordinate)
|
|
311
|
+
|
|
312
|
+
new_col = int(
|
|
313
|
+
new_coordinate[0][0] + (width - 1)
|
|
314
|
+
)
|
|
315
|
+
|
|
316
|
+
new_row = int(
|
|
317
|
+
new_coordinate[1][0]
|
|
318
|
+
)
|
|
319
|
+
|
|
320
|
+
if 0 <= new_col < width and 0 <= new_row < height:
|
|
321
|
+
|
|
322
|
+
transformed_image[new_row, new_col] = image[row, col]
|
|
323
|
+
|
|
324
|
+
return transformed_image
|
|
325
|
+
|
|
326
|
+
|
|
327
|
+
# --------------------------------------------------
|
|
328
|
+
# FLIP TOP ↔ BOTTOM
|
|
329
|
+
# Reflection about the X-axis
|
|
330
|
+
# --------------------------------------------------
|
|
331
|
+
|
|
332
|
+
def flipY2D(image):
|
|
333
|
+
|
|
334
|
+
A = [
|
|
335
|
+
[1, 0],
|
|
336
|
+
[0, -1]
|
|
337
|
+
]
|
|
338
|
+
|
|
339
|
+
height = image.shape[0]
|
|
340
|
+
width = image.shape[1]
|
|
341
|
+
|
|
342
|
+
transformed_image = np.zeros(
|
|
343
|
+
image.shape,
|
|
344
|
+
dtype=image.dtype
|
|
345
|
+
)
|
|
346
|
+
|
|
347
|
+
for row in range(height):
|
|
348
|
+
|
|
349
|
+
for col in range(width):
|
|
350
|
+
|
|
351
|
+
coordinate = [
|
|
352
|
+
[col],
|
|
353
|
+
[row]
|
|
354
|
+
]
|
|
355
|
+
|
|
356
|
+
new_coordinate = matmax_multiply(A, coordinate)
|
|
357
|
+
|
|
358
|
+
new_col = int(
|
|
359
|
+
new_coordinate[0][0]
|
|
360
|
+
)
|
|
361
|
+
|
|
362
|
+
new_row = int(
|
|
363
|
+
new_coordinate[1][0] + (height - 1)
|
|
364
|
+
)
|
|
365
|
+
|
|
366
|
+
if 0 <= new_col < width and 0 <= new_row < height:
|
|
367
|
+
|
|
368
|
+
transformed_image[new_row, new_col] = image[row, col]
|
|
369
|
+
|
|
370
|
+
return transformed_image
|
|
371
|
+
|
|
372
|
+
|
|
373
|
+
|
|
374
|
+
def translate2D(image, translation):
|
|
375
|
+
|
|
376
|
+
tx = translation[0]
|
|
377
|
+
ty = translation[1]
|
|
378
|
+
|
|
379
|
+
A = [
|
|
380
|
+
[1, 0],
|
|
381
|
+
[0, 1]
|
|
382
|
+
]
|
|
383
|
+
|
|
384
|
+
height = image.shape[0]
|
|
385
|
+
width = image.shape[1]
|
|
386
|
+
|
|
387
|
+
transformed_image = np.zeros(
|
|
388
|
+
image.shape,
|
|
389
|
+
dtype=image.dtype
|
|
390
|
+
)
|
|
391
|
+
|
|
392
|
+
for row in range(height):
|
|
393
|
+
|
|
394
|
+
for col in range(width):
|
|
395
|
+
|
|
396
|
+
coordinate = [
|
|
397
|
+
[col],
|
|
398
|
+
[row]
|
|
399
|
+
]
|
|
400
|
+
|
|
401
|
+
new_coordinate = matmax_multiply(A, coordinate)
|
|
402
|
+
|
|
403
|
+
new_col = int(
|
|
404
|
+
new_coordinate[0][0] + tx
|
|
405
|
+
)
|
|
406
|
+
|
|
407
|
+
new_row = int(
|
|
408
|
+
new_coordinate[1][0] + ty
|
|
409
|
+
)
|
|
410
|
+
|
|
411
|
+
if 0 <= new_col < width and 0 <= new_row < height:
|
|
412
|
+
|
|
413
|
+
transformed_image[new_row, new_col] = image[row, col]
|
|
414
|
+
|
|
415
|
+
return transformed_image
|
|
416
|
+
|
|
417
|
+
|
|
418
|
+
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
from math import cos, radians, sin
|
|
2
|
+
|
|
3
|
+
# A is a transformation matrix
|
|
4
|
+
def matvec_multiply(A, x):
|
|
5
|
+
b = [ sum (x[j] * A[i][j] for j in range(len(x)))for i in range(len(A))]
|
|
6
|
+
return b
|
|
7
|
+
|
|
8
|
+
# u is a vector
|
|
9
|
+
def horizontal_shear2D(u, k):
|
|
10
|
+
# k is shear factor, u is vector
|
|
11
|
+
A = [[1, k], [0, 1]] # A is horizontal shear matrix
|
|
12
|
+
b = matvec_multiply(A, u)
|
|
13
|
+
return b
|
|
14
|
+
#print(horizontal_shear2D([0, 2], -1))
|
|
15
|
+
|
|
16
|
+
def vertical_shear2D(u, k):
|
|
17
|
+
A = [[1, 0], [k, 1]] # A is vertical shear matrix
|
|
18
|
+
b = matvec_multiply(A, u)
|
|
19
|
+
return b
|
|
20
|
+
#print(vertical_shear2D([2, 2], 1))
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def scale2D(u, Sx, Sy):
|
|
24
|
+
#Sx and Sy are scalar constants
|
|
25
|
+
A = [[Sx, 0], [0, Sy]] # A is scale matrix
|
|
26
|
+
b = matvec_multiply(A, u)
|
|
27
|
+
return b
|
|
28
|
+
#print(scale([2, 2], 5, 0.5))
|
|
29
|
+
|
|
30
|
+
def clockwise_rotate2D(u, ang_deg):
|
|
31
|
+
theta = radians(ang_deg)
|
|
32
|
+
c = cos(theta)
|
|
33
|
+
s = sin(theta)
|
|
34
|
+
|
|
35
|
+
A = [[c, s],[-s, c]]
|
|
36
|
+
|
|
37
|
+
#A = [[cos(radians(ang_deg)), sin(radians(ang_deg))], [-sin(radians(ang_deg)), cos(radians(ang_deg))]]
|
|
38
|
+
b = matvec_multiply(A, u)
|
|
39
|
+
return b
|
|
40
|
+
#print(clockwise_rotate2D([1, 2], 30))
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def antiClockwise_rotate2D(u, ang_deg):
|
|
44
|
+
# A = [[cos(radians(ang_deg)), -sin(radians(ang_deg))], [sin(radians(ang_deg)), cos(radians(ang_deg))]]
|
|
45
|
+
theta = radians(ang_deg)
|
|
46
|
+
c = cos(theta)
|
|
47
|
+
s = sin(theta)
|
|
48
|
+
A = [[c, -s],[s, c]]
|
|
49
|
+
b = matvec_multiply(A, u)
|
|
50
|
+
return b
|
|
51
|
+
#print(clockwise_rotate2D([1, 2], 30))
|
|
52
|
+
|
|
53
|
+
def flipX2D(u):
|
|
54
|
+
#reflect along the y-axis
|
|
55
|
+
A = [[-1, 0], [0, 1]]
|
|
56
|
+
b = matvec_multiply(A, u)
|
|
57
|
+
return b
|
|
58
|
+
#print(flipX2D([1, 2]))
|
|
59
|
+
|
|
60
|
+
def flipY2D(u):
|
|
61
|
+
#reflect along the x-axis
|
|
62
|
+
A = [[1, 0], [0, -1]]
|
|
63
|
+
b = matvec_multiply(A, u)
|
|
64
|
+
return b
|
|
65
|
+
#print(flipY2D([1, 2]))
|
|
66
|
+
|
|
67
|
+
def translate2D(u, translation):
|
|
68
|
+
# translation is a 2d vector
|
|
69
|
+
A = [[1, 0], [0, 1]]
|
|
70
|
+
b = matvec_multiply(A, u)
|
|
71
|
+
translated = [b[0] + translation[0], b[1] + translation[1]]
|
|
72
|
+
return translated
|
|
73
|
+
#print(translate2D([1, 2], [1, -1]))
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import random
|
|
2
|
+
import numpy as np
|
|
3
|
+
import copy
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def unif_noise(image, lower, upper):
|
|
7
|
+
for i in range(image.shape[0]):
|
|
8
|
+
for j in range(image.shape[1]):
|
|
9
|
+
noise = random.uniform(lower, upper)
|
|
10
|
+
noisy_pixel = image[i, j] + noise
|
|
11
|
+
noisy_pixel = np.clip(noisy_pixel, 0, 255)
|
|
12
|
+
image[i, j] = np.uint8(np.round(noisy_pixel))
|
|
13
|
+
|
|
14
|
+
return image
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def gauss_noise(image, std):
|
|
19
|
+
for i in range(image.shape[0]):
|
|
20
|
+
for j in range(image.shape[1]):
|
|
21
|
+
noise = random.gauss(mu=0.0, sigma=std)
|
|
22
|
+
noisy_pixel = image[i, j] + noise
|
|
23
|
+
noisy_pixel = np.clip(noisy_pixel, 0, 255)
|
|
24
|
+
image[i, j] = np.uint8(np.round(noisy_pixel))
|
|
25
|
+
return image
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def salt_pepper_noise(image, prob_pepper, prob_salt):
|
|
31
|
+
|
|
32
|
+
copied_image = copy.deepcopy(image)
|
|
33
|
+
|
|
34
|
+
for i in range(len(copied_image)):
|
|
35
|
+
for j in range(len(copied_image[i])):
|
|
36
|
+
|
|
37
|
+
rand_num = random.random()
|
|
38
|
+
|
|
39
|
+
if rand_num < prob_pepper:
|
|
40
|
+
copied_image[i][j] = 0
|
|
41
|
+
|
|
42
|
+
elif rand_num > 1 - prob_salt:
|
|
43
|
+
copied_image[i][j] = 255
|
|
44
|
+
|
|
45
|
+
return copied_image
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import copy
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
def pad_matrix(B, row_count, col_count):
|
|
5
|
+
|
|
6
|
+
window_size = row_count * col_count
|
|
7
|
+
|
|
8
|
+
side_pad = (col_count - 1) // 2
|
|
9
|
+
across_pad = (row_count - 1) // 2
|
|
10
|
+
|
|
11
|
+
# Side Padding
|
|
12
|
+
for i in range(len(B)):
|
|
13
|
+
for j in range(side_pad):
|
|
14
|
+
B[i].append([0, 0, 0])
|
|
15
|
+
B[i].insert(0, [0, 0, 0])
|
|
16
|
+
|
|
17
|
+
# Create top/bottom padding row
|
|
18
|
+
my_pad = [[0, 0, 0] for i in range(len(B[0]))]
|
|
19
|
+
|
|
20
|
+
# Top and bottom padding
|
|
21
|
+
for i in range(across_pad):
|
|
22
|
+
B.insert(0, my_pad.copy())
|
|
23
|
+
B.append(my_pad.copy())
|
|
24
|
+
|
|
25
|
+
return B, window_size
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def med_filter(input_image, row_count, col_count):
|
|
29
|
+
|
|
30
|
+
# Make a copy so the original image is not modified
|
|
31
|
+
image_copy = copy.deepcopy(input_image)
|
|
32
|
+
|
|
33
|
+
# Pad the image
|
|
34
|
+
B, window_size = pad_matrix(
|
|
35
|
+
image_copy,
|
|
36
|
+
row_count,
|
|
37
|
+
col_count
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
output_image = []
|
|
41
|
+
|
|
42
|
+
# Move the window across B
|
|
43
|
+
for i in range(len(B) - row_count + 1):
|
|
44
|
+
|
|
45
|
+
row = []
|
|
46
|
+
|
|
47
|
+
for j in range(len(B[0]) - col_count + 1):
|
|
48
|
+
|
|
49
|
+
# Lists for the three RGB channels
|
|
50
|
+
red_values = []
|
|
51
|
+
green_values = []
|
|
52
|
+
blue_values = []
|
|
53
|
+
|
|
54
|
+
# Extract the window
|
|
55
|
+
for k in range(row_count):
|
|
56
|
+
for l in range(col_count):
|
|
57
|
+
|
|
58
|
+
pixel = B[i + k][j + l]
|
|
59
|
+
|
|
60
|
+
red_values.append(pixel[0])
|
|
61
|
+
green_values.append(pixel[1])
|
|
62
|
+
blue_values.append(pixel[2])
|
|
63
|
+
|
|
64
|
+
# Sort each channel
|
|
65
|
+
red_values.sort()
|
|
66
|
+
green_values.sort()
|
|
67
|
+
blue_values.sort()
|
|
68
|
+
|
|
69
|
+
# Get median of each channel
|
|
70
|
+
median_red = red_values[window_size // 2]
|
|
71
|
+
median_green = green_values[window_size // 2]
|
|
72
|
+
median_blue = blue_values[window_size // 2]
|
|
73
|
+
|
|
74
|
+
# Construct the output pixel
|
|
75
|
+
row.append([
|
|
76
|
+
median_red,
|
|
77
|
+
median_green,
|
|
78
|
+
median_blue
|
|
79
|
+
])
|
|
80
|
+
|
|
81
|
+
output_image.append(row)
|
|
82
|
+
|
|
83
|
+
return output_image
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import math
|
|
2
|
+
|
|
3
|
+
# Add more than 2 vectors
|
|
4
|
+
def add2D_multi(*vectors):
|
|
5
|
+
return (sum([v[0] for v in vectors]), sum([v[1] for v in vectors]))
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def vec_add2D(u, v):
|
|
10
|
+
w = [u[0] + v[0], u[1] + v[1]]
|
|
11
|
+
return w
|
|
12
|
+
#print(vec_add([2, 4], [5, 10]))
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def vec_subtract2D(u, v):
|
|
16
|
+
w = [u[0]- v[0], u[1] - v[1]]
|
|
17
|
+
return w
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def mag_v2D(v):
|
|
21
|
+
# return math.floor(math.sqrt(v[0]**2 + v[1]**2))
|
|
22
|
+
return math.sqrt(v[0]**2 + v[1]**2)
|
|
23
|
+
#print(mag_v([5, 10]))
|
|
24
|
+
|
|
25
|
+
def dis_vecs2D(u, v):
|
|
26
|
+
return math.sqrt((v[0]-u[0])**2 + (v[1]-u[1])**2)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def dot_product2D(u, v):
|
|
31
|
+
uv = u[0] * v[0] + u[1] * v[1]
|
|
32
|
+
return uv
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def ang_vectors2D(u, v):
|
|
37
|
+
uv = dot_product2D(u, v)
|
|
38
|
+
cos_val = uv/(mag_v2D(u) * mag_v2D(v))
|
|
39
|
+
ang_between = math.degrees(math.acos(cos_val))
|
|
40
|
+
return ang_between
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def scale(weight, v):
|
|
45
|
+
v = [weight * i for i in v]
|
|
46
|
+
return v
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
# use for single polar coordinates
|
|
51
|
+
def pole_to_cart(radius, angle):
|
|
52
|
+
x = radius * math.cos(math.radians(angle))
|
|
53
|
+
y = radius * math.sin(math.radians(angle))
|
|
54
|
+
return (x, y)
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def polar_vector_to_cart(polar_vector):
|
|
58
|
+
length, angle = polar_vector
|
|
59
|
+
return (length*math.cos(angle), length*math.sin(angle))
|
|
60
|
+
|
|
61
|
+
# use for single vector
|
|
62
|
+
def cart_to_pole(x, y):
|
|
63
|
+
radius = math.sqrt(x**2 + y**2)
|
|
64
|
+
# tan_theta = y/x
|
|
65
|
+
theta = math.atan2(y, x)
|
|
66
|
+
return (radius, theta)
|
|
67
|
+
|
|
68
|
+
# use for multiple vectors
|
|
69
|
+
def cart_to_polar(vector):
|
|
70
|
+
x, y = vector[0], vector[1]
|
|
71
|
+
angle = math.atan2(y,x)
|
|
72
|
+
return (mag_v2D(vector), angle)
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def rotate(angle, vectors):
|
|
76
|
+
polars = [cart_to_polar(v) for v in vectors]
|
|
77
|
+
return [polar_vector_to_cart((length, a+angle)) for length,a in polars]
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def translate(translation, vectors):
|
|
81
|
+
return [add2D_multi(translation, v) for v in vectors]
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def cross_prod(u, v):
|
|
85
|
+
u_cross_v = [u[1]* v[2] - u[2] * v[1] , u[2] * v[0] - u[0] * v[2] , u[0] * v[1] - u[1] * v[0] ]
|
|
86
|
+
return u_cross_v
|
|
87
|
+
#print(cross_prod([1, 2, -1], [2, -1, 2]))
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["hatchling"]
|
|
3
|
+
build-backend = "hatchling.build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "optiklear"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "A Python library for mathematical operations and image processing"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.10"
|
|
11
|
+
dependencies = [
|
|
12
|
+
"numpy",
|
|
13
|
+
]
|
|
14
|
+
|
|
15
|
+
[tool.hatch.build.targets.wheel]
|
|
16
|
+
packages = ["optiklear"]
|