Skip to content

Commit

Permalink
image entropy capture: always catch the touch
Browse files Browse the repository at this point in the history
higher frame-rate
  • Loading branch information
odudex committed Aug 12, 2024
1 parent b9e840f commit 1794a1c
Show file tree
Hide file tree
Showing 4 changed files with 89 additions and 42 deletions.
4 changes: 2 additions & 2 deletions src/krux/input.py
Original file line number Diff line number Diff line change
Expand Up @@ -167,10 +167,10 @@ def page_prev_event(self, check_yahboom=False):
return self.page_prev.event()
return False

def touch_event(self):
def touch_event(self, validate_position=True):
"""Intermediary method to pull button TOUCH event"""
if self.touch is not None:
return self.touch.event()
return self.touch.event(validate_position)
return False

def swipe_right_value(self):
Expand Down
119 changes: 81 additions & 38 deletions src/krux/pages/capture_entropy.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,10 @@

import board
import math
from ..display import FONT_HEIGHT, BOTTOM_LINE, BOTTOM_PROMPT_LINE
from . import Page
from ..display import FONT_HEIGHT, BOTTOM_LINE, BOTTOM_PROMPT_LINE
from ..krux_settings import t
from ..themes import theme

POOR_VARIANCE_TH = 10 # RMS value of L, A, B channels considered poor
INSUFFICIENT_VARIANCE_TH = 5 # RMS value of L, A, B channels considered insufficient
Expand All @@ -32,6 +34,10 @@
PROCEED_PRESSED = 1
CANCEL_PRESSED = 2

INSUFFICIENT_ENTROPY = 0
POOR_ENTROPY = 1
GOOD_ENTROPY = 2


class CameraEntropy(Page):
"""Class for capturing entropy from a snapshot"""
Expand All @@ -40,12 +46,24 @@ def __init__(self, ctx):
super().__init__(ctx, None)
self.ctx = ctx

# State machine to measure the entropy every 4 frames and reduce processing load
self.image_stats = None
self.image_stats_vector = [0] * 3
self.measurement_machine_state = 0
self.previous_measurement = POOR_ENTROPY
self.stdev_index = 0
self.y_label_offset = BOTTOM_LINE
if board.config["type"] == "amigo":
self.y_label_offset = BOTTOM_PROMPT_LINE

def _callback(self):
"""
Returns PROCEED if user pressed ENTER or touched the screen,
CANCEL if user pressed PAGE or PAGE_PREV, 0 otherwise
"""
if self.ctx.input.enter_event() or self.ctx.input.touch_event():
if self.ctx.input.enter_event() or self.ctx.input.touch_event(
validate_position=False
):
return PROCEED_PRESSED
if self.ctx.input.page_event() or self.ctx.input.page_prev_event():
return CANCEL_PRESSED
Expand All @@ -60,15 +78,69 @@ def rms_value(self, data):
rms = math.sqrt(mean_square)
return int(rms)

def entropy_measurement_update(self, img, all_at_once=False):
"""
Entropy measurement state machine calculates and prints entropy estimation every 4 frames
"""

if all_at_once:
self.measurement_machine_state = 0

if self.measurement_machine_state == 0:
self.image_stats = img.get_statistics()
if all_at_once:
# Calculate all channels at once for final entropy estimation
self.image_stats_vector[0] = self.image_stats.l_stdev()
self.image_stats_vector[1] = self.image_stats.a_stdev()
self.image_stats_vector[2] = self.image_stats.b_stdev()
self.stdev_index = self.rms_value(self.image_stats_vector)
entropy_level = INSUFFICIENT_ENTROPY
if self.stdev_index > POOR_VARIANCE_TH:
entropy_level = GOOD_ENTROPY
elif self.stdev_index > INSUFFICIENT_VARIANCE_TH:
entropy_level = POOR_ENTROPY
if self.previous_measurement != entropy_level and not all_at_once:
self.ctx.display.to_portrait()
self.previous_measurement = entropy_level
self.ctx.display.fill_rectangle(
0,
self.y_label_offset,
self.ctx.display.width(),
FONT_HEIGHT,
theme.bg_color,
)
if entropy_level == GOOD_ENTROPY:
self.ctx.display.draw_hcentered_text(
t("Good entropy"), self.y_label_offset, theme.go_color
)
elif entropy_level == POOR_ENTROPY:
self.ctx.display.draw_hcentered_text(
t("Poor entropy"), self.y_label_offset, theme.del_color
)
else:
self.ctx.display.draw_hcentered_text(
t("Insufficient entropy"),
self.y_label_offset,
theme.error_color,
)
self.ctx.display.to_landscape()

