Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

286 launch gui #287

Merged
merged 17 commits into from
Jan 26, 2024
34 changes: 34 additions & 0 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
name: Build

on:
pull_request:
merge_group:

jobs:
build:
name: Build
runs-on: windows-latest

steps:
- name: 🛎 Checkout
uses: actions/checkout@v4
with:
ref: ${{ github.head_ref }}

- name: 🐍 Setup Python
uses: actions/setup-python@v4
with:
python-version: '3.12'
cache: 'pip'

- name: 📦 Install Hatch
run: pip install hatch

- name: 🗃️ Cache Build Artifacts
uses: actions/cache@v4
with:
path: build
key: ${{ runner.os }}-build

- name: 🔨 Build
run: hatch -e exe run build
89 changes: 48 additions & 41 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# Electrophysiology Manipulator Link

[![PyPI version](https://badge.fury.io/py/ephys-link.svg)](https://badge.fury.io/py/ephys-link)
[![Build](https://github.com/VirtualBrainLab/ephys-link/actions/workflows/build.yml/badge.svg)](https://github.com/VirtualBrainLab/ephys-link/actions/workflows/build.yml)
[![CodeQL](https://github.com/VirtualBrainLab/ephys-link/actions/workflows/codeql-analysis.yml/badge.svg)](https://github.com/VirtualBrainLab/ephys-link/actions/workflows/codeql-analysis.yml)
[![Dependency Review](https://github.com/VirtualBrainLab/ephys-link/actions/workflows/dependency-review.yml/badge.svg)](https://github.com/VirtualBrainLab/ephys-link/actions/workflows/dependency-review.yml)
[![Hatch project](https://img.shields.io/badge/%F0%9F%A5%9A-Hatch-4051b5.svg)](https://github.com/pypa/hatch)
Expand Down Expand Up @@ -33,17 +34,12 @@ the [API reference](https://virtualbrainlab.org/api_reference_ephys_link.html).

## Prerequisites

1. [Python ≥ 3.8, < 3.13](https://www.python.org/downloads/release/python-3116/)
1. Python 3.12+ requires the latest version
of Microsoft Visual C++ (MSVC v143+ x86/64) and the Windows SDK (10/11) to
be installed. They can be acquired through
the [Visual Studio Build Tools Installer](https://visualstudio.microsoft.com/visual-cpp-build-tools/).
2. An **x86 Windows PC is required** to run the server.
3. For Sensapex devices, the controller unit must be connected via an ethernet
1. An **x86 Windows PC is required** to run the server.
2. For Sensapex devices, the controller unit must be connected via an ethernet
cable and powered. A USB-to-ethernet adapter is acceptable. For New Scale manipulators,
the controller unit must be connected via USB and be powered by a 6V power
supply.
4. To use the emergency stop feature, ensure an Arduino with
3. To use the emergency stop feature, ensure an Arduino with
the [StopSignal](https://github.com/VirtualBrainLab/StopSignal) sketch is
connected to the computer. Follow the instructions on that repo for how to
set up the Arduino.
Expand All @@ -52,59 +48,70 @@ the [API reference](https://virtualbrainlab.org/api_reference_ephys_link.html).
is currently designed to interface with local/desktop instances of Pinpoint. It
will not work with the web browser versions of Pinpoint at this time.

<div style="padding: 15px; border: 1px solid transparent; border-color: transparent; margin-bottom: 20px; border-radius: 4px; color: #31708f; background-color: #d9edf7; border-color: #bce8f1;">
<h3>Using a Python virtual environment is encouraged.</h3>
<p>Create a virtual environment by running <code>python -m venv ephys_link</code></p>
<p>Activate the environment by running <code>.\ephys_link\scripts\activate</code></p>
<p>A virtual environment helps to isolate installed packages from other packages on your computer and ensures a clean installation of Ephys Link</p>
</div>
## Install as Standalone Executable

## Install for use
1. Download the latest executable from
the [releases page](https://github.com/VirtualBrainLab/ephys-link/releases/latest).
2. Double-click the executable file to launch the configuration window.
1. Take note of the IP address and port. **Copy this information into Pinpoint to connect**.
3. Select the desired configuration and click "Launch Server".

Run the following command to install the server:
The configuration window will close and the server will launch. Your configurations will be saved for future use.

```bash
pip install ephys-link
```
To connect to the server from Pinpoint, provide the IP address and port. For example, if the server is running on the
same computer that Pinpoint is, use

Update the server like any other Python package:
- Server: `localhost`
- Port: `8081`

```bash
pip install --upgrade ephys-link
```
If the server is running on a different (local) computer, use the IP address of that computer as shown in the startup
window instead of `localhost`.

## Install for development
## Install for Development

1. Clone the repository.
2. Install [Hatch](https://hatch.pypa.io/latest/install/)
3. In a terminal, navigate to the repository's root directory and run
3. Install the latest Microsoft Visual C++ (MSVC v143+ x86/64) and the Windows SDK (10/11)
via [Visual Studio Build Tools Installer](https://visualstudio.microsoft.com/visual-cpp-build-tools/).
4. In a terminal, navigate to the repository's root directory and run

```bash
hatch shell
hatch shell
```

This will create a virtual environment and install the package in editable mode.
This will create a virtual environment, install Python 12 (if not found), and install the package in editable mode.

## Install as a Python package

# Usage
```bash
pip install ephys-link
```

Import the modules you need and launch the server.

Run the following commands in a terminal to start the server for the desired manipulator platform:
```python
from ephys_link.server import Server

| Manipulator Platform | Command |
|--------------------------------------|--------------------------------------|
| Sensapex uMp-4 | `ephys-link` |
| Sensapex uMp-3 | `ephys-link -t ump3` |
| New Scale | `ephys-link -t new_scale` |
| New Scale via Pathfinder HTTP server | `ephys-link -t new_scale_pathfinder` |
server = Server()
server.launch("sensapex", 8081)
```

There are a couple additional aliases for the Ephys Link executable: `ephys_link` and `el`.
# CLI Usage

By default, the server will broadcast with its local IP address on port 8081.
**Copy this information into Pinpoint to connect**.
Ephys Link can be launched from the command line directly. This is useful for computers or servers without graphical
user interfaces.

For example, if the server is running on the same computer that Pinpoint is, use
Run the following commands in a terminal to start the server for the desired manipulator platform without the startup
window:

- Server: `localhost`
- Port: `8081`
| Manipulator Platform | Command |
|--------------------------------------|---------------------------------------------|
| Sensapex uMp-4 | `ephys-link.exe -b` |
| Sensapex uMp-3 | `ephys-link.exe -b -t ump3` |
| New Scale | `ephys-link.exe -b -t new_scale` |
| New Scale via Pathfinder HTTP server | `ephys-link.exe -b -t new_scale_pathfinder` |

More options can be viewed by running `ephys-link.exe -h`.

# Documentation and More Information

Expand Down
Binary file added assets/icon.ico
Binary file not shown.
18 changes: 7 additions & 11 deletions ephys_link.spec
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
# -*- mode: python ; coding: utf-8 -*-

from ephys_link.__about__ import __version__ as version

a = Analysis(
['src\\ephys_link\\__main__.py'],
Expand All @@ -18,26 +19,21 @@ pyz = PYZ(a.pure)
exe = EXE(
pyz,
a.scripts,
a.binaries,
a.datas,
[],
exclude_binaries=True,
name='ephys_link',
name=f"ephys_link-v{version}-Windows-x86_64",
debug=False,
bootloader_ignore_signals=False,
strip=False,
upx=True,
upx_exclude=[],
runtime_tmpdir=None,
console=True,
disable_windowed_traceback=False,
argv_emulation=False,
target_arch=None,
codesign_identity=None,
entitlements_file=None,
)
coll = COLLECT(
exe,
a.binaries,
a.datas,
strip=False,
upx=True,
upx_exclude=[],
name='ephys_link',
icon='assets\\icon.ico',
)
1 change: 1 addition & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ classifiers = [
]
dependencies = [
"aiohttp==3.9.1",
"platformdirs==4.1.0",
"pyserial==3.5",
"python-socketio==5.11.0",
"pythonnet==3.0.3",
Expand Down
2 changes: 1 addition & 1 deletion src/ephys_link/__about__.py
Original file line number Diff line number Diff line change
@@ -1 +1 @@
__version__ = "1.1.1"
__version__ = "1.2.0"
107 changes: 25 additions & 82 deletions src/ephys_link/__main__.py
Original file line number Diff line number Diff line change
@@ -1,71 +1,20 @@
import argparse
import signal
import time
from threading import Event, Thread

import serial
import serial.tools.list_ports as ports
from argparse import ArgumentParser

from ephys_link import common as com
from ephys_link.__about__ import __version__ as version
from ephys_link.emergency_stop import EmergencyStop
from ephys_link.gui import GUI
from ephys_link.server import Server

# Setup Arduino serial port (emergency stop)
poll_rate = 0.05
kill_serial_event = Event()
poll_serial_thread: Thread

# Create Server
server = Server()


def poll_serial(kill_event: Event, serial_port: str) -> None:
"""Continuously poll serial port for data

:param kill_event: Event to stop polling
:type kill_event: Event
:param serial_port: The serial port to poll
:type serial_port: str
:return: None
"""
target_port = serial_port
if serial_port is None:
# Search for serial ports
for port, desc, _ in ports.comports():
if "Arduino" in desc or "USB Serial Device" in desc:
target_port = port
break
elif serial_port == "no-e-stop":
# Stop polling if no-e-stop is specified
return None

ser = serial.Serial(target_port, 9600, timeout=poll_rate)
while not kill_event.is_set():
if ser.in_waiting > 0:
ser.readline()
# Cause a break
com.dprint("[EMERGENCY STOP]\t\t Stopping all manipulators")
server.platform.stop()
ser.reset_input_buffer()
time.sleep(poll_rate)
print("Close poll")
ser.close()


def close_serial(_, __) -> None:
"""Close the serial connection"""
print("[INFO]\t\t Closing serial")
kill_serial_event.set()
poll_serial_thread.join()


# Setup argument parser
parser = argparse.ArgumentParser(
# Setup argument parser.
parser = ArgumentParser(
description="Electrophysiology Manipulator Link: a websocket interface for"
" manipulators in electrophysiology experiments",
prog="python -m ephys-link",
)
# parser.add_argument("-g", "--gui", dest="gui", action="store_true", help="Launches GUI")
parser.add_argument(
"-b", "--background", dest="background", action="store_true", help="Launches in headless mode (no GUI)"
)
parser.add_argument(
"-t",
"--type",
Expand Down Expand Up @@ -111,34 +60,28 @@ def close_serial(_, __) -> None:
def main() -> None:
"""Main function"""

# Parse arguments
# Parse arguments.
args = parser.parse_args()
com.DEBUG = args.debug

# Setup serial port
if args.serial != "no-e-stop":
# Register serial exit
signal.signal(signal.SIGTERM, close_serial)
signal.signal(signal.SIGINT, close_serial)
# Launch GUI if not background.
if not args.background:
gui = GUI()
gui.launch()
return None

# Otherwise, create Server from CLI.
server = Server()

# Start emergency stop system if serial is provided
global poll_serial_thread
poll_serial_thread = Thread(
target=poll_serial,
args=(
kill_serial_event,
args.serial,
),
daemon=True,
)
poll_serial_thread.start()
# Continue with CLI if not.
com.DEBUG = args.debug

# Register server exit
signal.signal(signal.SIGTERM, server.close_server)
signal.signal(signal.SIGINT, server.close_server)
# Setup serial port.
if args.serial != "no-e-stop":
e_stop = EmergencyStop(server, args.serial)
e_stop.watch()

# Launch with parsed arguments on main thread
server.launch_server(args.type, args.port, args.pathfinder_port)
# Launch with parsed arguments on main thread.
server.launch(args.type, args.port, args.pathfinder_port)


if __name__ == "__main__":
Expand Down
Loading
Loading