import numpy as np
from .pattern import DataDock, PatternMethod
from .zoo import ZooMeta
class BarPatternBase(PatternMethod):
"""
Creates a BarPattern or StationaryBarPattern depending on the requested barspeed
"""
def __new__(cls, *args, **kwargs):
if cls is BarPatternBase: # Check if the base class is being instantiated
if kwargs.get("bar_speed") != 0:
return super().__new__(BarPattern)
else:
return super().__new__(StationaryBarPattern)
return super().__new__(cls)
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
print(f"Initializing {self.__class__.__name__}")
[docs]
class StationaryBarPattern(BarPatternBase):
"""
Bar does not move
"""
name = "bar (stationary)"
def __init__(self, duty_cycle=0.2, bar_speed=0, period=30, **kwargs):
"""
:param duty_cycle: fraction of time spent on (float 0-1), and consequently fraction of
vertical axis containing "on" pixels
:param bar_speed: speed in um/min
:param period: period in um
"""
super().__init__(**kwargs)
self.duty_cycle = duty_cycle
self.bar_speed = 0
self.period_space = period # in um
self.period_time = 0 # in minutes
def generate(self, context):
_xx, yy = self.get_um_meshgrid()
is_on = ((yy / self.period_space) % 1.0) < self.duty_cycle
return is_on.astype(np.float16)
[docs]
class BarPattern(BarPatternBase):
"""
moves a bar along the y-axis
"""
name = "bar"
zoo_meta = ZooMeta(
source="mdck",
kwargs={"duty_cycle": 0.2, "bar_speed": 1.0, "period": 100},
title="Moving Bar",
description="Periodic bar sweeping along the y-axis at constant speed.",
)
def __init__(self, duty_cycle=0.2, bar_speed=1, period=30, **kwargs):
"""
:param duty_cycle: fraction of time spent on (float 0-1), and consequently fraction of
vertical axis containing "on" pixels
:param bar_speed: speed in um/min
:param period: period in um
"""
super().__init__(**kwargs)
self.duty_cycle = duty_cycle
self.bar_speed = bar_speed
self.period_space = period # in um
self.period_time = period / bar_speed # in minutes
def _get_pattern_at_time(self, t_minutes):
_xx, yy = self.get_um_meshgrid()
is_on = (
(t_minutes - (yy / self.bar_speed)) % self.period_time
) < self.duty_cycle * self.period_time
return is_on.astype(np.float16)
def generate(self, context):
return self._get_pattern_at_time(context.time / 60)
[docs]
class SawToothMethod(PatternMethod):
name = "sawtooth"
def __init__(self, duty_cycle=0.2, bar_speed=1, period=30, inverse=False, **kwargs):
"""
:param duty_cycle: fraction of time spent on (float 0-1), and consequently fraction of
vertical axis containing "on" pixels
:param bar_speed: speed in um/min
:param period: period in um
"""
super().__init__(**kwargs)
self.duty_cycle = duty_cycle
self.bar_speed = bar_speed
self.period_space = period # in um
self.period_time = period / bar_speed # in minutes
self.inverse = inverse
def generate(self, context):
t = context.time / 60
_xx, yy = self.get_um_meshgrid()
is_on = (
(t - (yy / self.bar_speed)) % self.period_time
) < self.duty_cycle * self.period_time
val = ((t - (yy / self.bar_speed)) % self.period_time) / (
self.duty_cycle * self.period_time
)
if not self.inverse:
val = 1 - val
pattern_out = (is_on * val).astype(np.float16)
print(np.min(pattern_out), np.max(pattern_out))
return pattern_out
[docs]
class BouncingBarPattern(BarPattern):
name = "bar_bounce"
def __init__(self, duty_cycle=0.2, bar_speed=1, period=30, t_loop=60, **kwargs):
"""
:param duty_cycle: fraction of time spent on (float 0-1), and consequently fraction of
vertical axis containing "on" pixels
:param bar_speed: speed in um/min
:param period: period in um
:param t_loop: period of reversal (there and back) in minutes
"""
super().__init__(
duty_cycle=duty_cycle, bar_speed=bar_speed, period=period, **kwargs
)
self.t_loop_s = t_loop * 60
def generate(self, context):
t = context.time
t = t % self.t_loop_s
halfway = self.t_loop_s / 2
if t > halfway:
t = halfway - (t - halfway)
return self._get_pattern_at_time(t / 60)
[docs]
class RotatingBarPattern(PatternMethod):
name = "rotate_bar"
zoo_meta = ZooMeta(
source="mdck",
kwargs={"num_bars": 12, "angular_velocity": 2.0, "bar_width": 20},
title="Rotating Bar",
description="Spoke-like bars rotating around the center of the field.",
)
def __init__(
self,
num_bars: int = 5,
angular_velocity: float = 1.0,
bar_width: float = 20,
angular_velocity_rad: float | None = None,
**kwargs,
):
"""
Directs a specified number of bars with fixed width to rotate around the center of the
camera view at a specified speed. Note that the default speed is in units of um/min. This
is based on the angular velocity of a point on the bar 100um out from the center. To use rad/min,
specify angular_velocity_rad
:param num_bars: number of bars sticking outwards from the center
:param angular_velocity: um/min speed of a point on the bar 100um from the center. Can be overwritten
by angular_velocity_rad
:param bar_width: um width of the bar
"""
super().__init__(**kwargs)
self.num_bars = num_bars
self.angular_velocity = angular_velocity
self.bar_width = bar_width
self.angular_velocity_rad = angular_velocity_rad
if not self.angular_velocity_rad:
self.angular_velocity_rad = self.angular_velocity / 100.0
def generate(self, context) -> np.ndarray:
t = context.time / 60.0
xx, yy = self.get_um_meshgrid()
center_x, center_y = self.center_um()
xx = xx - center_x
yy = yy - center_y
output = xx * 0.0
for spoke_n in range(self.num_bars):
angles_between = 2 * np.pi / self.num_bars
spoke_theta = spoke_n * angles_between + self.angular_velocity_rad * t
spoke_x = np.cos(spoke_theta)
spoke_y = np.sin(spoke_theta)
projection_mag = xx * spoke_x + yy * spoke_y
projection_x = projection_mag * spoke_x
projection_y = projection_mag * spoke_y
projection_distance = np.sqrt(
(xx - projection_x) ** 2 + (yy - projection_y) ** 2
)
output += (projection_mag > 0) & (
np.abs(projection_distance) < (self.bar_width / 2)
)
return output > 0