Skip to content

Commit 463542b

Browse files
committed
spike sorting
1 parent b901f4e commit 463542b

File tree

27 files changed

+2535
-106
lines changed

27 files changed

+2535
-106
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
# Changes
22

33
## August 13, 2025
4+
- Added job cancellation functionality to JobStatusHandler component with red "Cancel Job" button for running jobs
45
- Added continuous integration workflow to test neurosift Python package installation, NWB file creation with pynwb, and CLI functionality
56

67
## August 11, 2025

job_runners/neurosift_job_runner/pyproject.toml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@ dependencies = [
1717
"numpy",
1818
"scipy",
1919
"lindi",
20-
"dendro>=0.6.22",
2120
"rastermap",
2221
"click",
2322
]

job_runners/neurosift_job_runner_2/pyproject.toml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@ dependencies = [
1717
"numpy",
1818
"scipy",
1919
"lindi",
20-
"dendro>=0.6.22",
2120
"click",
2221
]
2322

Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
1+
# Byte-compiled / optimized / DLL files
2+
__pycache__/
3+
*.py[cod]
4+
*$py.class
5+
6+
# C extensions
7+
*.so
8+
9+
# Distribution / packaging
10+
.Python
11+
build/
12+
develop-eggs/
13+
dist/
14+
downloads/
15+
eggs/
16+
.eggs/
17+
lib/
18+
lib64/
19+
parts/
20+
sdist/
21+
var/
22+
wheels/
23+
share/python-wheels/
24+
*.egg-info/
25+
.installed.cfg
26+
*.egg
27+
28+
# PyInstaller
29+
# Usually these files are written by a python script from a template
30+
# before PyInstaller builds the exe, so as to inject date/other infos into it.
31+
*.manifest
32+
*.spec
33+
34+
# Installer logs
35+
pip-log.txt
36+
pip-delete-this-directory.txt
37+
38+
# Unit test / coverage reports
39+
htmlcov/
40+
.tox/
41+
.nox/
42+
.coverage
43+
.coverage.*
44+
.cache
45+
nosetests.xml
46+
coverage.xml
47+
*.cover
48+
*.py,cover
49+
.hypothesis/
50+
.pytest_cache/
51+
cover/
52+
53+
# Translations
54+
*.mo
55+
*.pot
56+
57+
# Django stuff:
58+
*.log
59+
local_settings.py
60+
db.sqlite3
61+
62+
# Flask stuff:
63+
instance/
64+
.webassets-cache
65+
66+
# Scrapy stuff:
67+
.scrapy
68+
69+
# Sphinx documentation
70+
docs/_build/
71+
72+
# PyBuilder
73+
target/
74+
75+
# Jupyter Notebook
76+
.ipynb_checkpoints
77+
78+
# IPython
79+
profile_default/
80+
ipython_config.py
81+
82+
# pyenv
83+
.python-version
84+
85+
# celery beat schedule file
86+
celerybeat-schedule
87+
celerybeat.pid
88+
89+
# SageMath parsed files
90+
*.sage.py
91+
92+
# Environments
93+
.env
94+
.venv
95+
env/
96+
venv/
97+
ENV/
98+
env.bak/
99+
venv.bak/
100+
101+
# Spyder project settings
102+
.spyderproject
103+
.spyderworkspace
104+
105+
# Rope project settings
106+
.ropeproject
107+
108+
# mkdocs documentation
109+
/site
110+
111+
# mypy
112+
.mypy_cache/
113+
.dmypy.json
114+
dmypy.json
115+
116+
# Pyre type checker
117+
.pyre/
118+
119+
# pytype static type analyzer
120+
.pytype/
121+
122+
# Cython debug symbols
123+
cython_debug/
124+
125+
# Docker
126+
.dockerignore
127+
128+
# VS Code
129+
.vscode/
130+
131+
# PyCharm
132+
.idea/
133+
134+
# Local environment variables
135+
.env.local
136+
.env.development.local
137+
.env.test.local
138+
.env.production.local
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
FROM python:3.10-slim
2+
3+
WORKDIR /app
4+
5+
# Install build dependencies
6+
RUN apt-get update && \
7+
apt-get install -y --no-install-recommends \
8+
build-essential \
9+
&& rm -rf /var/lib/apt/lists/*
10+
11+
# Copy the package files
12+
COPY pyproject.toml /app/
13+
COPY src /app/src/
14+
15+
# Install the package and its dependencies
16+
RUN pip install --no-cache-dir -e .
17+
18+
# Set environment variables
19+
ENV PYTHONUNBUFFERED=1
20+
21+
# Set the entry point to the CLI command
22+
ENTRYPOINT ["neurosift-job-runner-3"]
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
[build-system]
2+
requires = ["hatchling"]
3+
build-backend = "hatchling.build"
4+
5+
[project]
6+
name = "neurosift_job_runner_3"
7+
version = "0.1.0"
8+
description = "Command line tool for running Neurosift jobs"
9+
authors = [
10+
{ name = "Jeremy Magland" },
11+
]
12+
# readme = "README.md"
13+
requires-python = ">=3.8"
14+
dependencies = [
15+
"requests",
16+
"pydantic",
17+
"numpy",
18+
"scipy",
19+
"spikeinterface",
20+
"mountainsort5",
21+
"click",
22+
]
23+
24+
[project.scripts]
25+
neurosift-job-runner-3 = "neurosift_job_runner_3.cli:main"
26+
27+
[tool.hatch.build.targets.wheel]
28+
packages = ["src/neurosift_job_runner_3"]
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
"""Neurosift Job Runner package.
2+
3+
A job runner for executing Neurosift jobs from the command line.
4+
"""
5+
6+
from .core import process_job
7+
from .cli import cli
8+
9+
__all__ = ["process_job", "cli"]
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
#!/usr/bin/env python3
2+
3+
import sys
4+
import logging
5+
from typing import Optional
6+
7+
import click
8+
9+
from .job_utils import get_job, update_job_status
10+
from .processors import (
11+
process_mountainsort5_job,
12+
)
13+
14+
15+
@click.group()
16+
@click.version_option()
17+
def cli() -> None:
18+
"""Neurosift Job Runner - Command line tool for running Neurosift jobs."""
19+
pass
20+
21+
22+
@cli.command()
23+
@click.argument("job_id")
24+
@click.option("--api-base-url", help="Base URL for the API", envvar="NEUROSIFT_API_URL")
25+
def run_job(job_id: str, api_base_url: Optional[str] = None) -> None:
26+
"""Run a specific job by ID.
27+
28+
JOB_ID is the ID of the job to run.
29+
"""
30+
try:
31+
# Get job information
32+
job = get_job(job_id, api_base_url=api_base_url)
33+
34+
if job["status"] not in ["pending"]:
35+
click.echo(
36+
f"Error: Job {job_id} is not pending (status: {job['status']})",
37+
err=True,
38+
)
39+
sys.exit(1)
40+
41+
click.echo(f"Processing job {job_id} of type {job['type']}")
42+
43+
# Process job based on type
44+
handler_map = {"mountainsort5": process_mountainsort5_job}
45+
46+
handler = handler_map.get(job["type"])
47+
if handler:
48+
handler(job, api_base_url=api_base_url)
49+
else:
50+
error_msg = f"Unknown job type: {job['type']}"
51+
click.echo(f"Error: {error_msg}", err=True)
52+
update_job_status(
53+
job_id,
54+
{"status": "failed", "error": error_msg},
55+
api_base_url=api_base_url,
56+
)
57+
sys.exit(1)
58+
59+
except Exception as e:
60+
click.echo(f"Error processing job: {e}", err=True)
61+
sys.exit(1)
62+
63+
64+
def main() -> None:
65+
"""Entry point for the neurosift-job-runner-2 command-line tool."""
66+
cli(auto_envvar_prefix="NEUROSIFT")
67+
68+
69+
if __name__ == "__main__":
70+
main()
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
from typing import Optional
2+
import logging
3+
import sys
4+
5+
from .job_utils import get_job, update_job_status
6+
7+
8+
# Lazy imports for job processors to avoid loading all dependencies upfront
9+
def get_job_processor(job_type: str):
10+
"""Dynamically import and return the appropriate job processor."""
11+
if job_type == "image_series_to_mp4":
12+
from .processors.image_series_to_mp4 import process_image_series_to_mp4_job
13+
14+
return process_image_series_to_mp4_job
15+
else:
16+
raise ValueError(f"Unknown job type: {job_type}")
17+
18+
19+
def process_job(job_id: str, api_base_url: Optional[str] = None) -> None:
20+
"""Process a job by its ID.
21+
22+
Args:
23+
job_id: The ID of the job to process
24+
api_base_url: Optional base URL for the API
25+
26+
Raises:
27+
SystemExit: If job processing fails
28+
"""
29+
try:
30+
# Get job information
31+
kwargs = {}
32+
if api_base_url:
33+
kwargs["api_base_url"] = api_base_url
34+
job = get_job(job_id, **kwargs)
35+
36+
if job["status"] not in ["pending"]:
37+
error_msg = f"Job {job_id} is not pending (status: {job['status']})"
38+
logging.error(error_msg)
39+
sys.exit(1)
40+
41+
logging.info(f"Processing job {job_id} of type {job['type']}")
42+
43+
try:
44+
# Get the appropriate processor for this job type
45+
process_func = get_job_processor(job["type"])
46+
# Process the job
47+
process_func(job, **kwargs)
48+
except ImportError as e:
49+
error_msg = f"Failed to import processor for job type {job['type']}: {e}"
50+
logging.error(error_msg)
51+
update_job_status(
52+
job_id, {"status": "failed", "error": error_msg}, **kwargs
53+
)
54+
sys.exit(1)
55+
except Exception as e:
56+
error_msg = f"Error processing job: {e}"
57+
logging.error(error_msg)
58+
update_job_status(job_id, {"status": "failed", "error": str(e)}, **kwargs)
59+
sys.exit(1)
60+
61+
except Exception as e:
62+
logging.error(f"Error processing job: {e}")
63+
sys.exit(1)

0 commit comments

Comments
 (0)