Skip to content
Draft
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
16 changes: 12 additions & 4 deletions agave_app/ViewerState.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
#include "renderlib/version.h"
#include "renderlib/version.hpp"

#include <QApplication>
#include <QFile>
#include <QFileInfo>

Expand Down Expand Up @@ -89,18 +90,24 @@ stateToPythonScript(const Serialize::ViewerState& s)
std::string outFileName = LoadSpec::getFilename(s.datasets[0].url);

std::ostringstream ss;
ss << "# AGAVE Python script (standalone)" << std::endl;
ss << "# Generated by AGAVE version " << qApp->applicationVersion().toStdString() << std::endl;
ss << "# We recommend exiting any AGAVE instances before running this script." << std::endl;
ss << "# pip install agave_pyclient" << std::endl;
ss << "# agave --server &" << std::endl;
// TODO give user option to save a self-contained script or require agave server
// ss << "# agave --server &" << std::endl;
ss << "# python myscript.py" << std::endl << std::endl;
ss << "import agave_pyclient as agave" << std::endl << std::endl;
renderlib::RendererType rendererType = renderlib::RendererType_Pathtrace;
if (s.rendererType == Serialize::RendererType_PID::RAYMARCH) {
rendererType = renderlib::RendererType_Raymarch;
}
std::string mode = renderlib::rendererTypeToString(rendererType);
ss << "r = agave.AgaveRenderer(mode=\"" << mode << "\")" << std::endl;

std::string obj = "r.";
std::string appPath = qApp->applicationFilePath().toStdString();
// ss << "r = agave.AgaveRenderer(mode=\"" << mode << "\")" << std::endl;
ss << "r = agave.AgaveRenderer.launch_agave(\"" << appPath << "\", mode =\"" << mode << "\")" << std::endl;
ss << "with r:" << std::endl;
std::string obj = " r.";

// use whole loadspec to get multiresolution level, selected channels and sub-ROI
// TODO reconcile subpath with index of multiresolution level.
Expand Down Expand Up @@ -138,6 +145,7 @@ stateToPythonScript(const Serialize::ViewerState& s)
.toPythonString()
<< std::endl;
// TODO use value from viewport or render window capture settings?
// if user never went into capture, then this will be 32 the default, and will not re-create the viewport render.
ss << obj << SetRenderIterationsCommand({ s.capture.samples }).toPythonString() << std::endl;
ss << obj << SetPrimaryRayStepSizeCommand({ s.pathTracer.primaryStepSize }).toPythonString() << std::endl;
ss << obj << SetSecondaryRayStepSizeCommand({ s.pathTracer.secondaryStepSize }).toPythonString() << std::endl;
Expand Down
15 changes: 14 additions & 1 deletion agave_app/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,15 @@
#include <QString>
#include <QUrlQuery>

static constexpr int DEFAULT_PORT = 1235;
struct ServerParams
{
int _port;
QStringList _preloadList;

// defaults
ServerParams()
: _port(1235)
: _port(DEFAULT_PORT)
{
}
};
Expand Down Expand Up @@ -163,6 +164,12 @@ main(int argc, char* argv[])
QCommandLineOption serverOption("server",
QCoreApplication::translate("main", "Run as websocket server without GUI."));
parser.addOption(serverOption);
QCommandLineOption serverPortOption("port",
QCoreApplication::translate("main", "Specify the port for server mode."),
QCoreApplication::translate("main", "port"),
QString::number(DEFAULT_PORT));

parser.addOption(serverPortOption);

