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
20 changes: 20 additions & 0 deletions .github/workflows/integration-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -653,6 +653,26 @@ jobs:
if ($shortcut.WorkingDirectory -ne $installRoot) {
throw "Desktop shortcut working directory is not the install root."
}
$environmentPython = Join-Path `
$environment[0].FullName `
"Scripts\python.exe"
$expectedIconPath = (
& $environmentPython `
-I -m openhcs.resources.brand windows_icon
).Trim()
if ($LASTEXITCODE -ne 0) {
throw "Installed package could not resolve its Windows brand icon."
}
if (-not (Test-Path -LiteralPath $expectedIconPath -PathType Leaf)) {
throw "Installed Windows brand icon is missing."
}
$expectedIconLocation = "$expectedIconPath,0"
if ($shortcut.IconLocation -ne $expectedIconLocation) {
throw (
"Desktop shortcut icon '$($shortcut.IconLocation)' does not " +
"match '$expectedIconLocation'."
)
}
$guiSubsystem = python -c "import struct,sys; from pathlib import Path; data=Path(sys.argv[1]).read_bytes(); pe=struct.unpack_from('<I',data,0x3c)[0]; assert data[pe:pe+4]==b'PE\0\0'; print(struct.unpack_from('<H',data,pe+24+68)[0])" `
$shortcut.TargetPath
if ($guiSubsystem.Trim() -ne "2") {
Expand Down
2 changes: 2 additions & 0 deletions .github/workflows/website-pages.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ on:
pull_request:
paths:
- "website/**"
- "openhcs/resources/assets/openhcs-mark.svg"
- "docs/source/_static/ui.png"
- "scripts/build_website.py"
- "tests/unit/test_build_website.py"
Expand All @@ -13,6 +14,7 @@ on:
branches: [main]
paths:
- "website/**"
- "openhcs/resources/assets/openhcs-mark.svg"
- "docs/source/_static/ui.png"
- "scripts/build_website.py"
- "tests/unit/test_build_website.py"
Expand Down
11 changes: 3 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,9 @@

<div align="center">

<pre>
___ _ _ ___ _____
/ _ \ _ __ ___ _ ___ | | | |/ __\/ ___/
| | | | '_ \/ _ \| '_ \| |_| | | \___ \
| |_| | |_||| __/| | | || _ | |__ __/ |
\___/| .__/\___||_| |_||_| |_|\___/\____/
|_| High-Content Screening
</pre>
<img src="openhcs/resources/assets/openhcs-mark.svg" width="140" alt="OpenHCS logo">

<h1>OpenHCS</h1>

**Bioimage analysis platform for high-content screening**\
**Compile-time validation · Bidirectional GUI↔Code · Multi-GPU · LLM pipeline generation · Extensible function registry**
Expand Down
2 changes: 1 addition & 1 deletion openhcs/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@

from openhcs._source_dependencies import ensure_source_checkout_external_paths

__version__ = "0.7.1"
__version__ = "0.7.2"

# Configure polystore defaults for OpenHCS integration
os.environ.setdefault("POLYSTORE_METADATA_FILENAME", "openhcs_metadata.json")
Expand Down Expand Up @@ -49,7 +49,7 @@
configured_level_name = os.environ.get("OPENHCS_LOG_LEVEL", "INFO").upper()
configured_level = getattr(logging, configured_level_name, None)
if not isinstance(configured_level, int):
raise ValueError(f"Unknown OPENHCS_LOG_LEVEL: {configured_level_name!r}")

Check failure on line 52 in openhcs/__init__.py

View workflow job for this annotation

GitHub Actions / code-quality

ruff (TRY004)

openhcs/__init__.py:52:9: TRY004 Prefer `TypeError` exception for invalid type

# Only configure if no handlers exist and level is too high
if not root_logger.handlers and root_logger.level > configured_level:
Expand Down Expand Up @@ -77,17 +77,17 @@
# PhysicalPath,
#)
#
__all__ = [
# Core functions
"initialize",
"create_config",
"run_pipeline",
"stitch_images",

# Key types
"PipelineConfig",
"BackendConfig",
"MISTConfig",
"VirtualPath",
"PhysicalPath",
]

Check failure on line 93 in openhcs/__init__.py

View workflow job for this annotation

GitHub Actions / code-quality

ruff (RUF022)

openhcs/__init__.py:80:11: RUF022 `__all__` is not sorted help: Apply an isort-style sorting to `__all__`
4 changes: 4 additions & 0 deletions openhcs/gui_startup.py
Original file line number Diff line number Diff line change
Expand Up @@ -309,6 +309,7 @@ def _run_startup_window_child() -> int:
QPushButton,
QVBoxLayout,
)
from openhcs.pyqt_gui.branding import openhcs_application_icon
from pyqt_reactive.theming import ColorScheme, ThemeManager

class _EventBridge(QObject):
Expand Down Expand Up @@ -435,10 +436,13 @@ def _append_detail(self, message: str) -> None:

app = QApplication([sys.argv[0]])
app.setApplicationName("OpenHCS Startup")
application_icon = openhcs_application_icon()
app.setWindowIcon(application_icon)
color_scheme = ColorScheme()
theme_manager = ThemeManager(color_scheme)
theme_manager.apply_color_scheme(color_scheme)
window = _StartupWindow(color_scheme, theme_manager.style_generator)
window.setWindowIcon(application_icon)
bridge = _EventBridge()
bridge.event_received.connect(window.apply_event)

Expand Down
8 changes: 2 additions & 6 deletions openhcs/pyqt_gui/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,12 @@
import sys
import logging
from typing import Callable, Optional, TYPE_CHECKING
from pathlib import Path

from PyQt6.QtWidgets import QApplication, QMessageBox
from PyQt6.QtCore import qInstallMessageHandler
from PyQt6.QtGui import QIcon

from openhcs.core.config import GlobalPipelineConfig
from openhcs.pyqt_gui.branding import openhcs_application_icon
from polystore.base import storage_registry
from polystore.filemanager import FileManager
from objectstate import spawn_thread_with_context
Expand Down Expand Up @@ -168,10 +167,7 @@ def init_function_registry_background():

register_reactor_providers(lambda: self.runtime_context.ui_config.zmq)

# Set application icon (if available)
icon_path = Path(__file__).parent / "resources" / "openhcs_icon.png"
if icon_path.exists():
self.setWindowIcon(QIcon(str(icon_path)))
self.setWindowIcon(openhcs_application_icon())

# Setup exception handling
sys.excepthook = self.handle_exception
Expand Down
16 changes: 16 additions & 0 deletions openhcs/pyqt_gui/branding.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
"""Qt projection of the package-owned OpenHCS brand mark."""

from __future__ import annotations

from PyQt6.QtGui import QIcon, QPixmap

from openhcs.resources.brand import BrandAsset, brand_asset_bytes


def openhcs_application_icon() -> QIcon:
"""Build the OpenHCS application icon from its packaged raster asset."""

pixmap = QPixmap()
if not pixmap.loadFromData(brand_asset_bytes(BrandAsset.RASTER)):
raise RuntimeError("Packaged OpenHCS application icon could not be decoded.")
return QIcon(pixmap)
1 change: 1 addition & 0 deletions openhcs/resources/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
"""Packaged, implementation-independent OpenHCS resources."""
11 changes: 11 additions & 0 deletions openhcs/resources/assets/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# OpenHCS brand assets

`openhcs-mark.svg` is the canonical OpenHCS project mark. It preserves the
geometry and colors of the OpenHCSDev organization avatar.

`openhcs-mark.png`, `openhcs.ico`, and `openhcs.icns` are mechanically rendered
platform encodings of that SVG. Regenerate them with:

```bash
scripts/render_brand_assets.sh
```
Binary file added openhcs/resources/assets/openhcs-mark.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
8 changes: 8 additions & 0 deletions openhcs/resources/assets/openhcs-mark.svg
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 openhcs/resources/assets/openhcs.icns
Binary file not shown.
Binary file added openhcs/resources/assets/openhcs.ico
Binary file not shown.
45 changes: 45 additions & 0 deletions openhcs/resources/brand.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
"""Authoritative access to the packaged OpenHCS brand assets."""

from __future__ import annotations

import argparse
from enum import Enum
from importlib.resources import files
from pathlib import Path


class BrandAsset(str, Enum):
"""Closed set of mechanically equivalent OpenHCS mark encodings."""

SCALABLE = "openhcs-mark.svg"
RASTER = "openhcs-mark.png"
WINDOWS_ICON = "openhcs.ico"
MACOS_ICON = "openhcs.icns"


def brand_asset_path(asset: BrandAsset) -> Path:
"""Return the filesystem path for one installed OpenHCS brand asset."""

return Path(str(files("openhcs.resources") / "assets" / asset.value))


def brand_asset_bytes(asset: BrandAsset) -> bytes:
"""Read one packaged OpenHCS brand asset."""

return brand_asset_path(asset).read_bytes()


def main() -> int:
"""Print an installed brand asset path for native launcher integration."""

parser = argparse.ArgumentParser(description=__doc__)
choices = tuple(asset.name.lower() for asset in BrandAsset)
parser.add_argument("asset", choices=choices)
arguments = parser.parse_args()
asset = BrandAsset[arguments.asset.upper()]
print(brand_asset_path(asset))
return 0


if __name__ == "__main__":
raise SystemExit(main())
2 changes: 1 addition & 1 deletion packaging/codex/openhcs/.codex-plugin/plugin.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "openhcs",
"version": "0.7.1",
"version": "0.7.2",
"description": "Inspect, author, validate, and run OpenHCS microscopy workflows through the local OpenHCS MCP server.",
"author": {
"name": "OpenHCSDev",
Expand Down
2 changes: 1 addition & 1 deletion packaging/codex/openhcs/.mcp.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
"command": "uvx",
"args": [
"--from",
"openhcs[gui,mcp,viz]==0.7.1",
"openhcs[gui,mcp,viz]==0.7.2",
"openhcs-mcp"
]
}
Expand Down
8 changes: 8 additions & 0 deletions packaging/installers/macos/build-installer.sh
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,16 @@ set -euo pipefail
script_directory=$(cd "$(dirname "$0")" && pwd)
contract_path=${1:-"$script_directory/../installer_contract.json"}
output_path=${2:-"$script_directory/dist/OpenHCS Installer.app"}
brand_icon_path="$script_directory/../../../openhcs/resources/assets/openhcs.icns"

if [[ ! -f "$contract_path" ]]; then
printf 'Installer contract not found: %s\n' "$contract_path" >&2
exit 2
fi
if [[ ! -f "$brand_icon_path" ]]; then
printf 'OpenHCS brand icon not found: %s\n' "$brand_icon_path" >&2
exit 2
fi
if ! command -v xcrun >/dev/null 2>&1; then
printf 'Xcode command-line tools are required to build the macOS installer app.\n' >&2
exit 2
Expand Down Expand Up @@ -57,6 +62,7 @@ done
<key>CFBundleDisplayName</key><string>OpenHCS Installer</string>
<key>CFBundleExecutable</key><string>OpenHCSInstaller</string>
<key>CFBundleIdentifier</key><string>org.openhcs.installer</string>
<key>CFBundleIconFile</key><string>OpenHCS</string>
<key>CFBundleName</key><string>OpenHCS Installer</string>
<key>CFBundlePackageType</key><string>APPL</string>
<key>LSMinimumSystemVersion</key><string>12.0</string>
Expand All @@ -69,6 +75,8 @@ PLIST
"$temporary_app/Contents/Resources/install-openhcs.sh"
/bin/cp "$contract_path" \
"$temporary_app/Contents/Resources/installer_contract.json"
/bin/cp "$brand_icon_path" \
"$temporary_app/Contents/Resources/OpenHCS.icns"
/bin/chmod 755 "$temporary_app/Contents/MacOS/OpenHCSInstaller"
/bin/chmod 755 "$temporary_app/Contents/Resources/install-openhcs.sh"
/usr/bin/plutil -lint "$temporary_app/Contents/Info.plist"
Expand Down
41 changes: 30 additions & 11 deletions packaging/installers/macos/install-openhcs.sh
Original file line number Diff line number Diff line change
Expand Up @@ -114,9 +114,10 @@ temporary_uv_installer=$(
/usr/bin/mktemp "${TMPDIR:-/tmp}/openhcs-uv-installer.XXXXXX"
)
new_launcher_app="$applications_root/.$product_name.app.new.$$"
launcher_backup="$applications_root/.$product_name.app.backup.$$"
agent_registration_report="$application_root/agent-registration.json"
agent_registration_candidate="$application_root/.agent-registration.new.$$"
launcher_created=false
launcher_published=false
install_succeeded=false
active_child_pid=

Expand All @@ -143,13 +144,17 @@ cleanup() {
install_succeeded=true
fi
if [[ "$install_succeeded" == true ]]; then
/bin/rm -rf "$launcher_backup"
write_installer_state launcher-path "$launcher_app"
fi
if [[ "$install_succeeded" != true ]]; then
/bin/rm -rf "$new_environment"
if [[ "$launcher_created" == true ]]; then
if [[ "$launcher_published" == true ]]; then
/bin/rm -rf "$launcher_app"
fi
if [[ -d "$launcher_backup" ]]; then
/bin/mv "$launcher_backup" "$launcher_app"
fi
fi
}

Expand Down Expand Up @@ -281,32 +286,46 @@ if [[ -e "$launcher_app" && ! -d "$launcher_app" ]]; then
fi

report_progress 'Preparing Applications and Desktop shortcuts…'
if [[ ! -e "$launcher_app" ]]; then
/bin/rm -rf "$new_launcher_app"
/bin/mkdir -p "$new_launcher_app/Contents/MacOS"
/bin/cat >"$new_launcher_app/Contents/Info.plist" <<PLIST
/bin/rm -rf "$new_launcher_app" "$launcher_backup"
/bin/mkdir -p \
"$new_launcher_app/Contents/MacOS" \
"$new_launcher_app/Contents/Resources"
brand_icon_path=$(
"$environment_python" -m openhcs.resources.brand macos_icon
)
if [[ ! -f "$brand_icon_path" ]]; then
printf 'Installed OpenHCS brand icon is unavailable: %s\n' \
"$brand_icon_path" >&2
exit 1
fi
/bin/cp "$brand_icon_path" \
"$new_launcher_app/Contents/Resources/OpenHCS.icns"
/bin/cat >"$new_launcher_app/Contents/Info.plist" <<PLIST
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDisplayName</key><string>$product_name</string>
<key>CFBundleExecutable</key><string>launch-openhcs</string>
<key>CFBundleIdentifier</key><string>org.openhcs.desktop</string>
<key>CFBundleIconFile</key><string>OpenHCS</string>
<key>CFBundleName</key><string>$product_name</string>
<key>CFBundlePackageType</key><string>APPL</string>
<key>CFBundleVersion</key><string>1</string>
</dict>
</plist>
PLIST
/bin/cat >"$new_launcher_app/Contents/MacOS/launch-openhcs" <<LAUNCH_APP
/bin/cat >"$new_launcher_app/Contents/MacOS/launch-openhcs" <<LAUNCH_APP
#!/bin/bash
exec "\$HOME/Library/Application Support/$product_name/current/launch-openhcs.sh" "\$@"
LAUNCH_APP
/bin/chmod 755 "$new_launcher_app/Contents/MacOS/launch-openhcs"
/usr/bin/plutil -lint "$new_launcher_app/Contents/Info.plist"
/bin/mv "$new_launcher_app" "$launcher_app"
launcher_created=true
/bin/chmod 755 "$new_launcher_app/Contents/MacOS/launch-openhcs"
/usr/bin/plutil -lint "$new_launcher_app/Contents/Info.plist"
if [[ -d "$launcher_app" ]]; then
/bin/mv "$launcher_app" "$launcher_backup"
fi
/bin/mv "$new_launcher_app" "$launcher_app"
launcher_published=true

/bin/ln -s "$new_environment" "$current_candidate"
if ! /bin/mv -fh "$current_candidate" "$current_environment"; then
Expand Down
18 changes: 18 additions & 0 deletions packaging/installers/windows/Build-InstallerLauncher.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,21 @@ $resolvedContract = [IO.Path]::GetFullPath($ContractPath)
if (-not (Test-Path -LiteralPath $resolvedContract -PathType Leaf)) {
throw "Rendered installer contract not found: $resolvedContract"
}
$brandIconPath = [IO.Path]::GetFullPath(
[IO.Path]::Combine(
$PSScriptRoot,
"..",
"..",
"..",
"openhcs",
"resources",
"assets",
"openhcs.ico"
)
)
if (-not (Test-Path -LiteralPath $brandIconPath -PathType Leaf)) {
throw "OpenHCS brand icon not found: $brandIconPath"
}
$temporaryRoot = [IO.Path]::Combine(
[IO.Path]::GetTempPath(),
"openhcs-installer-launcher-$([Guid]::NewGuid().ToString('N'))"
Expand All @@ -37,6 +52,9 @@ try {
Copy-Item -LiteralPath $resolvedContract -Destination (
[IO.Path]::Combine($sourceRoot, "installer_contract.json")
)
Copy-Item -LiteralPath $brandIconPath -Destination (
[IO.Path]::Combine($windowsSourceRoot, "OpenHCS.ico")
)
$projectPath = [IO.Path]::Combine(
$windowsSourceRoot,
"InstallerLauncher.csproj"
Expand Down
Loading
Loading