Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix: wrong range of histogram in drop_pixel #275

Merged
merged 5 commits into from
Dec 15, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
144 changes: 111 additions & 33 deletions test/test_transforms.py
Original file line number Diff line number Diff line change
Expand Up @@ -151,7 +151,7 @@ def test_transform_drop_events_by_area(area_ratio):
break

assert (
dropped_area_found is True
dropped_area_found is True
), f"There should be an area with {dropped_events} events dropped in the obtained sequence."


Expand Down Expand Up @@ -223,6 +223,51 @@ def test_transform_drop_pixel(coordinates, hot_pixel_frequency):
assert events is not orig_events


@pytest.mark.parametrize(
"hot_pixel_frequency, event_max_freq",
[(59, 60), (10, 60)],
)
def test_transform_drop_pixel_unequal_sensor(hot_pixel_frequency, event_max_freq):
orig_events, sensor_size = create_random_input(
n_events=40000, sensor_size=(15, 20, 2)
)
orig_events = orig_events.tolist()
orig_events += [(0, 0, int(t * 1e3), 1) for t in np.arange(1, 1e6, 1e3 / event_max_freq)]
orig_events += [(0, 19, int(t * 1e3), 1) for t in np.arange(1, 1e6, 1e3 / event_max_freq)]
orig_events += [(14, 0, int(t * 1e3), 1) for t in np.arange(1, 1e6, 1e3 / event_max_freq)]
orig_events += [(14, 19, int(t * 1e3), 1) for t in np.arange(1, 1e6, 1e3 / event_max_freq)]
# cast back to numpy events
orig_events = np.asarray(orig_events, np.dtype([("x", int), ("y", int), ("t", int), ("p", int)]))

transform = transforms.DropPixel(
coordinates=None, hot_pixel_frequency=hot_pixel_frequency
)

events = transform(orig_events)
assert len(np.where((events["x"] == 0) & (events["y"] == 0))[0]) == 0
assert len(np.where((events["x"] == 14) & (events["y"] == 0))[0]) == 0
assert len(np.where((events["x"] == 0) & (events["y"] == 19))[0]) == 0
assert len(np.where((events["x"] == 14) & (events["y"] == 19))[0]) == 0


@pytest.mark.parametrize(
"coordinates, hot_pixel_frequency",
[(((9, 11), (10, 12), (11, 13)), None), (None, 10000)],
)
def test_transform_drop_pixel_empty(coordinates, hot_pixel_frequency):
orig_events, sensor_size = create_random_input(
n_events=0, sensor_size=(15, 20, 2)
)

transform = transforms.DropPixel(coordinates=None, hot_pixel_frequency=hot_pixel_frequency)
events = transform(orig_events)
assert len(events) == len(orig_events)

transform = transforms.DropPixel(coordinates=coordinates, hot_pixel_frequency=None)
events = transform(orig_events)
assert len(events) == len(orig_events)


@pytest.mark.parametrize(
"coordinates, hot_pixel_frequency",
[(((199, 11), (199, 12), (11, 13)), None), (None, 5000)],
Expand All @@ -247,7 +292,8 @@ def test_transform_drop_pixel_raster(coordinates, hot_pixel_frequency):
assert not merged_polarity_raster[merged_polarity_raster > 5000].sum().sum()


@pytest.mark.parametrize("time_factor, spatial_factor, target_size", [(1, 0.25, None), (1e-3, (1, 2), None), (1, 1, (5, 5))])
@pytest.mark.parametrize("time_factor, spatial_factor, target_size",
[(1, 0.25, None), (1e-3, (1, 2), None), (1, 1, (5, 5))])
def test_transform_downsample(time_factor, spatial_factor, target_size):
orig_events, sensor_size = create_random_input()

Expand All @@ -256,43 +302,42 @@ def test_transform_downsample(time_factor, spatial_factor, target_size):
)

events = transform(orig_events)

if not isinstance(spatial_factor, tuple):
spatial_factor = (spatial_factor, spatial_factor)

if target_size is None:
assert np.array_equal(
(orig_events["t"] * time_factor).astype(orig_events["t"].dtype), events["t"]
)
assert np.array_equal(np.floor(orig_events["x"] * spatial_factor[0]), events["x"])
assert np.array_equal(np.floor(orig_events["y"] * spatial_factor[1]), events["y"])

else:
spatial_factor_test = np.asarray(target_size) / sensor_size[:-1]
assert np.array_equal(np.floor(orig_events["x"] * spatial_factor_test[0]), events["x"])
assert np.array_equal(np.floor(orig_events["y"] * spatial_factor_test[1]), events["y"])