QCommandLineOption loadOption("load",
QCoreApplication::translate("main", "File or url to load."),
Expand All @@ -188,6 +195,8 @@ main(int argc, char* argv[])
parser.process(a);

bool isServer = parser.isSet(serverOption);
bool hasPort = parser.isSet(serverPortOption);
int port = parser.value(serverPortOption).toInt();
bool listDevices = parser.isSet(listDevicesOption);
int selectedGpu = parser.value(selectGpuOption).toInt();
QString fileInput = parser.value(loadOption);
Expand Down Expand Up @@ -218,6 +227,10 @@ main(int argc, char* argv[])
if (isServer) {
QString configPath = parser.value(serverConfigOption);
ServerParams p = readConfig(configPath);
// port from command line overrides config file
if (hasPort) {
p._port = port;
}

StreamServer* server = new StreamServer(p._port, false, 0);

Expand Down
91 changes: 88 additions & 3 deletions agave_pyclient/agave_pyclient/agave.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,61 @@
import json
import math
import numpy
import os
import queue
import re
from PIL import Image
import subprocess
import sys
from typing import List

from .commandbuffer import CommandBuffer


def find_matching_subdirectories(root_dir, regex):
"""
Finds subdirectories within a root directory that match a given regular expression.

Args:
root_dir: The path to the root directory to search within.
regex: The regular expression pattern to match against subdirectory names.

Returns:
A list of strings, where each string is the full path to a matching subdirectory.
Returns an empty list if no matching subdirectories are found.
"""
matching_dirs = []
for item in os.listdir(root_dir):
item_path = os.path.join(root_dir, item)
if os.path.isdir(item_path) and re.search(regex, item):
matching_dirs.append(item_path)
return matching_dirs


def guess_agave_path() -> str | None:
if sys.platform == "win32":
# find versioned install directory of the form:
# "Program Files\\AGAVE #.#.#\\agave-install"
possible = find_matching_subdirectories(
"C:\\Program Files", "AGAVE [0-9]+.[0-9]+.[0-9]+"
)
if len(possible) == 0:
print("AGAVE not found in Program Files")
return None
# if there are multiple versions, pick the last one
path = os.path.join(possible[-1], "agave-install", "agave.exe")
elif sys.platform == "linux" or sys.platform == "linux2":
path = "~/agave/build/agave"
elif sys.platform == "darwin":
path = "/Applications/agave.app/Contents/MacOS/agave"
else:
# Code to run on other platforms
print("Running on an unknown operating system")
print("Can't guess agave path")
return None
return path


def lerp(startframe, endframe, startval, endval):
x = numpy.linspace(
startframe, endframe, num=endframe - startframe + 1, endpoint=True
Expand Down Expand Up @@ -113,8 +161,8 @@ def wait_for_json(self):
break
return None

def received_message(self, m):
self.messages.put(copy.deepcopy(m))
def received_message(self, message):
self.messages.put(copy.deepcopy(message))

def closed(self, code, reason=None):
"""
Expand Down Expand Up @@ -170,7 +218,10 @@ class AgaveRenderer:

"""

def __init__(self, url="ws://localhost:1235/", mode="pathtrace") -> None:
def __init__(
self, url="ws://localhost:1235/", mode="pathtrace", agave_process=None
) -> None:
self.agave_process = agave_process
self.cb = CommandBuffer()
self.session_name = ""
if mode != "pathtrace" and mode != "raymarch":
Expand All @@ -183,6 +234,40 @@ def __init__(self, url="ws://localhost:1235/", mode="pathtrace") -> None:
# print("keyboard")
# ws.close()

@classmethod
def launch_agave(cls, path: str, port: int = 1235, mode: str = "pathtrace"):
try:
# Check to see if path exists. If path is empty or None,
# then try to guess at agave install locations.
if path is None or path == "":
guesspath = guess_agave_path()
if guesspath is None:
print(
"AGAVE not found. Try passing a known AGAVE path to launch_agave."
)
return None
path = guesspath

a = subprocess.Popen(
[
path,
"--server",
f"--port={port}",
]
)
except OSError as e:
print(f"Error launching AGAVE from {path}: {e}")
return None
return cls(f"ws://localhost:{port}/", mode=mode, agave_process=a)

def __enter__(self):
return self

def __exit__(self, exc_type, exc_val, exc_tb):
self.ws.close()
if self.agave_process:
self.agave_process.terminate()

def session(self, name: str):
"""
Set the current session name. Use the full path to the name of the output
Expand Down
2 changes: 2 additions & 0 deletions agave_pyclient/agave_pyclient/bin/example1.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
filename = (
"https://animatedcell-test-data.s3.us-west-2.amazonaws.com/variance/1.zarr"
)
# AGAVE is already running in server mode. Connect to it.
# Note that it could be remote.
r = AgaveRenderer(url="ws://localhost:1235/", mode="raymarch")
r.load_data(filename, 0, 0, 0)
r.set_resolution(512, 512)
Expand Down
122 changes: 122 additions & 0 deletions agave_pyclient/agave_pyclient/bin/example2.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
from agave_pyclient import AgaveRenderer


def main():
# r = AgaveRenderer(url="ws://localhost:1235/", mode="pathtrace")
r = AgaveRenderer.launch_agave("D:\\agave_build\\install\\agave-install\\agav.exe")
with r:
r.load_data(
"https://animatedcell-test-data.s3.us-west-2.amazonaws.com/variance/1.zarr",
0,
2,
0,
[0, 1, 2, 3, 4, 5, 6, 7, 8],
[0, 231, 0, 156, 0, 65],
)
r.set_resolution(1448, 996)
r.background_color(0, 0, 0)
r.show_bounding_box(1)
r.show_scale_bar(0)
r.bounding_box_color(1, 1, 1)
r.render_iterations(32)
r.set_primary_ray_step_size(4)
r.set_secondary_ray_step_size(4)
r.set_interpolation(1)
r.set_voxel_scale(0.433333, 0.433333, 0.29)
r.set_flip_axis(1, 1, 1)
r.set_clip_region(0, 1, 0, 1, 0, 1)
r.set_clip_plane(0, 0, 0, 0)
r.eye(0.5, 0.337662, 1.26718)
r.target(0.5, 0.337662, 0.0941558)
r.up(0, 1, 0)
r.camera_projection(1, 0.5)
r.exposure(0.75)
r.density(100)
r.aperture(0)
r.focaldist(0.75)
r.enable_channel(0, 1)
r.mat_diffuse(0, 1, 0, 1, 1)
r.mat_specular(0, 0, 0, 0, 0)
r.mat_emissive(0, 0, 0, 0, 0)
r.mat_glossiness(0, 1)
r.mat_opacity(0, 1)
r.set_window_level(0, 0.25, 0.1807)
r.set_color_ramp(0, "none", [0, 1, 1, 1, 1, 1, 1, 1, 1, 1])
r.enable_channel(1, 1)
r.mat_diffuse(1, 1, 1, 1, 1)
r.mat_specular(1, 0, 0, 0, 0)
r.mat_emissive(1, 0, 0, 0, 0)
r.mat_glossiness(1, 1)
r.mat_opacity(1, 1)
r.set_window_level(1, 0.25, 0.2289)
r.set_color_ramp(1, "none", [0, 1, 1, 1, 1, 1, 1, 1, 1, 1])
r.enable_channel(2, 1)
r.mat_diffuse(2, 0, 1, 1, 1)
r.mat_specular(2, 0, 0, 0, 0)
r.mat_emissive(2, 0, 0, 0, 0)
r.mat_glossiness(2, 1)
r.mat_opacity(2, 1)
r.set_window_level(2, 0.25, 0.2289)
r.set_color_ramp(2, "none", [0, 1, 1, 1, 1, 1, 1, 1, 1, 1])
r.enable_channel(3, 0)
r.mat_diffuse(3, 0.500008, 0, 0, 1)
r.mat_specular(3, 0, 0, 0, 0)
r.mat_emissive(3, 0, 0, 0, 0)
r.mat_glossiness(3, 1)
r.mat_opacity(3, 1)
r.set_percentile_threshold(3, 0.5, 0.98)
r.set_color_ramp(3, "none", [0, 1, 1, 1, 1, 1, 1, 1, 1, 1])
r.enable_channel(4, 0)
r.mat_diffuse(4, 0, 0.145892, 0.500008, 1)
r.mat_specular(4, 0, 0, 0, 0)
r.mat_emissive(4, 0, 0, 0, 0)
r.mat_glossiness(4, 1)
r.mat_opacity(4, 1)
r.set_percentile_threshold(4, 0.5, 0.98)
r.set_color_ramp(4, "none", [0, 1, 1, 1, 1, 1, 1, 1, 1, 1])
r.enable_channel(5, 0)
r.mat_diffuse(5, 0.291798, 0.500008, 0, 1)
r.mat_specular(5, 0, 0, 0, 0)
r.mat_emissive(5, 0, 0, 0, 0)
r.mat_glossiness(5, 1)
r.mat_opacity(5, 1)
r.set_percentile_threshold(5, 0.5, 0.98)
r.set_color_ramp(5, "none", [0, 1, 1, 1, 1, 1, 1, 1, 1, 1])
r.enable_channel(6, 0)
r.mat_diffuse(6, 0.500008, 0, 0.43769, 1)
r.mat_specular(6, 0, 0, 0, 0)
r.mat_emissive(6, 0, 0, 0, 0)
r.mat_glossiness(6, 1)
r.mat_opacity(6, 1)
r.set_percentile_threshold(6, 0.5, 0.98)
r.set_color_ramp(6, "none", [0, 1, 1, 1, 1, 1, 1, 1, 1, 1])
r.enable_channel(7, 0)
r.mat_diffuse(7, 0, 0.500008, 0.416403, 1)
r.mat_specular(7, 0, 0, 0, 0)
r.mat_emissive(7, 0, 0, 0, 0)
r.mat_glossiness(7, 1)
r.mat_opacity(7, 1)
r.set_percentile_threshold(7, 0.5, 0.98)
r.set_color_ramp(7, "none", [0, 1, 1, 1, 1, 1, 1, 1, 1, 1])
r.enable_channel(8, 0)
r.mat_diffuse(8, 0.500008, 0.270512, 0, 1)
r.mat_specular(8, 0, 0, 0, 0)
r.mat_emissive(8, 0, 0, 0, 0)
r.mat_glossiness(8, 1)
r.mat_opacity(8, 1)
r.set_percentile_threshold(8, 0.5, 0.98)
r.set_color_ramp(8, "none", [0, 1, 1, 1, 1, 1, 1, 1, 1, 1])
r.skylight_top_color(1, 1, 1)
r.skylight_middle_color(1, 1, 1)
r.skylight_bottom_color(1, 1, 1)
r.light_pos(0, 1.5, 5.54884, 0.952752)
r.light_color(0, 10, 10, 10)
r.light_size(0, 0.15, 0.15)
for i in range(1, 65):
r.set_clip_region(0, 1, 0, 1, 0, i / 65)
r.session(f"1_{i}.zarr.png")
r.redraw()


if __name__ == "__main__":
main()
Loading