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
160 changes: 160 additions & 0 deletions .github/workflows/desktop-app.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
# The desktop app: a double-clickable MEA-NAP for macOS (Apple silicon and
# Intel) and Windows, built with PyInstaller. See python/DESKTOP_APP_PLAN.md.
#
# When it runs:
# - an official release is published (v1.13.0, …) → build both, smoke-test
# them, attach the .dmg and the Setup.exe to that release;
# - by hand (Actions → Desktop app → Run workflow) → build both, keep them as
# downloadable workflow artifacts;
# - a pull request that touches the packaging → build and smoke-test, so a
# broken build is found before the release that needs it.
# Not on every merge: development builds are hundreds of MB each, and the
# dev-build workflow tags with GITHUB_TOKEN, which starts no other workflow.

name: Desktop app

on:
release:
types: [published]
workflow_dispatch:
pull_request:
paths:
- "packaging/**"
- ".github/workflows/desktop-app.yml"
- "src/meanap/frozen.py"
- "src/meanap/app_smoke.py"

permissions:
contents: write

jobs:
build:
# Development builds are pre-releases; only official releases get apps.
if: github.event_name != 'release' || !github.event.release.prerelease
strategy:
fail-fast: false
matrix:
include:
- os: macos-14 # Apple silicon
platform: macOS-arm64
- os: macos-15-intel # Intel
platform: macOS-Intel
- os: windows-latest
platform: Windows-x64
runs-on: ${{ matrix.os }}
timeout-minutes: 60
defaults:
run:
shell: bash
env:
# Windows consoles default to cp1252; MEA-NAP prints the odd arrow.
PYTHONUTF8: "1"
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0 # tags, for the build's version description

- uses: astral-sh/setup-uv@v6

- name: Install MEA-NAP and the build tools
# isosplit6 (MountainSort5) has no macOS wheel and is C++11 that
# doesn't say so; Apple's clang defaults to an older standard. It (and
# OASIS on Intel) is compiled here, by default for the runner's own
# macOS only: target the app's minimum (LSMinimumSystemVersion).
env:
CXXFLAGS: ${{ runner.os == 'macOS' && '-std=c++17' || '' }}
MACOSX_DEPLOYMENT_TARGET: ${{ runner.os == 'macOS' && '12.0' || '' }}
run: uv sync --frozen --extra oasis --extra sorting --group app

- name: Build
run: uv run --frozen python packaging/build_app.py

# Before the smoke test, so the first run is a cold start: nothing read
# from disk or scanned by antivirus yet. The second shows a warm start.
- name: Startup time (macOS)
if: runner.os == 'macOS'
env:
QT_QPA_PLATFORM: offscreen
run: |
for run in cold warm; do
echo "== $run start"
packaging/dist/MEA-NAP.app/Contents/MacOS/MEA-NAP --time-startup
done