elif self.measurement_machine_state == 1:
self.image_stats_vector[0] = self.image_stats.l_stdev()
elif self.measurement_machine_state == 2:
self.image_stats_vector[1] = self.image_stats.a_stdev()
elif self.measurement_machine_state == 3:
self.image_stats_vector[2] = self.image_stats.b_stdev()
self.measurement_machine_state += 1
self.measurement_machine_state %= 4

def capture(self, show_entropy_details=True):
"""Captures camera's entropy as the hash of image buffer"""
import hashlib
import gc
import sensor
import shannon
from ..wdt import wdt
from ..krux_settings import t
from ..themes import theme

self.ctx.display.clear()
self.ctx.display.draw_centered_text(t("TOUCH or ENTER to capture"))
Expand All @@ -77,51 +149,20 @@ def capture(self, show_entropy_details=True):
self.ctx.display.clear()

command = 0
y_label_offset = BOTTOM_LINE
if board.config["type"] == "amigo":
y_label_offset = BOTTOM_PROMPT_LINE

# Flush events ocurred while loading camera
self.ctx.input.reset_ios_state()
while True:
wdt.feed()

self.ctx.display.to_landscape()
img = sensor.snapshot()
self.ctx.display.render_image(img)

stdev_index = self.rms_value(
[
img.get_statistics().l_stdev(),
img.get_statistics().a_stdev(),
img.get_statistics().b_stdev(),
]
)

command = self._callback()
if command != NOT_PRESSED:
break

self.ctx.display.to_portrait()
self.ctx.display.fill_rectangle(
0,
y_label_offset,
self.ctx.display.width(),
FONT_HEIGHT,
theme.bg_color,
)
if stdev_index > POOR_VARIANCE_TH:
self.ctx.display.draw_hcentered_text(
t("Good entropy"), y_label_offset, theme.go_color
)
elif stdev_index > INSUFFICIENT_VARIANCE_TH:
self.ctx.display.draw_hcentered_text(
t("Poor entropy"), y_label_offset, theme.del_color
)
else:
self.ctx.display.draw_hcentered_text(
t("Insufficient entropy"), y_label_offset, theme.error_color
)
self.entropy_measurement_update(img)

self.ctx.display.to_portrait()
gc.collect()
Expand All @@ -134,6 +175,8 @@ def capture(self, show_entropy_details=True):

self.ctx.display.draw_centered_text(t("Processing ..."))

self.entropy_measurement_update(img, all_at_once=True)

img_bytes = img.to_bytes()
img_pixels = img.width() * img.height()
del img
Expand All @@ -146,12 +189,12 @@ def capture(self, show_entropy_details=True):
entropy_msg += str(round(shannon_16b, 2)) + " " + "bits/px" + "\n"
entropy_msg += t("(%d total)") % int(shannon_16b_total) + "\n\n"
entropy_msg += t("Pixels deviation index:") + " "
entropy_msg += str(stdev_index)
entropy_msg += str(self.stdev_index)
self.ctx.display.clear()
self.ctx.input.reset_ios_state()
if (
shannon_16b < INSUFFICIENT_SHANNONS_ENTROPY_TH
or stdev_index < INSUFFICIENT_VARIANCE_TH
or self.stdev_index < INSUFFICIENT_VARIANCE_TH
):
error_msg = t("Insufficient Entropy!")
error_msg += "\n\n"
Expand Down
4 changes: 3 additions & 1 deletion src/krux/pages/tiny_seed.py
Original file line number Diff line number Diff line change
Expand Up @@ -999,7 +999,9 @@ def _exit_camera(self):
self.ctx.display.clear()

def _check_buttons(self, w24, page):
enter_or_touch = self.ctx.input.enter_event() or self.ctx.input.touch_event()
enter_or_touch = self.ctx.input.enter_event() or self.ctx.input.touch_event(
validate_position=False
)
if w24:
if page == 0 and enter_or_touch:
self.capturing = True
Expand Down
4 changes: 3 additions & 1 deletion src/krux/touch.py
Original file line number Diff line number Diff line change
Expand Up @@ -160,13 +160,15 @@ def current_state(self):
print("Touch error")
return self.state

def event(self):
def event(self, validate_position=True):
"""Checks if a touch happened and stores the point"""
current_time = time.ticks_ms()
if current_time > self.sample_time + TOUCH_S_PERIOD:
if self.touch_driver.event():
# Resets touch and gets irq point
self.state = IDLE
if not validate_position:
return True
if isinstance(self.touch_driver.irq_point, tuple):
if self.valid_position(self.touch_driver.irq_point):
self._store_points(self.touch_driver.irq_point)
Expand Down

0 comments on commit 1794a1c

Please sign in to comment.