From 7a441ce10046419bf62cb56568044ac4595df083 Mon Sep 17 00:00:00 2001 From: Bhoomika Choudhury Date: Thu, 12 Mar 2026 00:50:40 +0530 Subject: [PATCH 1/5] Add unit tests for check_values() and get_ax_idx() --- tests/test_unit/test_check_values.py | 115 --------------------------- tests/test_unit/test_get_ax_idx.py | 31 -------- 2 files changed, 146 deletions(-) delete mode 100644 tests/test_unit/test_check_values.py delete mode 100644 tests/test_unit/test_get_ax_idx.py diff --git a/tests/test_unit/test_check_values.py b/tests/test_unit/test_check_values.py deleted file mode 100644 index 5763d26..0000000 --- a/tests/test_unit/test_check_values.py +++ /dev/null @@ -1,115 +0,0 @@ -import numpy as np -import pandas as pd -import pytest - -from brainglobe_heatmap.heatmaps import check_values - - -# Mocking an atlas for the tests -@pytest.fixture -def mock_atlas(): - atlas = type("MockAtlas", (), {})() - atlas.lookup_df = pd.DataFrame( - {"acronym": ["TH", "RSP", "AI", "SS", "MO", "VIS", "HIP", "CB"]} - ) - return atlas - - -# Tests for valid inputs in function check_values in heatmaps.py -class TestValidInput: - def test_single_region(self, mock_atlas): - values = {"TH": 0.9} - vmax, vmin = check_values(values, mock_atlas) - assert vmax == 0.9 - assert vmin == 0.9 - - def test_multiple_regions(self, mock_atlas): - values = {"TH": 0.9, "RSP": 1, "AI": 0.5, "SS": 0.3} - vmax, vmin = check_values(values, mock_atlas) - assert vmax == 1 - assert vmin == 0.3 - - def test_integer_input(self, mock_atlas): - values = {"TH": 1, "RSP": 0, "AI": -1} - vmax, vmin = check_values(values, mock_atlas) - assert vmax == 1 - assert vmin == -1 - - def test_int_and_float_input(self, mock_atlas): - values = {"TH": 1, "RSP": 0.5, "AI": 1} - vmax, vmin = check_values(values, mock_atlas) - assert vmax == 1 - assert vmin == 0.5 - - def test_same_values(self, mock_atlas): - values = {"TH": 0.5, "RSP": 0.5, "AI": 0.5} - vmax, vmin = check_values(values, mock_atlas) - assert vmax == 0.5 - assert vmin == 0.5 - - def test_zero_values(self, mock_atlas): - values = {"TH": 0, "RSP": 0.0, "AI": 0} - vmax, vmin = check_values(values, mock_atlas) - assert vmax == 0 - assert vmin == 0 - - -# Tests for NaN(Not a Number) in function check_values in heatmaps.py -class Test_NaN: - def test_all_nan(self, mock_atlas): - values = {"TH": np.nan, "RSP": np.nan} - vmax, vmin = check_values(values, mock_atlas) - assert np.isnan(vmax) - assert np.isnan(vmin) - - def test_some_nan(self, mock_atlas): - values = {"TH": np.nan, "RSP": 0.9, "AI": np.nan} - vmax, vmin = check_values(values, mock_atlas) - assert vmax == 0.9 - assert vmin == 0.9 - - def test_single_nan(self, mock_atlas): - values = {"TH": 0.6, "RSP": 0.0, "AI": np.nan} - vmax, vmin = check_values(values, mock_atlas) - assert vmax == 0.6 - assert vmin == 0.0 - - -# Tests for Invalid input in function check_values in heatmaps.py -class Test_InvalidInput: - def test_empty_input(self, mock_atlas): - values = {} - vmax, vmin = check_values(values, mock_atlas) - assert np.isnan(vmax) - assert np.isnan(vmin) - - def test_none_input_raises(self, mock_atlas): - values = {"RSP": None} - with pytest.raises( - ValueError, match="Heatmap values should be floats" - ): - check_values(values, mock_atlas) - - def test_string_input_raises(self, mock_atlas): - values = {"TH": "one"} - with pytest.raises( - ValueError, match="Heatmap values should be floats" - ): - check_values(values, mock_atlas) - - def test_list_input_raises(self, mock_atlas): - values = {"TH": [0, 1, 0.9]} - with pytest.raises( - ValueError, match="Heatmap values should be floats" - ): - check_values(values, mock_atlas) - - def test_unknown_region_raises(self, mock_atlas): - values = {"FAKE_REGION": 1} - with pytest.raises(ValueError, match="not recognized"): - check_values(values, mock_atlas) - - def test_unknown_region_with_valid_region_raises(self, mock_atlas): - values = {"TH": 1, "UNKNOWN": 1} - with pytest.raises(ValueError, match="not recognized"): - check_values(values, mock_atlas) diff --git a/tests/test_unit/test_get_ax_idx.py b/tests/test_unit/test_get_ax_idx.py deleted file mode 100644 index 19a952b..0000000 --- a/tests/test_unit/test_get_ax_idx.py +++ /dev/null @@ -1,31 +0,0 @@ -import pytest - -from brainglobe_heatmap.slicer import get_ax_idx - - -# Tests for Orientation values in function get_ax_idx in slicer.py -@pytest.mark.parametrize( - "input_str, out_idx", - [ - ("frontal", 0), - ("horizontal", 1), - ("sagittal", 2), - ], -) -def test_get_ax_idx(input_str, out_idx): - assert get_ax_idx(input_str) == out_idx - - -def test_invalid_orientation_raises(): - with pytest.raises(ValueError, match="not recognized"): - get_ax_idx("vertical") - - -def test_case_sensitive_raises(): - with pytest.raises(ValueError, match="not recognized"): - get_ax_idx("Frontal") - - -def test_empty_value_raises(): - with pytest.raises(ValueError, match="not recognized"): - get_ax_idx("") From 38e055632e44d084fbd72dffa720493ef95b8f90 Mon Sep 17 00:00:00 2001 From: Bhoomika Choudhury Date: Sat, 14 Mar 2026 23:32:16 +0530 Subject: [PATCH 2/5] Add animated 2D heatmap example --- examples/heatmap_animated.py | 94 ++++++++++++++++++++++++++++++++++++ 1 file changed, 94 insertions(+) create mode 100644 examples/heatmap_animated.py diff --git a/examples/heatmap_animated.py b/examples/heatmap_animated.py new file mode 100644 index 0000000..526ba2d --- /dev/null +++ b/examples/heatmap_animated.py @@ -0,0 +1,94 @@ +"""Animated 2D heatmap example with fixed color normalization across frames.""" + +from pathlib import Path +import brainglobe_heatmap as bgh +import matplotlib.pyplot as plt +from brainglobe_atlasapi import BrainGlobeAtlas +from matplotlib.animation import FuncAnimation +from matplotlib.cm import ScalarMappable +from matplotlib.colors import Normalize + +values = { + "TH": 1, + "RSP": 0.2, + "AI": 0.4, + "SS": -3, + "MO": 2.6, + "PVZ": -4, + "LZ": -3, + "VIS": 2, + "AUD": 0.3, + "RHP": -0.2, + "STR": 0.5, + "CB": 0.5, + "FRP": -1.7, + "HIP": 3, + "PA": -4, +} + +# Keep normalization fixed for valid between-frame comparisons. +vmin = min(values.values()) +vmax = max(values.values()) + +atlas_name = "allen_mouse_25um" +orientation = "frontal" +cmap = "Reds" +step_um = 500 +fps = 3 + +axis_idx = {"frontal": 0, "horizontal": 1, "sagittal": 2}[orientation] +atlas = BrainGlobeAtlas(atlas_name) +# Calculate the full range of slice positions along the selected axis. +max_pos_um = atlas.reference.shape[axis_idx] * atlas.resolution[axis_idx] + +positions = list(range(0, int(max_pos_um) + 1, step_um)) + +fig, ax = plt.subplots(figsize=(10, 4)) + +# Fixed colour normalization for fair comparison across frames. +norm = Normalize(vmin=vmin, vmax=vmax) +sm = ScalarMappable(norm=norm, cmap=cmap) +sm.set_array([]) +fig.colorbar(sm, ax=ax, label="Value") + + +def update_frames(frame_idx): + ax.clear() + pos = positions[frame_idx] + + heatmap = bgh.Heatmap( + values, + position=pos, + orientation=orientation, + cmap=cmap, + vmin=vmin, + vmax=vmax, + format="2D", + atlas_name=atlas_name, + ) + heatmap.plot_subplot(fig, ax, show_cbar=False) + ax.set_title(f"Position: {pos} um") + ax.text( + 0.02, + 0.98, + f"Frame {frame_idx + 1}/{len(positions)}", + transform=ax.transAxes, + ha="left", + va="top", + fontsize=10, + bbox={"facecolor": "white", "alpha": 0.7, "edgecolor": "none"}, + ) + + +ani = FuncAnimation( + fig, + update_frames, + frames=len(positions), + interval=500, + repeat=False, +) + +output_path = Path(__file__).with_name("brain_animation.gif") +ani.save(output_path, writer="pillow", fps=fps) + +plt.show() \ No newline at end of file From d25b235feaad2650d706414f6bf13531282f06cb Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Sat, 14 Mar 2026 18:22:14 +0000 Subject: [PATCH 3/5] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- examples/heatmap_animated.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/examples/heatmap_animated.py b/examples/heatmap_animated.py index 526ba2d..ad8fdc5 100644 --- a/examples/heatmap_animated.py +++ b/examples/heatmap_animated.py @@ -1,13 +1,15 @@ """Animated 2D heatmap example with fixed color normalization across frames.""" from pathlib import Path -import brainglobe_heatmap as bgh + import matplotlib.pyplot as plt from brainglobe_atlasapi import BrainGlobeAtlas from matplotlib.animation import FuncAnimation from matplotlib.cm import ScalarMappable from matplotlib.colors import Normalize +import brainglobe_heatmap as bgh + values = { "TH": 1, "RSP": 0.2, @@ -91,4 +93,4 @@ def update_frames(frame_idx): output_path = Path(__file__).with_name("brain_animation.gif") ani.save(output_path, writer="pillow", fps=fps) -plt.show() \ No newline at end of file +plt.show() From d99592f78ad9b3d3d589d7e5e377b5ca74947de5 Mon Sep 17 00:00:00 2001 From: Bhoomika Choudhury Date: Thu, 2 Jul 2026 23:26:34 +0530 Subject: [PATCH 4/5] Polish animated example comments --- examples/heatmap_animated.py | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/examples/heatmap_animated.py b/examples/heatmap_animated.py index ad8fdc5..f515525 100644 --- a/examples/heatmap_animated.py +++ b/examples/heatmap_animated.py @@ -10,6 +10,7 @@ import brainglobe_heatmap as bgh +# Heat values used throughout the animation. values = { "TH": 1, "RSP": 0.2, @@ -28,26 +29,28 @@ "PA": -4, } -# Keep normalization fixed for valid between-frame comparisons. -vmin = min(values.values()) -vmax = max(values.values()) - +# Example settings. atlas_name = "allen_mouse_25um" orientation = "frontal" cmap = "Reds" step_um = 500 fps = 3 +vmin = min(values.values()) +vmax = max(values.values()) + axis_idx = {"frontal": 0, "horizontal": 1, "sagittal": 2}[orientation] atlas = BrainGlobeAtlas(atlas_name) -# Calculate the full range of slice positions along the selected axis. + +# Build the slice positions for the selected axis. max_pos_um = atlas.reference.shape[axis_idx] * atlas.resolution[axis_idx] positions = list(range(0, int(max_pos_um) + 1, step_um)) +# Set up the figure and a shared colorbar. fig, ax = plt.subplots(figsize=(10, 4)) -# Fixed colour normalization for fair comparison across frames. +# Keep the same color scale across frames. norm = Normalize(vmin=vmin, vmax=vmax) sm = ScalarMappable(norm=norm, cmap=cmap) sm.set_array([]) @@ -55,9 +58,11 @@ def update_frames(frame_idx): + """Draw one animation frame for the current slice position.""" ax.clear() pos = positions[frame_idx] + # Recreate the heatmap for this slice position. heatmap = bgh.Heatmap( values, position=pos, @@ -70,6 +75,7 @@ def update_frames(frame_idx): ) heatmap.plot_subplot(fig, ax, show_cbar=False) ax.set_title(f"Position: {pos} um") + # Show the current frame number. ax.text( 0.02, 0.98, @@ -81,7 +87,7 @@ def update_frames(frame_idx): bbox={"facecolor": "white", "alpha": 0.7, "edgecolor": "none"}, ) - +# Build the animation by calling the update function for each frame. ani = FuncAnimation( fig, update_frames, @@ -90,6 +96,7 @@ def update_frames(frame_idx): repeat=False, ) +# Save the animation next to this example script. output_path = Path(__file__).with_name("brain_animation.gif") ani.save(output_path, writer="pillow", fps=fps) From e0de6ae6c0b90f7746370f2dff8b8dcf18f6e297 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Thu, 2 Jul 2026 17:56:50 +0000 Subject: [PATCH 5/5] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- examples/heatmap_animated.py | 1 + 1 file changed, 1 insertion(+) diff --git a/examples/heatmap_animated.py b/examples/heatmap_animated.py index f515525..11db3af 100644 --- a/examples/heatmap_animated.py +++ b/examples/heatmap_animated.py @@ -87,6 +87,7 @@ def update_frames(frame_idx): bbox={"facecolor": "white", "alpha": 0.7, "edgecolor": "none"}, ) + # Build the animation by calling the update function for each frame. ani = FuncAnimation( fig,