Apply suggestions from code review of branch fast-sim

Co-authored-by: Richard Bowman <richard.bowman@cantab.net>
Co-authored-by: Joe Knapper <joe.knapper@glasgow.ac.uk>
This commit is contained in:
Julian Stirling 2026-01-12 17:41:51 +00:00
parent 40a2e4c6ed
commit 4c557612fb
2 changed files with 24 additions and 17 deletions

View file

@ -46,16 +46,17 @@ RNG = np.random.default_rng()
DOWNSAMPLE = 2 DOWNSAMPLE = 2
# Upsample for sprites and then downsample to create sharp edges for each sprite # Upsample for sprites and then downsample to create sharp edges for each sprite
# as these are small and calculated once there is almos no performance penalty # as these are small and calculated once there is almost no performance penalty
# for a nice gain in quality. # for a nice gain in quality.
SPRITE_UPSAMPLE = 4 SPRITE_UPSAMPLE = 4
# A list of 6 digit hex colour codes separated by ;. Allow a trailing ; # A list of 6 digit hex colour codes separated by ;. Allow a trailing ;
# For example, OpenFlexure pink would be #C5247F;
COLOUR_LIST_REGEX = re.compile( COLOUR_LIST_REGEX = re.compile(
r"^\s*(#[0-9a-fA-F]{6})\s*(?:;\s*(#[0-9a-fA-F]{6})\s*)*;?\s*$" r"^\s*(#[0-9a-fA-F]{6})\s*(?:;\s*(#[0-9a-fA-F]{6})\s*)*;?\s*$"
) )
# regex to separate R, G and B from a 6 digit hex code with preceding #
COLOUR_REGEX = re.compile(r"^#([0-9a-f]{2})([0-9a-f]{2})([0-9a-f]{2})$") COLOUR_REGEX = re.compile(r"^#([0-9a-f]{2})([0-9a-f]{2})([0-9a-f]{2})$")
# regex to capture R, G and B
@overload @overload
@ -159,16 +160,21 @@ class SimulatedCamera(BaseCamera):
@lt.property @lt.property
def colour(self) -> str: def colour(self) -> str:
"""The number of colour of the blobs.""" """The colour of the blobs as a HTML hex string.
The string can either be a single colour (e.g. "#c5247f") or a list of
colours separated by semicolons (e.g. "#c5247f; #b937b9"). Additional
spaces are allowed between colours.
"""
return self._colour return self._colour
@colour.setter @colour.setter
def _set_colour(self, value: str) -> None: def _set_colour(self, colour_value: str) -> None:
if COLOUR_LIST_REGEX.match(value) is None: if COLOUR_LIST_REGEX.match(colour_value) is None:
self.logger.warning(f"{value} is not a valid colour string.") self.logger.warning(f"{colour_value} is not a valid colour string.")
return return
self._colour = value self._colour = colour_value
if self._capture_enabled: if self._capture_enabled:
self.generate_canvas() self.generate_canvas()
@ -210,7 +216,7 @@ class SimulatedCamera(BaseCamera):
sprite_pil = sprite_pil.resize( sprite_pil = sprite_pil.resize(
(self.glyph_size, self.glyph_size), Image.Resampling.BILINEAR (self.glyph_size, self.glyph_size), Image.Resampling.BILINEAR
) )
# Concert back and ensure all edges are zero as these are repeated at sample # Convert back and ensure all edges are zero as these are repeated at sample
# edge # edge
sprite = np.array(sprite_pil) sprite = np.array(sprite_pil)
sprite[0, :] = 0 sprite[0, :] = 0
@ -311,9 +317,10 @@ class SimulatedCamera(BaseCamera):
x_indices = x_indices % canvas_width x_indices = x_indices % canvas_width
y_indices = y_indices % canvas_height y_indices = y_indices % canvas_height
else: else:
# No sprites are placed right at the edge of the image so that the whole # Rather than use a modulo for the index list, as above when wrapping,
# sprite is on the canvas. For a non-repeating image just clip to the # this uses np.clip to coerce all out of bound indices to repeat the
# first or last pixel. # first or last pixel in the canvas. This works because no sprite touches
# the very edge of the canvas (to prevent partial sprites).
x_indices = np.clip(x_indices, 0, canvas_width - 1) x_indices = np.clip(x_indices, 0, canvas_width - 1)
y_indices = np.clip(y_indices, 0, canvas_height - 1) y_indices = np.clip(y_indices, 0, canvas_height - 1)

View file

@ -77,13 +77,13 @@ def all_colours_present(
if col_tuple not in colours: if col_tuple not in colours:
raise ValueError("Unexpected colour returned.") raise ValueError("Unexpected colour returned.")
found[colours.index(col_tuple)] = True found[colours.index(col_tuple)] = True
# Don't exit early or we don't confrm that extra colours are not returned. # Don't exit early or we don't confirm that extra colours are not returned.
return all(found) return all(found)
def test_colour_str_to_colour(): def test_colour_str_to_colour():
"""Test colour_str_to_colour with some basic predefined test cases.""" """Test colour_str_to_colour with some basic predefined test cases."""
# A basic test # A basic test to convert a single str to the expected colour
assert simulation.colour_str_to_colour("#123456") == (0x12, 0x34, 0x56) assert simulation.colour_str_to_colour("#123456") == (0x12, 0x34, 0x56)
# A basic test with a trailing semicolon and surrounding spacing # A basic test with a trailing semicolon and surrounding spacing
assert simulation.colour_str_to_colour(" #123456 ; ") == (0x12, 0x34, 0x56) assert simulation.colour_str_to_colour(" #123456 ; ") == (0x12, 0x34, 0x56)
@ -109,8 +109,8 @@ def test_colour_str_to_colour():
def test_colour_list_regex(colour_str): def test_colour_list_regex(colour_str):
"""Check that anything matching the regex doesn't error when generating colours. """Check that anything matching the regex doesn't error when generating colours.
This will error if colour_str when splot into individual colours produces This will error if splitting colour_str into individual colours produces
# an incorrect colour. Trying 100 times for each colour_str as the returned colour an incorrect colour. Trying 100 times for each colour_str as the returned colour
is randomised. Hypothesis will try to create the strings that match the regex but is randomised. Hypothesis will try to create the strings that match the regex but
break the test. break the test.
""" """
@ -119,7 +119,7 @@ def test_colour_list_regex(colour_str):
def test_canvas_regeneration(camera, caplog): def test_canvas_regeneration(camera, caplog):
"""Check canvas is regenerated if blob density of colour are changed.""" """Check canvas is regenerated if blob density or colour are changed."""
cached_canvas = camera.canvas cached_canvas = camera.canvas
original_colour = camera.colour original_colour = camera.colour
@ -172,7 +172,7 @@ def test_infinite_sample(camera, stage):
# Turn on repeating # Turn on repeating
camera.repeating = True camera.repeating = True
time.sleep(0.2) # Ensure frame regenerates time.sleep(0.2) # Ensure frame regenerates
# Sample is now infitine, so not all background # Sample is now infinite, so not all background
assert not np.all(camera.capture_array() == simulation.BG_COLOR) assert not np.all(camera.capture_array() == simulation.BG_COLOR)