Skip to content

Commit e1fc93e

Browse files
authored
Release v0.2.1 (#22)
2 parents 5aa7431 + 6b24ce7 commit e1fc93e

File tree

19 files changed

+583
-37
lines changed

19 files changed

+583
-37
lines changed

opsi/frontend/templates/settings.html

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,13 @@ <h2>Import/Export</h2>
2828
<input id="import-button" type="button" value="Import" />
2929
<input id="export-button" type="button" value="Export" />
3030
</div>
31+
<div class="preference">
32+
<h2>Import Camera Calibration</h2>
33+
<form id="import-calibration-form" enctype="multipart/form-data" method="post" name="import-form">
34+
<input type="file" class="bfi" required />
35+
</form>
36+
<input id="import-calibration-button" type="button" value="Import" />
37+
</div>
3138
<div class="preference network-settings">
3239
<h2>Network Config</h2>
3340
<div id="net-normal-settings">

opsi/frontend/www/scripts/settings.js

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,30 @@ $(document).ready(function() {
9292
};
9393
fileReader.readAsText(form[0].files[0]);
9494
});
95+
$("#import-calibration-button").click(function(event) {
96+
event.preventDefault();
97+
var form = $("#import-calibration-form")[0];
98+
var data = new FormData();
99+
data.append("file", form[0].files[0]);
100+
$("#update-button").prop("disabled", true);
101+
setIcons("spinner")
102+
$.ajax({
103+
type: "POST",
104+
enctype: "multipart/form-data",
105+
url: "/api/calibration",
106+
data: data,
107+
processData: false,
108+
contentType: false,
109+
cache: false,
110+
success: function(data) {
111+
setIcons("check")
112+
},
113+
error: function(e) {
114+
console.log(e);
115+
setIcons("cross")
116+
}
117+
});
118+
});
95119
$("#export-button").click(function() {
96120
$("<a />", {
97121
download: "nodetree.json",

opsi/manager/manager.py

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -69,12 +69,7 @@ def is_valid_function(cls, module):
6969
def closure(func):
7070
# Todo: are there any other times we don't want to register a Function?
7171
# This is important because the default is registering every single Function
72-
return (
73-
isfunction(func)
74-
and (not func.disabled)
75-
# If a module imports a Function from another module, do not register that Function
76-
and (inspect.getmodule(func) == module or func.force_enabled)
77-
)
72+
return isfunction(func) and not func.disabled
7873

7974
return closure
8075

opsi/manager/manager_schema.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,6 @@ class Function:
3737
require_restart: bool = False
3838
always_restart: bool = False
3939
disabled = False
40-
force_enabled = False
4140

4241
SettingTypes: List[Field]
4342
InputTypes: Dict[str, Type]
@@ -162,6 +161,8 @@ def _private_validate_settings(cls, settings):
162161

163162
def isfunction(func):
164163
try:
164+
if func is Function:
165+
return False
165166
return issubclass(func, Function)
166167
except TypeError: # func is not a type
167168
return False

opsi/modules/color.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -344,3 +344,24 @@ class Outputs:
344344
def run(self, inputs):
345345
img = inputs.img.resize(Point(self.settings.width, self.settings.height))
346346
return self.Outputs(img=img)
347+
348+
349+
class ColorBalance(Function):
350+
@dataclass
351+
class Settings:
352+
red_balance: Slide(min=0, max=100)
353+
blue_balance: Slide(min=0, max=100)
354+
355+
@dataclass
356+
class Inputs:
357+
img: Mat
358+
359+
@dataclass
360+
class Outputs:
361+
img: Mat
362+
363+
def run(self, inputs):
364+
img = inputs.img.color_balance(
365+
self.settings.red_balance / 100.0, self.settings.blue_balance / 100.0
366+
)
367+
return self.Outputs(img=img)

opsi/modules/contour.py

Lines changed: 70 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,8 @@
66
import numpy as np
77

88
from opsi.manager.manager_schema import Function
9-
from opsi.manager.types import Slide
109
from opsi.util.cv import Contours, Mat, MatBW, Point
10+
from opsi.util.cv.shape import Corners
1111

1212
__package__ = "opsi.contours"
1313
__version__ = "0.123"
@@ -79,7 +79,7 @@ class Outputs:
7979
def run(self, inputs):
8080
if len(inputs.contours.l) == 0:
8181
return self.Outputs(center=None, success=False, visual=inputs.img)
82-
82+
res = inputs.contours.l[0].res
8383
center = inputs.contours.centroid_of_all
8484

8585
if self.settings.draw:
@@ -99,7 +99,11 @@ def run(self, inputs):
9999
else:
100100
img = None
101101

102-
return self.Outputs(center=center, success=True, visual=img)
102+
scaled_center = Point(
103+
x=((center.x * 2) / res.x) - 1, y=((center.y * 2) / res.y) - 1
104+
)
105+
106+
return self.Outputs(center=scaled_center, success=True, visual=img)
103107

104108

105109
class FindAngle(Function):
@@ -118,39 +122,91 @@ def calculate_focal_length(cls, diagonalFOV, horizontalAspect, verticalAspect):
118122
math.atan(math.tan(diagonalView / 2) * (horizontalAspect / diagonalAspect))
119123
* 2
120124
)
121-
# verticalView = math.atan(math.tan(diagonalView/2) * (verticalAspect / diagonalAspect)) * 2
125+
verticalView = (
126+
math.atan(math.tan(diagonalView / 2) * (verticalAspect / diagonalAspect))
127+
* 2
128+
)
122129

123130
# Since point is -1 <= (x, y) <= 1: width, height = 2; center = (0, 0)
124131

125132
# Focal Length calculations: https://docs.google.com/presentation/d/1ediRsI-oR3-kwawFJZ34_ZTlQS2SDBLjZasjzZ-eXbQ/pub?start=false&loop=false&slide=id.g12c083cffa_0_165
126133
H_FOCAL_LENGTH = 2 / (2 * math.tan((horizontalView / 2)))
127-
# V_FOCAL_LENGTH = 2 / (2*math.tan((verticalView/2)))
134+
V_FOCAL_LENGTH = 2 / (2 * math.tan((verticalView / 2)))
128135

129-
return H_FOCAL_LENGTH
136+
return H_FOCAL_LENGTH, V_FOCAL_LENGTH
130137

131138
@dataclass
132139
class Settings:
140+
mode: ("Degrees", "Radians") = "Radians"
133141
diagonalFOV: float = 68.5
134142

135143
@dataclass
136144
class Inputs:
137-
pnt: Point
145+
point: Point
138146
img: Mat
139147

140148
@dataclass
141149
class Outputs:
142-
radians: float
150+
angle: Point
143151

144152
def run(self, inputs):
145-
width = inputs.img.shape[1]
146-
height = inputs.img.shape[0]
153+
width = inputs.img.res.x
154+
height = inputs.img.res.y
147155

148156
center_x = 0
149-
x = inputs.point[0]
157+
x = inputs.point.x
158+
y = inputs.point.y
150159

151-
H_FOCAL_LENGTH = self.calculate_focal_length(
160+
h_focal_length, v_focal_length = self.calculate_focal_length(
152161
self.settings.diagonalFOV, width, height
153162
)
154-
radians = math.atan2(x, H_FOCAL_LENGTH)
155163

156-
return self.Outputs(radians=radians)
164+
if self.settings.mode == "Radians":
165+
radians = Point(
166+
x=math.atan2(x, h_focal_length), y=math.atan2(y, v_focal_length)
167+
)
168+
return self.Outputs(angle=radians)
169+
else:
170+
degrees = Point(
171+
x=math.degrees(math.atan2(x, h_focal_length)),
172+
y=math.degrees(math.atan2(y, v_focal_length)),
173+
)
174+
return self.Outputs(angle=degrees)
175+
176+
177+
class FindCorners(Function):
178+
@dataclass
179+
class Inputs:
180+
contours: Contours
181+
182+
@dataclass
183+
class Outputs:
184+
corners: Corners
185+
success: bool
186+
187+
def run(self, inputs):
188+
if len(inputs.contours.l) == 0:
189+
return self.Outputs(corners=None, success=False)
190+
191+
cnt = inputs.contours.l[0]
192+
193+
ret, corners = cnt.corners
194+
195+
return self.Outputs(corners=corners, success=ret)
196+
197+
198+
class FindArea(Function):
199+
@dataclass
200+
class Inputs:
201+
contours: Contours
202+
203+
@dataclass
204+
class Outputs:
205+
area: float
206+
207+
def run(self, inputs):
208+
if inputs.contours is None or len(inputs.contours.l) == 0:
209+
return self.Outputs(area=0)
210+
else:
211+
return self.Outputs(area=sum([cnt.area for cnt in inputs.contours.l]))
212+

opsi/modules/draw/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
from opsi.util.cv import Contours, Mat, MatBW
99

1010
from .fps import DrawFPS
11-
from .shapes import DrawCircles, DrawSegments
11+
from .shapes import DrawCircles, DrawCorners, DrawSegments
1212

1313
__package__ = "opsi.draw"
1414
__version__ = "0.123"

opsi/modules/draw/fps.py

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,8 +32,6 @@ def fps(self):
3232

3333

3434
class DrawFPS(Function):
35-
force_enabled = True
36-
3735
def on_start(self):
3836
self.f = FPS()
3937
self.f.start()

opsi/modules/draw/shapes.py

Lines changed: 28 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,10 @@
55

66
from opsi.manager.manager_schema import Function
77
from opsi.util.cv import Mat
8-
from opsi.util.cv.shape import Circles, Segments
8+
from opsi.util.cv.shape import Circles, Corners, Segments
99

1010

1111
class DrawCircles(Function):
12-
force_enabled = True
13-
1412
@dataclass
1513
class Inputs:
1614
circles: Circles
@@ -43,8 +41,6 @@ def run(self, inputs):
4341

4442

4543
class DrawSegments(Function):
46-
force_enabled = True
47-
4844
@dataclass
4945
class Inputs:
5046
lines: Segments
@@ -67,3 +63,30 @@ def run(self, inputs):
6763

6864
draw = Mat(draw)
6965
return self.Outputs(img=draw)
66+
67+
68+
class DrawCorners(Function):
69+
force_enabled = True
70+
71+
@dataclass
72+
class Inputs:
73+
corners: Corners
74+
img: Mat
75+
76+
@dataclass
77+
class Outputs:
78+
img: Mat
79+
80+
def run(self, inputs):
81+
# If there are no circles return the input image
82+
if inputs.corners is None:
83+
return self.Outputs(img=inputs.img)
84+
img = np.copy(inputs.img.mat.img)
85+
86+
for corner in inputs.corners:
87+
cv2.circle(
88+
img, (int(corner.x), int(corner.y)), 5, (0, 0, 255), 3,
89+
)
90+
img = Mat(img)
91+
92+
return self.Outputs(img=img)

opsi/modules/gpio.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
from dataclasses import dataclass
22

33
import gpiozero
4-
54
from opsi.manager.manager_schema import Function
65

76
__package__ = "opsi.gpio"

0 commit comments

Comments
 (0)