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
2 changes: 1 addition & 1 deletion LICENSE
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
MIT License

Copyright (c) 2026 Wyss Center
Copyright (c) 2026 Wyss Center for Bio and Neuroengineering

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
Expand Down
12 changes: 6 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
A desktop application for annotating Deep Brain Stimulation (DBS) clinical programming sessions. Built for clinicians and researchers working with DBS systems (Medtronic Percept and others).

**Version:** derived from `dbs_annotator.__version__`
**Publisher:** Wyss Center (contact: lucia.poma@wysscenter.ch)
**Publisher:** Wyss Center for Bio and Neuroengineering (contact: lucia.poma@wysscenter.ch)

## For End Users

Expand Down Expand Up @@ -98,7 +98,7 @@ App_ClinicalDBSAnnot/
│ ├── config.py # App configuration and constants
│ └── config_electrode_models.py # Electrode model definitions
├── styles/ # QSS theme files (Briefcase + dev; see resource_path)
├── icons/ # Application icons
├── icons/ # Application icons (e.g. logosimple/ bundle)
├── scripts/ # Utility scripts
└── pyproject.toml # Project configuration and dependencies
```
Expand Down Expand Up @@ -132,19 +132,19 @@ uv run briefcase package windows -p zip # avoids WiX; omit -p (MSI) when WiX

`macOS` builds on Apple Silicon produce **arm64** artifacts when `universal_build = false` is set under `[tool.briefcase.app.dbs_annotator.macOS]` in `pyproject.toml`.

**Windows Briefcase quirks:** keep `[tool.briefcase].version` in sync with `dbs_annotator.__version__` (Briefcase does not use Hatch’s dynamic `[project]` version). If `briefcase build` fails at **“Setting stub app details”** / RCEdit with **“Unable to commit changes”**, exclude the repo or `build\` from real-time antivirus scanning and retry (see [Briefcase issue #1530](https://github.com/beeware/briefcase/issues/1530)).
**Windows Briefcase quirks:** keep `[tool.briefcase].version` in sync with `dbs_annotator.__version__` (Briefcase does not use Hatch’s dynamic `[project]` version). Bump both in one step with `uv run python scripts/release_prepare.py <version>` (or `--bump …`). If `briefcase build` fails at **“Setting stub app details”** / RCEdit with **“Unable to commit changes”**, exclude the repo or `build\` from real-time antivirus scanning and retry (see [Briefcase issue #1530](https://github.com/beeware/briefcase/issues/1530)).

The Windows stub binary is named **`DBSAnnotator.exe`** (from `[tool.briefcase.app.dbs_annotator].formal_name`). After changing that field, run **`briefcase create windows app`** again (or delete `build\dbs_annotator\windows`) before **`briefcase build`**.

Icons for the **stub**, **MSI/ZIP**, and **Qt** (`QApplication` / window chrome) come from **`icons/logoneutral.ico`** and **`icons/logoneutral.png`** at the **repository root** (`icon = "icons/logoneutral"` in `pyproject.toml`). That folder is also listed as a Briefcase **`sources`** entry so a sibling `icons\` directory is shipped next to the app package inside the bundle. Runtime lookup uses `resource_path()` (package dir, then `src\icons`, then repo-root `icons\`).
Icons for the **stub**, **MSI/ZIP**, and **Qt** (`QApplication` / window chrome) live under **`icons/logosimple/`**: **`logosimple.ico`**, **`logosimple.png`**, plus **`logosimple-{16,32,64,128,256,512}.png`** for **Linux system** (BeeWare copies them into the Freedesktop hicolor tree; all six are listed in the upstream `briefcase-linux-system-template`). **`logosimple.icns`** is for macOS (build with `iconutil` on a Mac; see `scripts/build_app_icons.py`). Configure with `icon = "icons/logosimple/logosimple"` in `pyproject.toml`. The repo-root **`icons/`** tree is a Briefcase **`sources`** entry and is shipped next to the app package; runtime lookup uses `resource_path()` (package dir, then `src/icons`, then repo-root `icons/`).

**Inventory (for packaging):**

| Area | Notes |
| --- | --- |
| App type | Qt **GUI** (`console_app` is false by default). |
| Heavy deps | `PySide6`, `matplotlib`, `pandas`, `python-docx`, `docx2pdf` (Windows: `pywin32`; macOS: `appscript` via `docx2pdf`). |
| Data files | JSON presets under `src/dbs_annotator/config/`; QSS and SVG under repo-root **`styles/`** (also a Briefcase **`sources`** entry); **`icons/logoneutral.{ico,png}`** at repo root (Briefcase + Qt). |
| Data files | JSON presets under `src/dbs_annotator/config/`; QSS and SVG under repo-root **`styles/`** (also a Briefcase **`sources`** entry); app icons under **`icons/logosimple/`** (Briefcase + Qt). |
| macOS entitlements | Add an entitlements plist only if you enable the Hardened Runtime and need extra capabilities (network is usually fine without custom entitlements). |

### Release signing (distribution outside store)
Expand Down Expand Up @@ -209,4 +209,4 @@ MIT License — see [LICENSE](LICENSE) for details.

## Contact

Wyss Center — lucia.poma@wysscenter.ch
Wyss Center for Bio and Neuroengineering — lucia.poma@wysscenter.ch
6 changes: 4 additions & 2 deletions docs/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,13 @@

_DIST_NAME = "dbs-annotator"

from dbs_annotator.config import APP_LEAD_AUTHOR, ORGANIZATION_PUBLISHER

project = "DBS Annotator"
author = "Wyss Center"
author = APP_LEAD_AUTHOR
release = metadata.version(_DIST_NAME)
version = ".".join(release.split(".")[:2])
copyright = f"2025-{datetime.now().year}, {author}"
copyright = f"2025-{datetime.now().year}, {ORGANIZATION_PUBLISHER}"

extensions = [
"sphinx.ext.autodoc",
Expand Down
1 change: 1 addition & 0 deletions docs/faq.rst
Original file line number Diff line number Diff line change
Expand Up @@ -159,4 +159,5 @@ Contact & Support

For bug reports, feature requests, or questions:

| **Wyss Center for Bio and Neuroengineering**
| **Lucia Poma** — lucia.poma@wysscenter.ch
3 changes: 2 additions & 1 deletion docs/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,8 @@ adjustments, to the automatic generation of structured Word and PDF reports.
Developed at the **Brain Modulation Lab, Massachusetts General Hospital**.

.. note::
Version |release|. Contact: lucia.poma@wysscenter.ch
Version |release|. Publisher: Wyss Center for Bio and Neuroengineering.
Contact: lucia.poma@wysscenter.ch

----

Expand Down
Binary file removed icons/logobml.icns
Binary file not shown.
Binary file removed icons/logobml.ico
Binary file not shown.
Binary file removed icons/logobml.iconset/icon_128x128.png
Binary file not shown.
Binary file removed icons/logobml.iconset/icon_128x128@2x.png
Binary file not shown.
Binary file removed icons/logobml.iconset/icon_16x16.png
Binary file not shown.
Binary file removed icons/logobml.iconset/icon_16x16@2x.png
Binary file not shown.
Binary file removed icons/logobml.iconset/icon_256x256.png
Binary file not shown.
Binary file removed icons/logobml.iconset/icon_256x256@2x.png
Binary file not shown.
Binary file removed icons/logobml.iconset/icon_32x32.png
Binary file not shown.
Binary file removed icons/logobml.iconset/icon_32x32@2x.png
Binary file not shown.
Binary file removed icons/logobml.iconset/icon_512x512.png
Binary file not shown.
Binary file removed icons/logobml.iconset/icon_512x512@2x.png
Binary file not shown.
Binary file removed icons/logobml.png
Binary file not shown.
Binary file removed icons/logoneutral.iconset/icon_128x128.png
Binary file not shown.
Binary file removed icons/logoneutral.iconset/icon_128x128@2x.png
Binary file not shown.
Binary file removed icons/logoneutral.iconset/icon_16x16.png
Binary file not shown.
Binary file removed icons/logoneutral.iconset/icon_16x16@2x.png
Binary file not shown.
Binary file removed icons/logoneutral.iconset/icon_256x256.png
Binary file not shown.
Binary file removed icons/logoneutral.iconset/icon_256x256@2x.png
Binary file not shown.
Binary file removed icons/logoneutral.iconset/icon_32x32.png
Binary file not shown.
Binary file removed icons/logoneutral.iconset/icon_32x32@2x.png
Binary file not shown.
Binary file removed icons/logoneutral.iconset/icon_512x512.png
Binary file not shown.
Binary file removed icons/logoneutral.iconset/icon_512x512@2x.png
Binary file not shown.
File renamed without changes.
File renamed without changes.
File renamed without changes
Binary file added icons/logosimple/logosimple-128.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added icons/logosimple/logosimple-16.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added icons/logosimple/logosimple-256.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added icons/logosimple/logosimple-32.png
Binary file added icons/logosimple/logosimple-512.png
Binary file added icons/logosimple/logosimple-64.png
Binary file added icons/logosimple/logosimple.af
Binary file not shown.
Binary file added icons/logosimple/logosimple.ico
Binary file not shown.
Binary file added icons/logosimple/logosimple.iconset/icon_16x16.png
Binary file added icons/logosimple/logosimple.png
1 change: 1 addition & 0 deletions newsfragments/66.changed.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Consolidate branding under `icons/logosimple/` for Briefcase and Qt, add `scripts/build_app_icons.py` to generate platform icon sizes from a single source PNG, and document Linux `linux system` icon requirements.
8 changes: 5 additions & 3 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,7 @@ select = [
]

[tool.ruff.lint.per-file-ignores]
"docs/conf.py" = ["E402"]
"src/dbs_annotator/views/wizard_window.py" = ["E501"]

[tool.codespell]
Expand All @@ -140,6 +141,7 @@ quiet = false

[tool.uv]
exclude-newer = "1 week"
exclude-newer-package = { lxml = false }

[tool.towncrier]
package = "dbs_annotator"
Expand Down Expand Up @@ -178,7 +180,7 @@ bundle = "ch.wysscenter.dbsannotator"
# Briefcase requires a static version here even when [project] uses dynamic version
version = "0.4.0a1"
license = { file = "LICENSE" }
author = "WyssCenter"
author = "Lucia Poma"
author_email = "lucia.poma@wysscenter.ch"
url = "https://github.com/Brain-Modulation-Lab/App_ClinicalDBSAnnot"

Expand All @@ -193,8 +195,8 @@ It supports clinicians and researchers by organizing session information,
tracking stimulation settings, and generating standardized exports and reports.
"""
sources = ["src/dbs_annotator", "icons", "styles"]
icon = "icons/logoneutral"
installer_icon = "icons/logoneutral"
icon = "icons/logosimple/logosimple"
installer_icon = "icons/logosimple/logosimple"
requirement_installer_args = [
"--index-url",
"https://pypi.org/simple",
Expand Down
212 changes: 212 additions & 0 deletions scripts/build_app_icons.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,212 @@
#!/usr/bin/env python3
"""Build desktop icon assets (Briefcase + Qt) from a single **square-ish** source PNG.

**Output directory** defaults to ``icons/logosimple/`` so all app-icon files for one
mark live together.

**Linux (``linux system`` / .deb):** BeeWare’s template lists exactly six PNGs:
``icon.16`` … ``icon.512`` in ``briefcase.toml``; Briefcase copies them from
``{icon}-{size}.png`` into ``usr/share/icons/hicolor/<size>/apps/<bundle id>.png``.
All six sizes are required (missing files keep the template placeholder).

**Windows:** one multi-size ``.ico`` (16, 32, 48, 64, 256).

**macOS .icns:** Apple’s ``iconutil`` reads a ``.iconset`` folder (fixed PNG names and
pixel sizes) and writes a **single** ``.icns`` binary. That is the only portable way
to produce a valid ``.icns``; run ``iconutil`` on macOS (or copy the ``.iconset`` to
a Mac). The ``.iconset`` is build input only — you can delete it after ``.icns`` exists.

**Source:** prefer ≥512 px for clean downscales.

Requires Pillow (pulled in via project deps e.g. matplotlib).
"""

from __future__ import annotations

import argparse
import shutil
import subprocess
import sys
from pathlib import Path

from PIL import Image

# Windows stub / MSI (BeeWare Windows icon format)
WIN_ICO_SIZES = (16, 32, 48, 64, 256)
# Linux system: briefcase-linux-system-template briefcase.toml path_index
LINUX_PNG_SIZES = (16, 32, 64, 128, 256, 512)
# macOS .icns: logical pixel size -> PNG file (same as `iconutil` / .iconset)
# Each tuple: (filename, width_px, height_px)
MACOS_ICONSET = (
("icon_16x16.png", 16, 16),
("icon_16x16@2x.png", 32, 32),
("icon_32x32.png", 32, 32),
("icon_32x32@2x.png", 64, 64),
("icon_128x128.png", 128, 128),
("icon_128x128@2x.png", 256, 256),
("icon_256x256.png", 256, 256),
("icon_256x256@2x.png", 512, 512),
("icon_512x512.png", 512, 512),
("icon_512x512@2x.png", 1024, 1024),
)


def _load_rgba(path: Path) -> Image.Image:
img = Image.open(path).convert("RGBA")
return img


def _letterbox_rgba(src: Image.Image) -> Image.Image:
"""Square canvas: transparent padding (canvas side = max of width, height)."""
w, h = src.size
m = max(w, h)
canvas = Image.new("RGBA", (m, m), (0, 0, 0, 0))
canvas.paste(src, ((m - w) // 2, (m - h) // 2), src)
return canvas


def build_icons(
source: Path,
out_dir: Path,
name: str,
master_px: int,
*,
write_master: bool,
) -> None:
out_dir = out_dir.resolve()
out_dir.mkdir(parents=True, exist_ok=True)
src = _load_rgba(source)
src_sq = _letterbox_rgba(src)

master = src_sq.copy()
if max(master.size) != master_px:
master = master.resize((master_px, master_px), Image.Resampling.LANCZOS)

master_path = out_dir / f"{name}.png"
if source.resolve() == master_path.resolve() and not write_master:
print(
f"Skip {master_path} (same as --source; "
f"use --write-master to re-encode to {master_px}px)",
file=sys.stderr,
)
else:
master.save(master_path, format="PNG", optimize=True)
print(f"Wrote {master_path}", file=sys.stderr)

# Windows .ico (one file, multiple embedded sizes)
ico_images: list[Image.Image] = []
for px in WIN_ICO_SIZES:
ico_img = src_sq.copy()
ico_img = ico_img.resize((px, px), Image.Resampling.LANCZOS)
ico_images.append(ico_img)
ico_path = out_dir / f"{name}.ico"
ico_images[0].save(
ico_path,
format="ICO",
sizes=[(i.width, i.height) for i in ico_images],
append_images=ico_images[1:],
)
print(f"Wrote {ico_path} sizes={list(WIN_ICO_SIZES)}", file=sys.stderr)

# Linux AppImage PNGs
for px in LINUX_PNG_SIZES:
li = src_sq.copy()
li = li.resize((px, px), Image.Resampling.LANCZOS)
p = out_dir / f"{name}-{px}.png"
li.save(p, format="PNG", optimize=True)
print(
f"Wrote {len(LINUX_PNG_SIZES)} Linux PNGs: {name}-<size>.png",
file=sys.stderr,
)

# macOS .iconset
iconset_dir = out_dir / f"{name}.iconset"
if iconset_dir.is_dir():
shutil.rmtree(iconset_dir)
iconset_dir.mkdir(parents=True)
for fname, w, h in MACOS_ICONSET:
im = src_sq.copy()
im = im.resize((w, h), Image.Resampling.LANCZOS)
im.save(iconset_dir / fname, format="PNG", optimize=True)
print(f"Wrote {iconset_dir}/ ({len(MACOS_ICONSET)} files)", file=sys.stderr)

icns_path = out_dir / f"{name}.icns"
if sys.platform == "darwin":
try:
# Writes ``{name}.icns`` next to ``{name}.iconset`` (overwrites if present).
subprocess.run(
["iconutil", "-c", "icns", f"{name}.iconset", "-o", f"{name}.icns"],
check=True,
capture_output=True,
text=True,
cwd=out_dir,
)
print(f"Wrote {icns_path} (iconutil)", file=sys.stderr)
except (subprocess.CalledProcessError, FileNotFoundError) as e:
print(
f"iconutil failed: {e}; on a Mac, run (cwd = output dir above):\n"
f" iconutil -c icns {name}.iconset -o {name}.icns",
file=sys.stderr,
)
else:
print(
f"Skip {icns_path} (not macOS). On macOS (cwd = {out_dir}):\n"
f" iconutil -c icns {name}.iconset -o {name}.icns",
file=sys.stderr,
)


def main() -> None:
root = Path(__file__).resolve().parents[1]
p = argparse.ArgumentParser(description=__doc__)
p.add_argument(
"--source",
type=Path,
default=root / "icons" / "logosimple" / "logosimple.png",
help="Source PNG (master artwork)",
)
p.add_argument(
"--out-dir",
type=Path,
default=root / "icons" / "logosimple",
help="Output directory (default: ./icons/logosimple)",
)
p.add_argument(
"--name",
default="logosimple",
help="Base filename without extension (default: logosimple)",
)
p.add_argument(
"--master-px",
type=int,
default=512,
help="Pixel size of {name}.png (default: 512)",
)
p.add_argument(
"--write-master",
action=argparse.BooleanOptionalAction,
default=None,
help=(
"Write {name}.png from source. If --source is that file, default is skip "
"re-encoding; use --write-master to replace."
),
)
args = p.parse_args()
if not args.source.is_file():
sys.exit(f"Not found: {args.source.resolve()}")
master_path = (args.out_dir / f"{args.name}.png").resolve()
if args.write_master is None:
write_master = args.source.resolve() != master_path
else:
write_master = args.write_master
build_icons(
source=args.source,
out_dir=args.out_dir,
name=args.name,
master_px=args.master_px,
write_master=write_master,
)


if __name__ == "__main__":
main()
5 changes: 4 additions & 1 deletion src/dbs_annotator/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,10 @@

__version__ = "0.4.0a1"
__app_name__ = "DBS Annotator"
__author__ = "Wyss Center"

from .config import APP_LEAD_AUTHOR

__author__ = APP_LEAD_AUTHOR

from .models import ClinicalScale, SessionData, SessionScale, StimulationParameters
from .views import WizardWindow
Expand Down
16 changes: 10 additions & 6 deletions src/dbs_annotator/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,25 +13,29 @@
APP_NAME = "DBS Annotator"
APP_VERSION = get_version()

# Legal / marketing organization label (not used for on-disk paths).
ORGANIZATION_NAME = "Wyss Center"
# Publisher (UI, About, docs). Not used for on-disk paths.
ORGANIZATION_PUBLISHER = "Wyss Center for Bio and Neuroengineering"

# Lead author (About; list same person first in package metadata).
APP_LEAD_AUTHOR = "Lucia Poma"

# Qt application identity for :func:`QStandardPaths` and :class:`QSettings`.
# Use ASCII without spaces so per-user directories never contain spaces
# (``%LOCALAPPDATA%\\<org>\\<app>\\``, Application Support on macOS, etc.).
FS_ORG_NAME = "WyssCenter"
FS_ORG_NAME = "WyssGeneva"
FS_APP_NAME = "DBSAnnotator"

# Canonical upstream (releases + issue tracker; keep aligned with updater repo slug).
APP_REPOSITORY_URL = "https://github.com/Brain-Modulation-Lab/App_ClinicalDBSAnnot"
APP_ISSUES_URL = f"{APP_REPOSITORY_URL}/issues"
# Primary contact for feedback (same person as APP_LEAD_AUTHOR).
UPDATE_FEEDBACK_EMAIL = "lucia.poma@wysscenter.ch"

# File paths (relative to executable)
ICON_FILENAME = "logoneutral.png"
ICO_FILENAME = "logoneutral.ico"
ICON_FILENAME = "logosimple.png"
ICO_FILENAME = "logosimple.ico"
STYLE_FILENAME = "style.qss"
ICONS_DIR = "icons"
ICONS_DIR = "icons/logosimple"

# Window size ratios for responsive design
WINDOW_SIZE_RATIO = {
Expand Down
Loading
Loading