Skip to content
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
7 changes: 5 additions & 2 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ dependencies = [
"fonttools==4.63.0",
"flet==0.86.5",
"flet-permission-handler==0.86.5",
"hyperkey-file-opener @ file:///D:/Masters%20of%20computing/Tech%20Launcher/hyperkey/hyperkey-file-opener",
"hyperkey-file-opener @ file://C:/Users/Samwi/OneDrive/Documents/Uni/Tech%20Launcher/hyperkey/hyperkey_file_opener",
"greenlet==3.5.1",
"kiwisolver==1.5.0",
"Markdown==3.10.3",
Expand All @@ -33,4 +33,7 @@ dependencies = [
module = "hyperkey"

[tool.flet.android.permission]
"android.permission.MANAGE_EXTERNAL_STORAGE" = true
"android.permission.MANAGE_EXTERNAL_STORAGE" = true

[tool.flet.android]
extract_packages = ["matplotlib"]
34 changes: 21 additions & 13 deletions scripts/visualise_heatmap.py
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,8 @@ def calculate_square_grid(n_measurements):

def validate_row_range_columns(csv_path):
"""
Check if CSV contains 'row' and 'range' columns (case-insensitive) and validate them.
Check if CSV contains 'row' and either 'range' or 'col' columns
(case-insensitive) and validate them.
Returns (rows_list, ranges_list) if valid, (None, None) if columns don't exist.
Raises ValueError if columns exist but contain non-integer values.
"""
Expand All @@ -105,27 +106,31 @@ def validate_row_range_columns(csv_path):
cols_lower = {col.lower(): col for col in raw_df.columns}

has_row = 'row' in cols_lower
has_range = 'range' in cols_lower
coordinate_col = cols_lower.get('range') or cols_lower.get('col')
has_coordinate = coordinate_col is not None

# If neither exists, return None and use square grid
if not has_row and not has_range:
if not has_row and not has_coordinate:
return None, None

# If only one exists, that's an error
if has_row != has_range:
missing = 'range' if has_row else 'row'
raise ValueError(f"Found 'row' column but missing '{missing}' column. Both must exist together.")
if has_row != has_coordinate:
raise ValueError(
f"Found 'row' column but missing 'range' or 'col' column. "
"Both must exist together."
)

# Both columns exist - validate them strictly
row_col = cols_lower['row']
range_col = cols_lower['range']

try:
row_vals = pd.to_numeric(raw_df[row_col], errors='coerce')
range_vals = pd.to_numeric(raw_df[range_col], errors='coerce')
range_vals = pd.to_numeric(raw_df[coordinate_col], errors='coerce')

if row_vals.isna().any() or range_vals.isna().any():
raise ValueError("Found non-numeric values in 'row' or 'range' columns")
raise ValueError(
f"Found non-numeric values in 'row' or '{coordinate_col}' columns"
)

# Check if all values are integers
if not (row_vals == row_vals.astype(int)).all() or not (range_vals == range_vals.astype(int)).all():
Expand Down Expand Up @@ -176,16 +181,19 @@ def load_location_mapping(location_path, measurement_names):
"""Load a location file and map each measurement name to a grid coordinate."""
location_df = pd.read_csv(location_path, header=0)
cols_lower = {col.lower(): col for col in location_df.columns}
coordinate_col = cols_lower.get('range') or cols_lower.get('col')

if 'row' in cols_lower and 'range' in cols_lower:
if 'row' in cols_lower and coordinate_col is not None:
row_col = cols_lower['row']
range_col = cols_lower['range']

row_vals = pd.to_numeric(location_df[row_col], errors='coerce')
range_vals = pd.to_numeric(location_df[range_col], errors='coerce')
range_vals = pd.to_numeric(location_df[coordinate_col], errors='coerce')

if row_vals.isna().any() or range_vals.isna().any():
raise ValueError("Found non-numeric values in location file 'row' or 'range' columns")
raise ValueError(
f"Found non-numeric values in location file 'row' or "
f"'{coordinate_col}' columns"
)

if not (row_vals == row_vals.astype(int)).all() or not (range_vals == range_vals.astype(int)).all():
raise ValueError("All values in location file 'row' and 'range' columns must be integers")
Expand Down
33 changes: 33 additions & 0 deletions tests/test_visualise_heatmap.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
"""Tests for heatmap coordinate parsing."""

import sys
from pathlib import Path

import pandas as pd
import pytest

sys.path.insert(0, str(Path(__file__).parent.parent / "scripts"))

from visualise_heatmap import validate_row_range_columns


def test_validate_row_col_columns(tmp_path):
csv_path = tmp_path / "spectral.csv"
pd.DataFrame({"row": [1, 2], "col": [3, 4]}).to_csv(csv_path, index=False)

assert validate_row_range_columns(csv_path) == ([1, 2], [3, 4])


def test_validate_row_range_columns_still_supported(tmp_path):
csv_path = tmp_path / "spectral.csv"
pd.DataFrame({"row": [1, 2], "range": [3, 4]}).to_csv(csv_path, index=False)

assert validate_row_range_columns(csv_path) == ([1, 2], [3, 4])


def test_validate_row_without_coordinate_column_raises(tmp_path):
csv_path = tmp_path / "spectral.csv"
pd.DataFrame({"row": [1, 2], "value": [3, 4]}).to_csv(csv_path, index=False)

with pytest.raises(ValueError, match="range.*col"):
validate_row_range_columns(csv_path)
Loading