assert events is not orig_events
@pytest.mark.parametrize("target_size, dt, downsampling_method, noise_threshold, differentiator_time_bins",


@pytest.mark.parametrize("target_size, dt, downsampling_method, noise_threshold, differentiator_time_bins",
[((50, 50), 0.05, 'integrator', 1, None),
((20, 15), 5, 'differentiator', 3, 1)])
def test_transform_event_downsampling(target_size, dt, downsampling_method, noise_threshold,
def test_transform_event_downsampling(target_size, dt, downsampling_method, noise_threshold,
differentiator_time_bins):

orig_events, sensor_size = create_random_input()
transform = transforms.EventDownsampling(sensor_size=sensor_size, target_size=target_size, dt=dt,

transform = transforms.EventDownsampling(sensor_size=sensor_size, target_size=target_size, dt=dt,
downsampling_method=downsampling_method, noise_threshold=noise_threshold,
differentiator_time_bins=differentiator_time_bins)

events = transform(orig_events)

assert len(events) <= len(orig_events)
assert np.logical_and(np.all(events["x"] <= target_size[0]), np.all(events["y"] <= target_size[1]))
assert events is not orig_events


@pytest.mark.parametrize("target_size", [(50, 50), (10, 5)])
def test_transform_random_crop(target_size):
Expand Down Expand Up @@ -465,13 +510,13 @@ def test_transform_spatial_jitter(variance, clip_outliers):
assert np.isclose(events["y"].all(), orig_events["y"].all(), atol=2 * variance)

assert (
events["x"] - orig_events["x"]
== (events["x"] - orig_events["x"]).astype(int)
events["x"] - orig_events["x"]
== (events["x"] - orig_events["x"]).astype(int)
).all()

assert (
events["y"] - orig_events["y"]
== (events["y"] - orig_events["y"]).astype(int)
events["y"] - orig_events["y"]
== (events["y"] - orig_events["y"]).astype(int)
).all()

else:
Expand Down Expand Up @@ -503,8 +548,8 @@ def test_transform_time_jitter(std, clip_negative, sort_timestamps):
np.testing.assert_array_equal(events["y"], orig_events["y"])
np.testing.assert_array_equal(events["p"], orig_events["p"])
assert (
events["t"] - orig_events["t"]
== (events["t"] - orig_events["t"]).astype(int)
events["t"] - orig_events["t"]
== (events["t"] - orig_events["t"]).astype(int)
).all()
assert events is not orig_events

Expand Down Expand Up @@ -562,17 +607,7 @@ def test_transform_time_skew(coefficient, offset):
assert events is not orig_events


@pytest.mark.parametrize(
"n",
[
100,
0,
(
10,
100,
),
],
)
@pytest.mark.parametrize("n", [100, 0, (10, 100)])
def test_transform_uniform_noise(n):
orig_events, sensor_size = create_random_input()

Expand All @@ -597,6 +632,16 @@ def test_transform_uniform_noise(n):
assert events is not orig_events


@pytest.mark.parametrize("n", [100, 0, (10, 100)])
def test_transform_uniform_noise_empty(n):
orig_events, sensor_size = create_random_input(n_events=0)
assert len(orig_events) == 0

transform = transforms.UniformNoise(sensor_size=sensor_size, n=n)
events = transform(orig_events)
assert len(events) == 0 # check returns an empty array, independent of n.


def test_transform_time_alignment():
orig_events, sensor_size = create_random_input()

Expand All @@ -606,3 +651,36 @@ def test_transform_time_alignment():

assert np.min(events["t"]) == 0
assert events is not orig_events


def test_toframe_empty():
orig_events, sensor_size = create_random_input(n_events=0)
assert len(orig_events) == 0

with pytest.raises(ValueError): # check that empty array raises error if no slicing method is specified
transform = transforms.ToFrame(sensor_size=sensor_size)
frame = transform(orig_events)

n_event_bins = 100
transform = transforms.ToFrame(sensor_size=sensor_size, n_event_bins=n_event_bins)
frame = transform(orig_events)
assert frame.shape == (n_event_bins, sensor_size[2], sensor_size[0], sensor_size[1])
assert frame.sum() == 0

n_time_bins = 100
transform = transforms.ToFrame(sensor_size=sensor_size, n_time_bins=n_time_bins)
frame = transform(orig_events)
assert frame.shape == (n_time_bins, sensor_size[2], sensor_size[0], sensor_size[1])
assert frame.sum() == 0

event_count = 1e3
transform = transforms.ToFrame(sensor_size=sensor_size, event_count=event_count)
frame = transform(orig_events)
assert frame.shape == (1, sensor_size[2], sensor_size[0], sensor_size[1])
assert frame.sum() == 0

time_window = 1e3
transform = transforms.ToFrame(sensor_size=sensor_size, time_window=time_window)
frame = transform(orig_events)
assert frame.shape == (1, sensor_size[2], sensor_size[0], sensor_size[1])
assert frame.sum() == 0
2 changes: 1 addition & 1 deletion tonic/functional/drop_pixel.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ def identify_hot_pixel(events: np.ndarray, hot_pixel_frequency: float):
hist = np.histogram2d(
events["x"],
events["y"],
bins=(np.arange(events["y"].max() + 1), np.arange(events["x"].max() + 1)),
bins=(np.arange(events["x"].max() + 2), np.arange(events["y"].max() + 2)),
)[0]
max_occur = hot_pixel_frequency * total_time * 1e-6
hot_pixels = np.asarray((hist > max_occur).nonzero()).T
Expand Down
27 changes: 14 additions & 13 deletions tonic/functional/to_frame.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,14 @@


def to_frame_numpy(
events,
sensor_size,
time_window=None,
event_count=None,
n_time_bins=None,
n_event_bins=None,
overlap=0.0,
include_incomplete=False,
events,
sensor_size,
time_window=None,
event_count=None,
n_time_bins=None,
n_event_bins=None,
overlap=0.0,
include_incomplete=False,
):
"""Accumulate events to frames by slicing along constant time (time_window), constant number of
events (event_count) or constant number of frames (n_time_bins / n_event_bins).
Expand All @@ -37,11 +37,11 @@ def to_frame_numpy(
assert "x" and "t" and "p" in events.dtype.names

if (
not sum(
param is not None
for param in [time_window, event_count, n_time_bins, n_event_bins]
)
== 1
not sum(
param is not None
for param in [time_window, event_count, n_time_bins, n_event_bins]
)
== 1
):
raise ValueError(
"Please assign a value to exactly one of the parameters time_window,"
Expand Down Expand Up @@ -93,3 +93,4 @@ def to_frame_numpy(
for i, event_slice in enumerate(event_slices):
np.add.at(frames, (i, event_slice["p"].astype(int), event_slice["x"]), 1)
return frames

49 changes: 34 additions & 15 deletions tonic/transforms.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,15 +61,15 @@ def __call__(self, events: np.ndarray) -> np.ndarray:
if type(self.size) == int:
self.size = [self.size, self.size]
offsets = (self.sensor_size[0] - self.size[0]) // 2, (
self.sensor_size[1] - self.size[1]
self.sensor_size[1] - self.size[1]
) // 2
offset_idx = [max(offset, 0) for offset in offsets]
cropped_events = events[
(offset_idx[0] <= events["x"])
& (events["x"] < (offset_idx[0] + self.size[0]))
& (offset_idx[1] <= events["y"])
& (events["y"] < (offset_idx[1] + self.size[1]))
]
]
cropped_events["x"] -= offsets[0]
cropped_events["y"] -= offsets[1]
return cropped_events
Expand Down Expand Up @@ -228,6 +228,9 @@ class DropPixel:
hot_pixel_frequency: Optional[int] = None

def __call__(self, events):
if len(events) == 0:
return events # return empty array

if events.dtype.names is not None:
# assert "x", "y", "p" in events.dtype.names
if self.hot_pixel_frequency:
Expand Down Expand Up @@ -723,7 +726,7 @@ def __call__(self, events):
@dataclass(frozen=True)
class UniformNoise:
"""Adds a fixed number of n noise events that are uniformly distributed across sensor size
dimensions such as x, y, t and p.
dimensions such as x, y, t and p. Not applied if the input is empty.

Parameters:
sensor_size: a 3-tuple of x,y,p for sensor_size
Expand All @@ -744,6 +747,9 @@ def get_params(n: Union[int, Tuple[int, int]]):
return n

def __call__(self, events):
if len(events) == 0:
return events

n = self.get_params(n=self.n)
return functional.uniform_noise_numpy(
events=events, sensor_size=self.sensor_size, n=n
Expand Down Expand Up @@ -782,10 +788,10 @@ class NumpyAsType:

def __call__(self, events):
source_is_structured_array = (
hasattr(events.dtype, "names") and events.dtype.names != None
hasattr(events.dtype, "names") and events.dtype.names != None
)
target_is_structured_array = (
hasattr(self.dtype, "names") and self.dtype.names != None
hasattr(self.dtype, "names") and self.dtype.names != None
)
if source_is_structured_array and not target_is_structured_array:
return np.lib.recfunctions.structured_to_unstructured(events, self.dtype)
Expand Down Expand Up @@ -891,16 +897,29 @@ class ToFrame:
include_incomplete: bool = False

def __call__(self, events):
return functional.to_frame_numpy(
events=events,
sensor_size=self.sensor_size,
time_window=self.time_window,
event_count=self.event_count,
n_time_bins=self.n_time_bins,
n_event_bins=self.n_event_bins,
overlap=self.overlap,
include_incomplete=self.include_incomplete,
)

# if events are empty, return a frame in the expected format
if len(events) == 0:
if self.time_window is not None or self.event_count is not None:
return np.zeros((1, self.sensor_size[2], self.sensor_size[0], self.sensor_size[1]))
elif self.n_event_bins is not None:
return np.zeros((self.n_event_bins, self.sensor_size[2], self.sensor_size[0], self.sensor_size[1]))
elif self.n_time_bins is not None:
return np.zeros((self.n_time_bins, self.sensor_size[2], self.sensor_size[0], self.sensor_size[1]))
else:
raise ValueError("No slicing method specified.")

else:
return functional.to_frame_numpy(
events=events,
sensor_size=self.sensor_size,
time_window=self.time_window,
event_count=self.event_count,
n_time_bins=self.n_time_bins,
n_event_bins=self.n_event_bins,
overlap=self.overlap,
include_incomplete=self.include_incomplete,
)


@dataclass(frozen=True)
Expand Down