- name: Startup time (Windows)
if: runner.os == 'Windows'
shell: pwsh
env:
QT_QPA_PLATFORM: offscreen
run: |
foreach ($run in "cold", "warm") {
"== $run start"
$p = Start-Process -FilePath "packaging\dist\MEA-NAP\MEA-NAP.exe" `
-ArgumentList "--time-startup" -Wait -PassThru
Get-Content "$env:LOCALAPPDATA\MEA-NAP\logs\meanap.log"
if ($p.ExitCode -ne 0) { exit $p.ExitCode }
}

- name: Smoke test (macOS)
if: runner.os == 'macOS'
env:
QT_QPA_PLATFORM: offscreen
run: packaging/dist/MEA-NAP.app/Contents/MacOS/MEA-NAP --smoke-test

- name: Smoke test (Windows)
if: runner.os == 'Windows'
shell: pwsh
env:
QT_QPA_PLATFORM: offscreen
# A windowed .exe has no console: wait for it, then show the log it
# wrote its output to (meanap.frozen.log_file).
run: |
$p = Start-Process -FilePath "packaging\dist\MEA-NAP\MEA-NAP.exe" `
-ArgumentList "--smoke-test" -Wait -PassThru
Get-Content "$env:LOCALAPPDATA\MEA-NAP\logs\meanap.log"
exit $p.ExitCode

- name: Version
id: version
run: echo "version=$(tr -d '[:space:]' < version.txt)" >> "$GITHUB_OUTPUT"

- name: Package .dmg
if: runner.os == 'macOS'
env:
NAME: MEA-NAP-${{ steps.version.outputs.version }}-${{ matrix.platform }}
run: |
staging=packaging/build/dmg
rm -rf "$staging" && mkdir -p "$staging"
cp -R packaging/dist/MEA-NAP.app "$staging/"
ln -s /Applications "$staging/Applications" # drag-to-install
hdiutil create -volname "MEA-NAP" -srcfolder "$staging" -ov \
-format UDZO "packaging/dist/$NAME.dmg"
echo "ASSET=packaging/dist/$NAME.dmg" >> "$GITHUB_ENV"

- name: Package installer
if: runner.os == 'Windows'
shell: pwsh
run: |
if (-not (Get-Command iscc -ErrorAction SilentlyContinue)) {
choco install innosetup -y --no-progress
$env:Path += ";${env:ProgramFiles(x86)}\Inno Setup 6"
}
iscc /DAppVersion=${{ steps.version.outputs.version }} `
/DSourceDir="$PWD\packaging\dist\MEA-NAP" `
/DOutputDir="$PWD\packaging\dist" `
packaging\windows\meanap.iss
$asset = "packaging/dist/MEA-NAP-${{ steps.version.outputs.version }}-${{ matrix.platform }}-Setup.exe"
"ASSET=$asset" | Out-File -FilePath $env:GITHUB_ENV -Append -Encoding utf8

- uses: actions/upload-artifact@v4
with:
name: MEA-NAP-${{ matrix.platform }}
path: ${{ env.ASSET }}
if-no-files-found: error
retention-days: 14

- name: Attach to the release
if: github.event_name == 'release'
env:
GH_TOKEN: ${{ github.token }}
run: gh release upload "${{ github.event.release.tag_name }}" "$ASSET" --clobber
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -20,3 +20,7 @@ local/

# Sphinx autosummary-generated API stub pages (regenerated on every docs build)
docs/python/api/generated/

# Desktop app build output (packaging/build_app.py)
packaging/build/
packaging/dist/
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
> [!IMPORTANT]
> **MEA-NAP is now available in Python (beta).** It is much faster and introduces a lot of new features. The MATLAB version is still supported.
>
> [**Read the release note**](release_notes/python-beta.md) · [**Install**](docs/python/installation.md) · [**Quickstart**](docs/python/quickstart.md) · [**MATLAB vs Python**](docs/python/matlab-vs-python.md) · [**Send us feedback**](https://github.com/SAND-Lab/MEA-NAP/discussions)
> [**Read the release note**](release_notes/python-beta.md) · [**Download the app**](docs/python/installation.md#the-desktop-app-easiest) (Windows, Mac) · [**Install from source**](docs/python/installation.md#from-the-source-code) · [**Quickstart**](docs/python/quickstart.md) · [**MATLAB vs Python**](docs/python/matlab-vs-python.md) · [**Send us feedback**](https://github.com/SAND-Lab/MEA-NAP/discussions)

![Visitors](https://api.visitorbadge.io/api/visitors?path=https%3A%2F%2Fgithub.com%2FSAND-Lab%2FAnalysisPipeline&label=Visitors&countColor=%23263759)
[![Documentation Status](https://readthedocs.org/projects/analysis-pipeline/badge/?version=latest)](https://analysis-pipeline.readthedocs.io/en/latest/?badge=latest)
Expand Down
133 changes: 129 additions & 4 deletions docs/python/installation.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,130 @@
# Installation

There are two ways to get the Python version of MEA-NAP:

- **The desktop app** — download one file, double-click, done. No Python, no
terminal. For Windows and Macs (Apple silicon or Intel).
- **From the source code** — a clone of the repository and a few commands in a
terminal. For Linux, for the newest code between releases, and for anyone
who wants to change MEA-NAP itself.

Both run the same pipeline with the same pinned libraries, so they give the
same results.

## The desktop app (easiest)

Download the file for your computer from the
[**latest release**](https://github.com/SAND-Lab/MEA-NAP/releases/latest)
on GitHub, under **Assets**:

| Computer | File |
|---|---|
| Windows 10 or 11 (64-bit) | `MEA-NAP-<version>-Windows-x64-Setup.exe` |
| Mac with Apple silicon (M1, M2, M3, M4 …) | `MEA-NAP-<version>-macOS-arm64.dmg` |
| Mac with an Intel processor | `MEA-NAP-<version>-macOS-Intel.dmg` |

Not sure which Mac you have? Apple menu → **About This Mac**: "Chip: Apple M…"
means Apple silicon; "Processor: … Intel …" means an Intel Mac. Either needs
macOS 12 (Monterey) or later.

The download is about 150 MB (about 700 MB once installed): it carries its own Python
and every library MEA-NAP uses, including OASIS for CAT-NAP and the spike
sorters.

**If your browser warns about the download.** Chrome in particular may say
the file is *suspicious*, *dangerous* or *not commonly downloaded*, and hold it
back. It says this about any program that isn't signed with a paid
certificate and that few people have downloaded yet — and each new release
starts that count again. To keep the file in Chrome:

1. Open the downloads list: the download icon at the top right, or
**Ctrl+J** (Windows) / **⌘+Shift+J** (Mac).
2. Find the MEA-NAP file and choose **Keep** (or **Download suspicious
file**).
3. Confirm with **Keep anyway** (or **Download anyway**).

In Edge, it's the **…** beside the download → **Keep** → **Show more** →
**Keep anyway**. Only download MEA-NAP from the
[GitHub releases page](https://github.com/SAND-Lab/MEA-NAP/releases) — that is
what makes it safe to click through.

:::::{tab-set}

::::{tab-item} Windows

1. Double-click `MEA-NAP-…-Setup.exe`.
2. Windows may show **"Windows protected your PC"**. This appears for any
app that is not yet signed with a paid certificate. Click
**More info**, then **Run anyway**.
3. Click through the installer. It installs for your user only and doesn't
need administrator rights. Tick **Create a desktop shortcut** if you want one.
4. Start MEA-NAP from the Start menu (or the desktop shortcut).

To update, download the newer installer and run it: it replaces the old
version and keeps your settings. To uninstall: **Settings → Apps → Installed
apps → MEA-NAP → Uninstall**.

::::

::::{tab-item} macOS

1. Double-click the `MEA-NAP-…-macOS-….dmg` you downloaded, then drag **MEA-NAP** onto
the **Applications** folder in the window that opens.
2. Open MEA-NAP from Applications (or Launchpad). The first time, macOS says
it **"could not verify MEA-NAP is free of malware"** (older macOS: "cannot
be opened because the developer cannot be verified"). This appears for any
app that is not yet notarised by Apple. Click **Done**.
3. Open **System Settings → Privacy & Security**, scroll down to the line
about MEA-NAP, and click **Open Anyway**. Enter your password if asked,
then **Open**.

On macOS 14 (Sonoma) or earlier you can instead right-click (or
Control-click) MEA-NAP in Applications and choose **Open**, then **Open**
again.
4. From then on MEA-NAP opens normally.

To update, download the newer `.dmg` and drag MEA-NAP into Applications
again, replacing the old one. To uninstall, drag MEA-NAP from Applications to
the Bin.

If macOS instead says MEA-NAP **"is damaged and can't be opened"**, the
download was quarantined in a way step 3 doesn't clear. Open **Terminal**
(Applications → Utilities), paste this line, press Return, and open MEA-NAP
again:

```bash
xattr -dr com.apple.quarantine /Applications/MEA-NAP.app
```

::::

:::::

**The first analysis is slower than later ones.** Parts of step 4 are compiled
for your processor the first time they run, and the result is kept for next
time. On Windows, antivirus software may also scan the app the first time it
starts, so the window can take a minute to appear.

**Updates.** The version number in the toolbar, beside the **Mode**
selector, shows **⬆ update** when a newer release is out. Click it, then
**Download**.

**What the app doesn't include.** Running MATLAB or switching between
MEA-NAP versions from inside the GUI, CAT-NAP's cell tracking across days
(which needs PyTorch), and Kilosort. Use a source install for those.

**Something went wrong?** The app writes everything it would have printed to
a log file. Please attach it when you
[report a problem](https://github.com/SAND-Lab/MEA-NAP/issues):

- Windows: `%LOCALAPPDATA%\MEA-NAP\logs\meanap.log` (paste that into the
File Explorer address bar)
- macOS: `~/Library/Logs/MEA-NAP/meanap.log` (Finder → Go → Go to Folder…)

The log of the run before is kept beside it as `meanap.previous.log`.

## From the source code

The Python port requires **Python 3.11 or later** (the repository itself is
pinned to 3.13 via `.python-version`) and is installed straight from a clone
of the MEA-NAP repository — it isn't on PyPI yet.
Expand All @@ -14,7 +139,7 @@ That `cd` matters: every install command on this page is run from inside the
its subfolders. If you downloaded a ZIP from GitHub instead of cloning, the
folder is called `MEA-NAP-main` — use `cd MEA-NAP-main`.

## macOS prerequisites
### macOS prerequisites

On macOS some dependencies have no pre-built wheel for your Python version or
chip and are compiled from source during the install, which needs a C/C++
Expand Down Expand Up @@ -126,7 +251,7 @@ Both errors mean the same thing: the command ran in the wrong directory. Run
inside `python/`, run `cd ..` and try again.
:::

## Optional: OASIS deconvolution (CAT-NAP)
### Optional: OASIS deconvolution (CAT-NAP)

The calcium-imaging denoising pipeline ([CAT-NAP](catnap.md)) deconvolves each
trace with [OASIS](https://github.com/j-friedrich/OASIS). It is optional, and
Expand Down Expand Up @@ -156,7 +281,7 @@ Note that denoising output is cached as `Fdenoised.npy` and carries no record
of which method produced it, so installing OASIS after a run will not
recompute anything — tick **Redo denoising** to force it.

## Optional: spike sorting
### Optional: spike sorting

Step 1 can [sort spikes into units](spike-sorting.md) instead of detecting
them per electrode, so that each neuron — not each electrode — is a node of
Expand All @@ -180,7 +305,7 @@ uv run python -c "from meanap.pipeline.spike_sorting import installed_sorters; p
should list `tridesclous2` and `mountainsort5`. Kilosort4 users can add
`kilosort` (and a CUDA PyTorch) themselves; it then appears in the same list.

## Verifying your install
### Verifying your install

The fastest sanity check is the GUI's own **🧪 Test pipeline** button — it
downloads a small bundled example dataset and runs the full 4-step pipeline
Expand Down
Loading
Loading