Skip to content

Commit

Permalink
Merge pull request #61 from thewtex/agave-renderer
Browse files Browse the repository at this point in the history
Agave renderer
  • Loading branch information
thewtex authored Sep 5, 2023
2 parents 88c74ff + 30fb5d6 commit 279f371
Show file tree
Hide file tree
Showing 7 changed files with 313 additions and 0 deletions.
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
"dev": "pnpm -r --parallel run dev",
"build": "pnpm -r build",
"lint": "pnpm prettier --check . && pnpm eslint .",
"lint:fix": "pnpm prettier --write . && pnpm eslint --fix .",
"test": "pnpm test:downloadData && pnpm cy:component",
"test:ci": "pnpm test:downloadData && pnpm cy:component:ci",
"test:downloadData": "node test/downloadData.mjs",
Expand Down
21 changes: 21 additions & 0 deletions packages/agave-renderer/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# ITK Viewer Agave Renderer

[![PyPI - Version](https://img.shields.io/pypi/v/itk-viewer-agave-renderer.svg)](https://pypi.org/project/itk-viewer-agave-renderer)
[![PyPI - Python Version](https://img.shields.io/pypi/pyversions/itk-viewer-agave-renderer.svg)](https://pypi.org/project/itk-viewer-agave-renderer)

---

**Table of Contents**

- [Installation](#installation)
- [License](#license)

## Installation

```console
pip install itk-viewer-agave-renderer
```

## License

`itk-viewer-agave-renderer` is distributed under the terms of the [Apache-2.0](https://spdx.org/licenses/Apache-2.0.html) license.
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# SPDX-FileCopyrightText: 2023-present NumFOCUS
#
# SPDX-License-Identifier: Apache-2.0
__version__ = "0.0.1"
5 changes: 5 additions & 0 deletions packages/agave-renderer/itk_viewer_agave_renderer/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# SPDX-FileCopyrightText: 2023-present NumFOCUS
#
# SPDX-License-Identifier: Apache-2.0

from .renderer import Renderer
116 changes: 116 additions & 0 deletions packages/agave-renderer/itk_viewer_agave_renderer/renderer.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
# SPDX-FileCopyrightText: 2023-present NumFOCUS
#
# SPDX-License-Identifier: Apache-2.0

import time
from enum import Enum, auto
import functools

import agave_pyclient as agave
from imjoy_rpc.hypha import connect_to_server
from itkwasm_htj2k import encode
from itkwasm import Image, ImageType, PixelTypes, IntTypes
import numpy as np
from PIL import Image as PILImage

class AgaveRendererMemoryRedraw(agave.AgaveRenderer):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)

def memory_redraw(self):
self.cb.add_command("REDRAW")
buf = self.cb.make_buffer()
# TODO ENSURE CONNECTED
self.ws.send(buf, True)
# and then WAIT for render to be completed
binarydata = self.ws.wait_for_image()
# ready for next frame
self.cb = agave.commandbuffer.CommandBuffer()
img = PILImage.open(binarydata)
rgba = img.convert('RGBA').tobytes()
return rgba

# how to handle unknown events
class UnknownEventAction(Enum):
ERROR = auto()
WARNING = auto()
IGNORE = auto()

class Renderer():
def __init__(self, width=500, height=400, unknown_event_action=UnknownEventAction.WARNING):
self.width = width
self.height = height
self.unknown_event_action = unknown_event_action

async def setup(self):
# Note: the agave websocket server needs to be running
self.agave = AgaveRendererMemoryRedraw()
self.agave.set_resolution(self.width, self.height)

async def render(self):
r = self.agave
start_time = time.time()
rgba = r.memory_redraw()
image_type = ImageType(dimension=2, componentType=IntTypes.UInt8, pixelType=PixelTypes.RGBA, components=4)
image = Image(image_type)
image.size = [self.width, self.height]
image.data = np.frombuffer(rgba, dtype=np.uint8)
# lossless
# rgba_encoded = encode(image)
rgba_encoded = encode(image, not_reversible=True, quantization_step=0.02)
elapsed = time.time() - start_time
return { "frame": rgba_encoded, "renderTime": elapsed }

def update_renderer(self, events):
r = self.agave

for [event_type, payload] in events:
match event_type:
case 'density':
r.density(payload)
case 'cameraPose':
eye = payload['eye']
r.eye(eye[0], eye[1], eye[2])
up = payload['up']
r.up(up[0], up[1], up[2])
target = payload['target']
r.target(target[0], target[1], target[2])
case 'renderIterations':
r.render_iterations(payload)
case _:
self.handle_unknown_event(event_type)

def handle_unknown_event(self, event_type):
match self.unknown_event_action:
case UnknownEventAction.ERROR:
raise Exception(f"Unknown event type: {event_type}")
case UnknownEventAction.WARNING:
print(f"Unknown event type: {event_type}", flush=True)
case UnknownEventAction.IGNORE:
pass

async def connect(self, hypha_server_url, load_image_into_agave_fn, visibility="public", identifier="agave-renderer"):
server = await connect_to_server(
{
"name": "agave-renderer-client",
"server_url": hypha_server_url
}
)

await server.register_service({
"name": "Agave Renderer",
"id": identifier,
"config": {
"visibility": visibility,
"require_context": False,
"run_in_executor": False,
},

"setup": self.setup,

"loadImage": functools.partial(load_image_into_agave_fn, self),

"render": self.render,
"updateRenderer": self.update_renderer,
})
print("Renderer is ready to receive request!", server.config, flush=True)
163 changes: 163 additions & 0 deletions packages/agave-renderer/pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"

[project]
name = "itk-viewer-agave-renderer"
dynamic = ["version"]
description = ''
readme = "README.md"
requires-python = ">=3.7"
license = "Apache-2.0"
keywords = []
authors = [
{ name = "Matt McCormick", email = "matt.mccormick@kitware.com" },
]
classifiers = [
"Development Status :: 4 - Beta",
"Programming Language :: Python",
"Programming Language :: Python :: 3.7",
"Programming Language :: Python :: 3.8",
"Programming Language :: Python :: 3.9",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: Implementation :: CPython",
"Programming Language :: Python :: Implementation :: PyPy",
]
dependencies = [
"agave-pyclient",
"hypha",
"itkwasm-htj2k",
"numpy",
"pillow",
]

[project.urls]
Documentation = "https://github.com/InsightSoftwareConsortium/itk-viewer/tree/main/packages/agave-renderer#readme"
Issues = "https://github.com/InsightSoftwareConsortium/itk-viewer/issues"
Source = "https://github.com/InsightSoftwareConsortium/itk-viewer"

[tool.hatch.version]
path = "itk_viewer_agave_renderer/__about__.py"

[tool.hatch.envs.default]
dependencies = [
"coverage[toml]>=6.5",
"pytest",
]
[tool.hatch.envs.default.scripts]
test = "pytest {args:tests}"
test-cov = "coverage run -m pytest {args:tests}"
cov-report = [
"- coverage combine",
"coverage report",
]
cov = [
"test-cov",
"cov-report",
]

[[tool.hatch.envs.all.matrix]]
python = ["3.7", "3.8", "3.9", "3.10", "3.11"]

[tool.hatch.envs.lint]
detached = true
dependencies = [
"black>=23.1.0",
"mypy>=1.0.0",
"ruff>=0.0.243",
]
[tool.hatch.envs.lint.scripts]
typing = "mypy --install-types --non-interactive {args:itk_viewer_agave_renderer tests}"
style = [
"ruff {args:.}",
"black --check --diff {args:.}",
]
fmt = [
"black {args:.}",
"ruff --fix {args:.}",
"style",
]
all = [
"style",
"typing",
]

[tool.black]
target-version = ["py37"]
line-length = 120
skip-string-normalization = true

[tool.ruff]
target-version = "py37"
line-length = 120
select = [
"A",
"ARG",
"B",
"C",
"DTZ",
"E",
"EM",
"F",
"FBT",
"I",
"ICN",
"ISC",
"N",
"PLC",
"PLE",
"PLR",
"PLW",
"Q",
"RUF",
"S",
"T",
"TID",
"UP",
"W",
"YTT",
]
ignore = [
# Allow non-abstract empty methods in abstract base classes
"B027",
# Allow boolean positional values in function calls, like `dict.get(... True)`
"FBT003",
# Ignore checks for possible passwords
"S105", "S106", "S107",
# Ignore complexity
"C901", "PLR0911", "PLR0912", "PLR0913", "PLR0915",
]
unfixable = [
# Don't touch unused imports
"F401",
]

[tool.ruff.isort]
known-first-party = ["itk_viewer_agave_renderer"]

[tool.ruff.flake8-tidy-imports]
ban-relative-imports = "all"

[tool.ruff.per-file-ignores]
# Tests can use magic values, assertions, and relative imports
"tests/**/*" = ["PLR2004", "S101", "TID252"]

[tool.coverage.run]
source_pkgs = ["itk_viewer_agave_renderer", "tests"]
branch = true
parallel = true
omit = [
"itk_viewer_agave_renderer/__about__.py",
]

[tool.coverage.paths]
itk_viewer_agave_renderer = ["itk_viewer_agave_renderer", "*/itk-viewer-agave-renderer/itk_viewer_agave_renderer"]
tests = ["tests", "*/itk-viewer-agave-renderer/tests"]

[tool.coverage.report]
exclude_lines = [
"no cov",
"if __name__ == .__main__.:",
"if TYPE_CHECKING:",
]
3 changes: 3 additions & 0 deletions packages/agave-renderer/tests/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# SPDX-FileCopyrightText: 2023-present NumFOCUS
#
# SPDX-License-Identifier: Apache 2.0

0 comments on commit 279f371

Please sign in to comment.