Skip to content
Open
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
17 changes: 14 additions & 3 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,10 +1,21 @@
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
settings.json
_data/*
.DS_Store

# Distribution / packaging
*.egg
*.egg-info/
uv.lock

# IDE / editor settings
settings.json

# macOS system files
.DS_Store

# Data directories
_data/*

# Logs and temporary files
logs/*
temp/*
19 changes: 19 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# Makefile for ELK project

# Default environment (production or development)
ELK_ENV ?= production

# Main target: run the project
run:
@echo "Running in $(ELK_ENV) environment..."
@ELK_ENV=$(ELK_ENV) uv run src/main.py

# Optional: create virtual environment using uv if needed
venv:
@echo "Ensuring virtual environment exists..."
@uv venv

# Clean virtual environment (optional)
clean:
@echo "Removing virtual environment..."
@rm -rf venv
21 changes: 10 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,15 +38,18 @@ pip install uv

## Running the App

Once `uv` is installed, navigate to the project's root directory and run:
Once the project is set up, navigate to the project root directory and use the provided Makefile to run the app:

## Running the App
```sh
# Run with the default environment (production)
make run

Once `uv` is installed, navigate to the project's root directory and run:
# Run in development environment
make run ELK_ENV=development

```sh
uv run src/main.py
uv run src/main.py
### Optional:
make venv # Creates/ensures the virtual environment exists
make clean # Clean the virtual environment
```

On the first run, this command creates a virtual environment, installs all dependencies, and starts the app. This may take a moment but only happens the first time.
Expand Down Expand Up @@ -75,7 +78,7 @@ For help and support, please contact:

## Version

The current version of the package is **0.7.1**.
The current version of the package is **0.8.0**.

## License

Expand Down Expand Up @@ -240,7 +243,3 @@ Ensure your pull request includes:

- A clear description of what the changes do and why they are necessary.
- Any relevant issue numbers.

---

This `README.md` provides an overview of the ELK package, including its key features, installation instructions, supported formats, authorship, contact information, current version, and license details. For any additional information or assistance, please reach out to the provided contact emails.
4 changes: 0 additions & 4 deletions elk.command

This file was deleted.

24 changes: 14 additions & 10 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,24 +1,28 @@
[project]
name = "electrodelocalizationkit"
version = "0.3.1"
name = "electrode-localization-kit"
version = "0.8.0"
description = "A tool for rapid EEG electrode localization using IR stereo cameras or MRI."
readme = "README.md"
requires-python = ">=3.11, <3.12"
license = { file = "LICENSE" }
license = "GPL-3.0-or-later"
license-files = ["LICEN[CS]E"]
requires-python = "==3.11.*"

dependencies = [
"nibabel==5.2.1",
"vtk==9.2.6",
"PyQt6==6.7.0",
"numpy==1.25.0",
"opencv_python==4.8.1.78",
"pandas==2.2.2",
"PyQt6==6.7.0",
"PyQt6_sip==13.6.0",
"scipy==1.15.3",
"nibabel==5.2.1",
"vedo==2023.4.6",
"vtk==9.2.6"
"PyQt6_sip==13.6.0",
"scikit_learn==1.7.1",
"scikit_image==0.25.2",
"opencv_python==4.8.1.78",
]

[tool.black]
line-length = 100

[tool.ruff]
line-length = 100
line-length = 100
49 changes: 40 additions & 9 deletions requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -8,58 +8,89 @@ deprecated==1.2.18
# via vedo
fonttools==4.56.0
# via matplotlib
imageio==2.37.0
# via scikit-image
joblib==1.5.2
# via scikit-learn
kiwisolver==1.4.8
# via matplotlib
lazy-loader==0.4
# via scikit-image
matplotlib==3.10.0
# via vtk
networkx==3.5
# via scikit-image
nibabel==5.2.1
# via electrodelocalizationkit (pyproject.toml)
# via electrode-localization-kit (pyproject.toml)
numpy==1.25.0
# via
# electrodelocalizationkit (pyproject.toml)
# electrode-localization-kit (pyproject.toml)
# contourpy
# imageio
# matplotlib
# nibabel
# opencv-python
# pandas
# scikit-image
# scikit-learn
# scipy
# tifffile
# vedo
opencv-python==4.8.1.78
# via electrodelocalizationkit (pyproject.toml)
# via electrode-localization-kit (pyproject.toml)
packaging==24.2
# via
# lazy-loader
# matplotlib
# nibabel
# scikit-image
pandas==2.2.2
# via electrodelocalizationkit (pyproject.toml)
# via electrode-localization-kit (pyproject.toml)
pillow==11.1.0
# via matplotlib
# via
# imageio
# matplotlib
# scikit-image
pygments==2.19.1
# via vedo
pyparsing==3.2.1
# via matplotlib
pyqt6==6.7.0
# via electrodelocalizationkit (pyproject.toml)
# via electrode-localization-kit (pyproject.toml)
pyqt6-qt6==6.7.3
# via pyqt6
pyqt6-sip==13.6.0
# via
# electrodelocalizationkit (pyproject.toml)
# electrode-localization-kit (pyproject.toml)
# pyqt6
python-dateutil==2.9.0.post0
# via
# matplotlib
# pandas
pytz==2025.1
# via pandas
scikit-image==0.25.2
# via electrode-localization-kit (pyproject.toml)
scikit-learn==1.7.1
# via electrode-localization-kit (pyproject.toml)
scipy==1.15.3
# via
# electrode-localization-kit (pyproject.toml)
# scikit-image
# scikit-learn
six==1.17.0
# via python-dateutil
threadpoolctl==3.6.0
# via scikit-learn
tifffile==2025.10.16
# via scikit-image
tzdata==2025.1
# via pandas
vedo==2023.4.6
# via electrodelocalizationkit (pyproject.toml)
# via electrode-localization-kit (pyproject.toml)
vtk==9.2.6
# via
# electrodelocalizationkit (pyproject.toml)
# electrode-localization-kit (pyproject.toml)
# vedo
wrapt==1.17.2
# via deprecated
11 changes: 6 additions & 5 deletions src/config/sizes.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,16 @@
Size configuration for the electrodes and flagposts.
"""


class ElectrodeSizes:
HEADSCAN_ELECTRODE_SIZE = 0.02
HEADSCAN_ELECTRODE_SIZE = 3.5
MRI_ELECTRODE_SIZE = 0.02
LABEL_ELECTRODE_SIZE = 0.04

HEADSCAN_FLAGPOST_SIZE = 0.6
MRI_FLAGPOST_SIZE = 0.6
LABEL_FLAGPOST_SIZE = 0.6
HEADSCAN_FLAGPOST_HEIGHT = 0.05

HEADSCAN_FLAGPOST_HEIGHT = 5.0
MRI_FLAGPOST_HEIGHT = 0.05
LABEL_FLAGPOST_HEIGHT = 0.05
LABEL_FLAGPOST_HEIGHT = 0.05
7 changes: 5 additions & 2 deletions src/data/loader.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,13 @@
import vedo as vd
import nibabel as nib

from processing_models.mesh.mesh_loader import MeshLoader

def load_head_surface_mesh_from_file(filename: str) -> vd.Mesh:

def load_head_surface_mesh_from_file(surface_file: str, texture_file: str = None) -> vd.Mesh:
"""Loads a head surface mesh from a file."""
return vd.Mesh(filename)
mesh_loader = MeshLoader(surface_file, texture_file)
return mesh_loader.mesh_preprocessed.clone()


def load_mri_surface_mesh_from_file(filename: str) -> vd.Mesh:
Expand Down
18 changes: 9 additions & 9 deletions src/data_models/cap_model.py
Original file line number Diff line number Diff line change
Expand Up @@ -117,15 +117,15 @@ def insert_electrode(self, electrode: Electrode, parent=QModelIndex()) -> None:
electrode.coordinates, electrode.modality, include_fiducials=True
)

too_close_electrodes = [
d[0]
for d in distances
if (
d[1] <= ElectrodeSizes.HEADSCAN_ELECTRODE_SIZE / 2
or d[1] <= ElectrodeSizes.MRI_ELECTRODE_SIZE / 2
or d[1] <= ElectrodeSizes.LABEL_ELECTRODE_SIZE / 2
)
]
min_distance = -1
if electrode.modality == ModalitiesMapping.HEADSCAN:
min_distance = ElectrodeSizes.HEADSCAN_ELECTRODE_SIZE / 2
elif electrode.modality == ModalitiesMapping.MRI:
min_distance = ElectrodeSizes.MRI_ELECTRODE_SIZE / 2
elif electrode.modality == ModalitiesMapping.REFERENCE:
min_distance = ElectrodeSizes.LABEL_ELECTRODE_SIZE / 2

too_close_electrodes = [d[0] for d in distances if (d[1] <= min_distance)]

if len(too_close_electrodes) > 0:
return
Expand Down
11 changes: 7 additions & 4 deletions src/data_models/head_models.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,16 +37,16 @@ def __init__(self, surface_file: str, texture_file: str | None = None):
self.texture_file = texture_file

self.mesh = None
self.mesh = load_head_surface_mesh_from_file(surface_file)
self.mesh = load_head_surface_mesh_from_file(surface_file, texture_file)

self.modality = ModalitiesMapping.HEADSCAN
self.fiducials = []

self.normalization_scale = 1
self.normalization_scale = 1000

self._registered = False

self.normalize()
# self.normalize()

self.apply_texture()

Expand All @@ -56,9 +56,12 @@ def normalize(self):
def rescale_to_original_size(self):
self.normalization_scale = rescale_to_original_size(self.mesh, self.normalization_scale) # type: ignore

def apply_texture(self):
def apply_texture(self, texture_file: str | None = None):
if self.texture_file is not None:
self.mesh = self.mesh.texture(self.texture_file) # type: ignore
elif texture_file is not None:
self.texture_file = texture_file
self.mesh = self.mesh.texture(texture_file)

def register_mesh(self, surface_registrator: BaseSurfaceRegistrator) -> np.ndarray:
transform_matrix = surface_registrator.register() # type: ignore
Expand Down
2 changes: 1 addition & 1 deletion src/fileio/locations.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ def load_locations(
# model.remove_electrode_by_id(electrode_id)

if ENV == "development":
files["locations"] = "sample_data/measured_electrodes.ced"
files["locations"] = "sample_data/electrode_locations.ced"
else:
file_path, _ = QFileDialog.getOpenFileName(
None,
Expand Down
Loading