juham-watermeter 0.0.7__tar.gz → 0.0.9__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.
- {juham_watermeter-0.0.7 → juham_watermeter-0.0.9}/CHANGELOG.rst +16 -0
- {juham_watermeter-0.0.7/juham_watermeter.egg-info → juham_watermeter-0.0.9}/PKG-INFO +5 -27
- {juham_watermeter-0.0.7 → juham_watermeter-0.0.9}/juham_watermeter/watermeter_imgdiff.py +1 -5
- {juham_watermeter-0.0.7 → juham_watermeter-0.0.9}/juham_watermeter/webcamera.py +1 -6
- {juham_watermeter-0.0.7 → juham_watermeter-0.0.9/juham_watermeter.egg-info}/PKG-INFO +5 -27
- {juham_watermeter-0.0.7 → juham_watermeter-0.0.9}/juham_watermeter.egg-info/SOURCES.txt +3 -1
- {juham_watermeter-0.0.7 → juham_watermeter-0.0.9}/juham_watermeter.egg-info/requires.txt +1 -1
- {juham_watermeter-0.0.7 → juham_watermeter-0.0.9}/pyproject.toml +3 -4
- juham_watermeter-0.0.9/tests/test_leakdetector.py +124 -0
- juham_watermeter-0.0.9/tests/test_webcamera.py +100 -0
- {juham_watermeter-0.0.7 → juham_watermeter-0.0.9}/LICENSE.rst +0 -0
- {juham_watermeter-0.0.7 → juham_watermeter-0.0.9}/MANIFEST.in +0 -0
- {juham_watermeter-0.0.7 → juham_watermeter-0.0.9}/README.rst +0 -0
- {juham_watermeter-0.0.7 → juham_watermeter-0.0.9}/juham_watermeter/__init__.py +0 -0
- {juham_watermeter-0.0.7 → juham_watermeter-0.0.9}/juham_watermeter/leakdetector.py +0 -0
- {juham_watermeter-0.0.7 → juham_watermeter-0.0.9}/juham_watermeter/py.typed +0 -0
- {juham_watermeter-0.0.7 → juham_watermeter-0.0.9}/juham_watermeter/watermeter_ocr.py +0 -0
- {juham_watermeter-0.0.7 → juham_watermeter-0.0.9}/juham_watermeter/watermeter_ts.py +0 -0
- {juham_watermeter-0.0.7 → juham_watermeter-0.0.9}/juham_watermeter.egg-info/dependency_links.txt +0 -0
- {juham_watermeter-0.0.7 → juham_watermeter-0.0.9}/juham_watermeter.egg-info/entry_points.txt +0 -0
- {juham_watermeter-0.0.7 → juham_watermeter-0.0.9}/juham_watermeter.egg-info/top_level.txt +0 -0
- {juham_watermeter-0.0.7 → juham_watermeter-0.0.9}/setup.cfg +0 -0
- {juham_watermeter-0.0.7 → juham_watermeter-0.0.9}/tests/__init__.py +0 -0
- {juham_watermeter-0.0.7 → juham_watermeter-0.0.9}/tests/test_watermeter.py +0 -0
@@ -1,6 +1,22 @@
|
|
1
1
|
Changelog
|
2
2
|
=========
|
3
3
|
|
4
|
+
|
5
|
+
|
6
|
+
[0.0.9] - June 1, 2025
|
7
|
+
------------------------
|
8
|
+
|
9
|
+
- Duplicate interval attribute, defined in both derived and the base class, fixed.
|
10
|
+
The default time interval increased from 30 s to 10 min.
|
11
|
+
|
12
|
+
|
13
|
+
|
14
|
+
[0.0.8] - April 18, 2025
|
15
|
+
------------------------
|
16
|
+
|
17
|
+
- Updated pyproject.toml to comply with the new SPDX expression for packaging standards
|
18
|
+
|
19
|
+
|
4
20
|
[0.0.7] - March 09, 2025
|
5
21
|
------------------------
|
6
22
|
|
@@ -1,32 +1,10 @@
|
|
1
|
-
Metadata-Version: 2.
|
1
|
+
Metadata-Version: 2.4
|
2
2
|
Name: juham-watermeter
|
3
|
-
Version: 0.0.
|
3
|
+
Version: 0.0.9
|
4
4
|
Summary: Web-camera based watermeter for Juham
|
5
5
|
Author-email: J Meskanen <juham.api@gmail.com>
|
6
6
|
Maintainer-email: "J. Meskanen" <juham.api@gmail.com>
|
7
|
-
License: MIT
|
8
|
-
===========
|
9
|
-
|
10
|
-
Copyright (c) 2024, Juha Meskanen
|
11
|
-
|
12
|
-
Permission is hereby granted, free of charge, to any person obtaining a copy
|
13
|
-
of this software and associated documentation files (the "Software"), to deal
|
14
|
-
in the Software without restriction, including without limitation the rights
|
15
|
-
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
16
|
-
copies of the Software, and to permit persons to whom the Software is
|
17
|
-
furnished to do so, subject to the following conditions:
|
18
|
-
|
19
|
-
The above copyright notice and this permission notice shall be included in all
|
20
|
-
copies or substantial portions of the Software.
|
21
|
-
|
22
|
-
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
23
|
-
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
24
|
-
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
25
|
-
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
26
|
-
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
27
|
-
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
28
|
-
|
29
|
-
|
7
|
+
License-Expression: MIT
|
30
8
|
Project-URL: Homepage, https://gitlab.com/juham/juham
|
31
9
|
Project-URL: Bug Reports, https://gitlab.com/juham/juham
|
32
10
|
Project-URL: Funding, https://meskanen.com
|
@@ -36,18 +14,18 @@ Keywords: object-oriented,plugin,framework,watermeter,home automation
|
|
36
14
|
Classifier: Development Status :: 2 - Pre-Alpha
|
37
15
|
Classifier: Intended Audience :: Developers
|
38
16
|
Classifier: Topic :: Software Development
|
39
|
-
Classifier: License :: OSI Approved :: MIT License
|
40
17
|
Classifier: Programming Language :: Python :: 3.8
|
41
18
|
Requires-Python: >=3.8
|
42
19
|
Description-Content-Type: text/markdown
|
43
20
|
License-File: LICENSE.rst
|
44
|
-
Requires-Dist: juham-core>=0.1.
|
21
|
+
Requires-Dist: juham-core>=0.1.5
|
45
22
|
Requires-Dist: numpy
|
46
23
|
Requires-Dist: Pillow>=10.4.0
|
47
24
|
Requires-Dist: opencv-python-headless>=4.10.0
|
48
25
|
Requires-Dist: pytesseract>=0.3.13
|
49
26
|
Provides-Extra: dev
|
50
27
|
Requires-Dist: check-manifest; extra == "dev"
|
28
|
+
Dynamic: license-file
|
51
29
|
|
52
30
|
Watermeter plugin for Juham™
|
53
31
|
=============================
|
@@ -143,10 +143,6 @@ class WaterMeterThreadImgDiff(WebCameraThread):
|
|
143
143
|
# Return a value between 0.0 (identical) and 1.0 (maximally different)
|
144
144
|
return min(max(change_area, 0.0), 1.0)
|
145
145
|
|
146
|
-
@override
|
147
|
-
def update_interval(self) -> float:
|
148
|
-
return self._interval
|
149
|
-
|
150
146
|
def upload_file(self, filename: str) -> None:
|
151
147
|
"""Upload the given filename to the ftp server, if the server is configured.
|
152
148
|
|
@@ -256,7 +252,7 @@ class WaterMeterImgDiff(WebCamera):
|
|
256
252
|
]
|
257
253
|
|
258
254
|
_workerThreadId: str = WaterMeterThreadImgDiff.get_class_id()
|
259
|
-
update_interval: float =
|
255
|
+
update_interval: float = 60 * 10 # 10 minutes
|
260
256
|
topic = "watermeter"
|
261
257
|
location = "home"
|
262
258
|
camera: int = 0
|
@@ -1,6 +1,4 @@
|
|
1
|
-
"""Web camera with basic image processing features.
|
2
|
-
|
3
|
-
"""
|
1
|
+
"""Web camera with basic image processing features."""
|
4
2
|
|
5
3
|
import cv2
|
6
4
|
import numpy as np
|
@@ -64,7 +62,6 @@ class WebCameraThread(MasterPieceThread):
|
|
64
62
|
|
65
63
|
# Check if the webcam is opened correctly
|
66
64
|
if not cap.isOpened():
|
67
|
-
print("CANNOT ACCESS THE CAMERA")
|
68
65
|
self.error(f"Could not access the camera {self._camera}.")
|
69
66
|
return np.zeros(
|
70
67
|
(1, 1, 3), dtype=np.uint8
|
@@ -74,7 +71,6 @@ class WebCameraThread(MasterPieceThread):
|
|
74
71
|
try:
|
75
72
|
ret, frame = cap.read()
|
76
73
|
if not ret:
|
77
|
-
print("WTF, Camera failed")
|
78
74
|
self.error("Could not capture image.")
|
79
75
|
frame = np.zeros(
|
80
76
|
(1, 1, 3), dtype=np.uint8
|
@@ -142,7 +138,6 @@ class WebCamera(JuhamThread):
|
|
142
138
|
_WEBCAMERA_ATTRS: list[str] = ["location", "camera"]
|
143
139
|
|
144
140
|
_workerThreadId: str = WebCameraThread.get_class_id()
|
145
|
-
update_interval: float = 60
|
146
141
|
location = "home"
|
147
142
|
camera: int = 0
|
148
143
|
|
@@ -1,32 +1,10 @@
|
|
1
|
-
Metadata-Version: 2.
|
1
|
+
Metadata-Version: 2.4
|
2
2
|
Name: juham-watermeter
|
3
|
-
Version: 0.0.
|
3
|
+
Version: 0.0.9
|
4
4
|
Summary: Web-camera based watermeter for Juham
|
5
5
|
Author-email: J Meskanen <juham.api@gmail.com>
|
6
6
|
Maintainer-email: "J. Meskanen" <juham.api@gmail.com>
|
7
|
-
License: MIT
|
8
|
-
===========
|
9
|
-
|
10
|
-
Copyright (c) 2024, Juha Meskanen
|
11
|
-
|
12
|
-
Permission is hereby granted, free of charge, to any person obtaining a copy
|
13
|
-
of this software and associated documentation files (the "Software"), to deal
|
14
|
-
in the Software without restriction, including without limitation the rights
|
15
|
-
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
16
|
-
copies of the Software, and to permit persons to whom the Software is
|
17
|
-
furnished to do so, subject to the following conditions:
|
18
|
-
|
19
|
-
The above copyright notice and this permission notice shall be included in all
|
20
|
-
copies or substantial portions of the Software.
|
21
|
-
|
22
|
-
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
23
|
-
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
24
|
-
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
25
|
-
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
26
|
-
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
27
|
-
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
28
|
-
|
29
|
-
|
7
|
+
License-Expression: MIT
|
30
8
|
Project-URL: Homepage, https://gitlab.com/juham/juham
|
31
9
|
Project-URL: Bug Reports, https://gitlab.com/juham/juham
|
32
10
|
Project-URL: Funding, https://meskanen.com
|
@@ -36,18 +14,18 @@ Keywords: object-oriented,plugin,framework,watermeter,home automation
|
|
36
14
|
Classifier: Development Status :: 2 - Pre-Alpha
|
37
15
|
Classifier: Intended Audience :: Developers
|
38
16
|
Classifier: Topic :: Software Development
|
39
|
-
Classifier: License :: OSI Approved :: MIT License
|
40
17
|
Classifier: Programming Language :: Python :: 3.8
|
41
18
|
Requires-Python: >=3.8
|
42
19
|
Description-Content-Type: text/markdown
|
43
20
|
License-File: LICENSE.rst
|
44
|
-
Requires-Dist: juham-core>=0.1.
|
21
|
+
Requires-Dist: juham-core>=0.1.5
|
45
22
|
Requires-Dist: numpy
|
46
23
|
Requires-Dist: Pillow>=10.4.0
|
47
24
|
Requires-Dist: opencv-python-headless>=4.10.0
|
48
25
|
Requires-Dist: pytesseract>=0.3.13
|
49
26
|
Provides-Extra: dev
|
50
27
|
Requires-Dist: check-manifest; extra == "dev"
|
28
|
+
Dynamic: license-file
|
51
29
|
|
52
30
|
Watermeter plugin for Juham™
|
53
31
|
=============================
|
@@ -10,11 +10,11 @@ packages = ["juham_watermeter"]
|
|
10
10
|
|
11
11
|
[project]
|
12
12
|
name = "juham-watermeter"
|
13
|
-
version = "0.0.
|
13
|
+
version = "0.0.9"
|
14
14
|
description = "Web-camera based watermeter for Juham"
|
15
15
|
readme = {file = "README.rst", content-type = "text/markdown"}
|
16
16
|
requires-python = ">=3.8"
|
17
|
-
license =
|
17
|
+
license = "MIT"
|
18
18
|
keywords = ["object-oriented", "plugin", "framework", "watermeter", "home automation"]
|
19
19
|
authors = [
|
20
20
|
{name = "J Meskanen", email = "juham.api@gmail.com" }
|
@@ -27,12 +27,11 @@ classifiers = [
|
|
27
27
|
"Development Status :: 2 - Pre-Alpha",
|
28
28
|
"Intended Audience :: Developers",
|
29
29
|
"Topic :: Software Development",
|
30
|
-
"License :: OSI Approved :: MIT License",
|
31
30
|
"Programming Language :: Python :: 3.8",
|
32
31
|
]
|
33
32
|
|
34
33
|
dependencies = [
|
35
|
-
"juham-core >= 0.1.
|
34
|
+
"juham-core >= 0.1.5",
|
36
35
|
"numpy",
|
37
36
|
"Pillow >= 10.4.0",
|
38
37
|
"opencv-python-headless >= 4.10.0",
|
@@ -0,0 +1,124 @@
|
|
1
|
+
import unittest
|
2
|
+
from unittest.mock import patch, MagicMock
|
3
|
+
from typing import Any
|
4
|
+
from juham_watermeter.leakdetector import LeakDetector
|
5
|
+
from masterpiece.mqtt import MqttMsg
|
6
|
+
|
7
|
+
|
8
|
+
class MqttTestMsg:
|
9
|
+
def __init__(self, topic: str, payload: bytes):
|
10
|
+
self.topic = topic
|
11
|
+
self.payload = payload
|
12
|
+
|
13
|
+
|
14
|
+
class TestLeakDetector(unittest.TestCase):
|
15
|
+
|
16
|
+
def setUp(self) -> None:
|
17
|
+
self.leak_detector = LeakDetector()
|
18
|
+
self.leak_detector.activity_timeout = 300.0 # Set timeout to 5 minutes
|
19
|
+
|
20
|
+
def test_initialization(self) -> None:
|
21
|
+
self.assertFalse(self.leak_detector.leak_detected)
|
22
|
+
self.assertEqual(self.leak_detector.zero_usage_periods_count, 0)
|
23
|
+
self.assertTrue(self.leak_detector.watermeter_full_topic.endswith("watermeter"))
|
24
|
+
self.assertTrue(self.leak_detector.motion_full_topic.endswith("motion"))
|
25
|
+
|
26
|
+
def test_to_from_dict(self) -> None:
|
27
|
+
state = self.leak_detector.to_dict()
|
28
|
+
new_instance = LeakDetector()
|
29
|
+
new_instance.from_dict(state)
|
30
|
+
self.assertEqual(new_instance.motion_topic, self.leak_detector.motion_topic)
|
31
|
+
|
32
|
+
@patch.object(LeakDetector, "subscribe")
|
33
|
+
def test_on_connect_success(self, mock_subscribe: MagicMock) -> None:
|
34
|
+
self.leak_detector.on_connect(None, None, 0, 0)
|
35
|
+
self.assertEqual(mock_subscribe.call_count, 2)
|
36
|
+
|
37
|
+
@patch.object(LeakDetector, "process_water_meter_data")
|
38
|
+
@patch.object(LeakDetector, "process_motion_data")
|
39
|
+
def test_on_message_routing(
|
40
|
+
self, mock_motion: MagicMock, mock_water: MagicMock
|
41
|
+
) -> None:
|
42
|
+
water_payload = MqttTestMsg(
|
43
|
+
topic=self.leak_detector.watermeter_full_topic,
|
44
|
+
payload=b'{"active_lpm": 1.0, "ts": 123}',
|
45
|
+
)
|
46
|
+
|
47
|
+
motion_payload = MqttTestMsg(
|
48
|
+
topic=self.leak_detector.motion_full_topic,
|
49
|
+
payload=b'{"motion": true, "ts": 123}',
|
50
|
+
)
|
51
|
+
|
52
|
+
self.leak_detector.on_message(None, None, water_payload)
|
53
|
+
mock_water.assert_called_once()
|
54
|
+
|
55
|
+
self.leak_detector.on_message(None, None, motion_payload)
|
56
|
+
mock_motion.assert_called_once()
|
57
|
+
|
58
|
+
@patch("juham_watermeter.leakdetector.timestamp", return_value=1000.0)
|
59
|
+
def test_detect_activity_true(self, _: MagicMock) -> None:
|
60
|
+
self.leak_detector.motion_last_detected_ts = 500000.0
|
61
|
+
self.assertTrue(self.leak_detector.detect_activity(1000.0))
|
62
|
+
|
63
|
+
def test_detect_activity_false(self) -> None:
|
64
|
+
self.leak_detector.motion_last_detected_ts = 0.0
|
65
|
+
self.assertFalse(self.leak_detector.detect_activity(500000.0))
|
66
|
+
|
67
|
+
@patch.object(LeakDetector, "publish")
|
68
|
+
@patch.object(LeakDetector, "warning")
|
69
|
+
def test_process_water_leak_detected(
|
70
|
+
self, mock_warning: MagicMock, mock_publish: MagicMock
|
71
|
+
) -> None:
|
72
|
+
self.leak_detector.motion_last_detected_ts = 0.0 # No motion
|
73
|
+
data = {"active_lpm": 1.2, "ts": 1000000.0}
|
74
|
+
|
75
|
+
# Process water meter data
|
76
|
+
self.leak_detector.process_water_meter_data(data)
|
77
|
+
|
78
|
+
# Assert leak is detected
|
79
|
+
self.assertTrue(self.leak_detector.leak_detected)
|
80
|
+
self.assertEqual(self.leak_detector.zero_usage_periods_count, 0)
|
81
|
+
mock_warning.assert_called_once()
|
82
|
+
mock_publish.assert_called_once()
|
83
|
+
|
84
|
+
@patch.object(LeakDetector, "publish")
|
85
|
+
def test_process_water_leak_reset(self, mock_publish: MagicMock) -> None:
|
86
|
+
self.leak_detector.leak_detected = True
|
87
|
+
self.leak_detector.zero_usage_periods_count = 61
|
88
|
+
data = {"active_lpm": 0.0, "ts": 2000.0}
|
89
|
+
|
90
|
+
# Process water meter data
|
91
|
+
self.leak_detector.process_water_meter_data(data)
|
92
|
+
|
93
|
+
# Assert leak is reset
|
94
|
+
self.assertFalse(self.leak_detector.leak_detected)
|
95
|
+
self.assertEqual(self.leak_detector.motion_last_detected_ts, 2000.0)
|
96
|
+
mock_publish.assert_called_once()
|
97
|
+
|
98
|
+
@patch.object(LeakDetector, "publish")
|
99
|
+
def test_process_water_normal_usage(self, mock_publish: MagicMock) -> None:
|
100
|
+
self.leak_detector.motion_last_detected_ts = 900.0
|
101
|
+
self.leak_detector.detect_activity = MagicMock(return_value=True)
|
102
|
+
data = {"active_lpm": 2.0, "ts": 1000.0}
|
103
|
+
|
104
|
+
# Process water meter data
|
105
|
+
self.leak_detector.process_water_meter_data(data)
|
106
|
+
|
107
|
+
# Assert no leak detected and no warning
|
108
|
+
self.assertFalse(self.leak_detector.leak_detected)
|
109
|
+
self.assertEqual(self.leak_detector.zero_usage_periods_count, 0)
|
110
|
+
mock_publish.assert_called_once()
|
111
|
+
|
112
|
+
def test_process_motion_data(self) -> None:
|
113
|
+
self.leak_detector.motion_last_detected_ts = 0.0
|
114
|
+
data = {"motion": True, "ts": 1337.0}
|
115
|
+
|
116
|
+
# Process motion data
|
117
|
+
self.leak_detector.process_motion_data(data)
|
118
|
+
|
119
|
+
# Assert last motion timestamp updated
|
120
|
+
self.assertEqual(self.leak_detector.motion_last_detected_ts, 1337.0)
|
121
|
+
|
122
|
+
|
123
|
+
if __name__ == "__main__":
|
124
|
+
unittest.main()
|
@@ -0,0 +1,100 @@
|
|
1
|
+
import unittest
|
2
|
+
from unittest.mock import patch, MagicMock
|
3
|
+
import numpy as np
|
4
|
+
import cv2
|
5
|
+
|
6
|
+
from typing import Any
|
7
|
+
from juham_watermeter.webcamera import WebCameraThread
|
8
|
+
|
9
|
+
|
10
|
+
class TestWebCameraThread(unittest.TestCase):
|
11
|
+
def setUp(self) -> None:
|
12
|
+
self.cam = WebCameraThread()
|
13
|
+
|
14
|
+
def test_init_sets_defaults(self) -> None:
|
15
|
+
self.assertEqual(self.cam._interval, 60)
|
16
|
+
self.assertEqual(self.cam._location, "unknown")
|
17
|
+
self.assertEqual(self.cam._camera, 0)
|
18
|
+
self.assertEqual(self.cam.image.shape, (1, 1, 3))
|
19
|
+
self.assertEqual(self.cam.image_timestamp, 0.0)
|
20
|
+
|
21
|
+
def test_init_method_sets_values(self) -> None:
|
22
|
+
self.cam.init(30.0, "office", 1)
|
23
|
+
self.assertEqual(self.cam._interval, 30.0)
|
24
|
+
self.assertEqual(self.cam._location, "office")
|
25
|
+
self.assertEqual(self.cam._camera, 1)
|
26
|
+
|
27
|
+
@patch("cv2.VideoCapture")
|
28
|
+
def test_capture_image_successful(self, mock_VideoCapture: MagicMock) -> None:
|
29
|
+
mock_cap = MagicMock()
|
30
|
+
mock_frame = np.ones((480, 640, 3), dtype=np.uint8)
|
31
|
+
mock_cap.isOpened.return_value = True
|
32
|
+
mock_cap.read.return_value = (True, mock_frame)
|
33
|
+
mock_VideoCapture.return_value = mock_cap
|
34
|
+
|
35
|
+
result = self.cam.capture_image()
|
36
|
+
self.assertEqual(result.shape, mock_frame.shape)
|
37
|
+
|
38
|
+
@patch("cv2.VideoCapture")
|
39
|
+
def test_capture_image_camera_unavailable(
|
40
|
+
self, mock_VideoCapture: MagicMock
|
41
|
+
) -> None:
|
42
|
+
mock_cap = MagicMock()
|
43
|
+
mock_cap.isOpened.return_value = False
|
44
|
+
mock_VideoCapture.return_value = mock_cap
|
45
|
+
|
46
|
+
result = self.cam.capture_image()
|
47
|
+
self.assertTrue(np.array_equal(result, np.zeros((1, 1, 3), dtype=np.uint8)))
|
48
|
+
|
49
|
+
@patch("cv2.VideoCapture")
|
50
|
+
def test_capture_image_read_failure(self, mock_VideoCapture: MagicMock) -> None:
|
51
|
+
mock_cap = MagicMock()
|
52
|
+
mock_cap.isOpened.return_value = True
|
53
|
+
mock_cap.read.return_value = (False, None)
|
54
|
+
mock_VideoCapture.return_value = mock_cap
|
55
|
+
|
56
|
+
result = self.cam.capture_image()
|
57
|
+
self.assertTrue(np.array_equal(result, np.zeros((1, 1, 3), dtype=np.uint8)))
|
58
|
+
|
59
|
+
def test_process_image_with_empty_input(self) -> None:
|
60
|
+
result = self.cam.process_image(np.zeros((1, 1, 3), dtype=np.uint8))
|
61
|
+
self.assertEqual(result.shape, (1, 1, 3))
|
62
|
+
|
63
|
+
def test_process_image_grayscale_conversion(self) -> None:
|
64
|
+
dummy_img = np.ones((480, 640, 3), dtype=np.uint8) * 255
|
65
|
+
self.cam._expected_image_size = dummy_img.size
|
66
|
+
result = self.cam.process_image(dummy_img)
|
67
|
+
self.assertEqual(result.ndim, 2) # grayscale image
|
68
|
+
|
69
|
+
def test_enhance_contrast_on_invalid_input(self) -> None:
|
70
|
+
# Input is not grayscale
|
71
|
+
dummy_img = np.ones((480, 640, 3), dtype=np.uint8)
|
72
|
+
result = self.cam.enhance_contrast(dummy_img)
|
73
|
+
self.assertTrue((result == dummy_img).all())
|
74
|
+
|
75
|
+
def test_enhance_contrast_output_shape(self) -> None:
|
76
|
+
gray_img = np.ones((480, 640), dtype=np.uint8) * 100
|
77
|
+
result = self.cam.enhance_contrast(gray_img)
|
78
|
+
self.assertEqual(result.shape, gray_img.shape)
|
79
|
+
self.assertEqual(result.dtype, gray_img.dtype)
|
80
|
+
|
81
|
+
def test_update_interval_returns_correct_value(self) -> None:
|
82
|
+
self.cam._interval = 42.0
|
83
|
+
self.assertEqual(self.cam.update_interval(), 42.0)
|
84
|
+
|
85
|
+
@patch("juham_watermeter.webcamera.timestamp", return_value=123.456)
|
86
|
+
@patch.object(
|
87
|
+
WebCameraThread,
|
88
|
+
"capture_image",
|
89
|
+
return_value=np.ones((480, 640, 3), dtype=np.uint8),
|
90
|
+
)
|
91
|
+
def test_update_method(self, mock_capture, mock_timestamp):
|
92
|
+
self.cam._expected_image_size = 480 * 640 * 3
|
93
|
+
result = self.cam.update()
|
94
|
+
self.assertTrue(result)
|
95
|
+
self.assertEqual(self.cam.image.shape, (480, 640, 3))
|
96
|
+
self.assertEqual(self.cam.image_timestamp, 123.456)
|
97
|
+
|
98
|
+
|
99
|
+
if __name__ == "__main__":
|
100
|
+
unittest.main()
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
{juham_watermeter-0.0.7 → juham_watermeter-0.0.9}/juham_watermeter.egg-info/dependency_links.txt
RENAMED
File without changes
|
{juham_watermeter-0.0.7 → juham_watermeter-0.0.9}/juham_watermeter.egg-info/entry_points.txt
RENAMED
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|