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
25 changes: 18 additions & 7 deletions client/platform/desktop/backend/native/interactive.ts
Original file line number Diff line number Diff line change
Expand Up @@ -201,12 +201,15 @@ export class InteractiveServiceManager extends EventEmitter {
const segConfig = npath.join(pipelines, 'interactive_segmenter_default.conf');
const stereoConfig = npath.join(pipelines, 'interactive_stereo_default.conf');

// -I: isolated mode — do not prepend the process cwd to sys.path. Without
// this, a sibling folder named "coverage" (e.g. Vitest HTML output under
// client/coverage/) shadows Python's coverage package, breaks numba import,
// and prevents ocv_watershed from registering during plugin load.
// -s: ignore the per-user site-packages dir so a stray package in
// ~/.local (e.g. a conflicting "coverage" that breaks numba import and
// prevents ocv_watershed from registering) can't shadow VIAME's. We can't
// use -I/-E here: those also discard PYTHONPATH, which setup_viame.sh uses
// to expose the viame module in from-source/system-python builds. The
// process cwd is forced to viamePath (below), which has no shadowing
// modules, so the cwd entry on sys.path is harmless.
const pyCommand = [
`"${pythonExe}" -I -m viame.core.interactive_service`,
`"${pythonExe}" -s -m viame.core.interactive_service`,
`--segmentation-config "${segConfig}"`,
`--stereo-config "${stereoConfig}"`,
].join(' ');
Expand Down Expand Up @@ -456,11 +459,19 @@ export class InteractiveServiceManager extends EventEmitter {
settings: Settings,
calibration?: StereoCalibration,
calibrationFile?: string,
): Promise<{ success: boolean; error?: string }> {
): Promise<{ success: boolean; error?: string; launchFailed?: boolean }> {
try {
await this.ensureStarted(settings);
} catch (err) {
return { success: false, error: err instanceof Error ? err.message : String(err) };
// The service process couldn't even start (missing interpreter, import
// failure, etc.). This is an infrastructure error, distinct from a benign
// per-dataset enable failure (e.g. missing calibration), so flag it so the
// caller always surfaces it rather than degrading silently.
return {
success: false,
launchFailed: true,
error: err instanceof Error ? err.message : String(err),
};
}
try {
const response = await this.sendRequest({
Expand Down
9 changes: 8 additions & 1 deletion client/platform/desktop/backend/native/linux.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,14 @@ function getViameConstants(settings: Settings): viame.ViameConstants {
}

function getViamePythonExe(settings: Settings): string {
return npath.join(settings.viamePath, 'bin', 'python');
// Packaged binary installs bundle an interpreter at bin/python. From-source
// builds against system python have no such interpreter and instead rely on
// setup_viame.sh putting the correct python on PATH, so fall back to that.
const bundled = npath.join(settings.viamePath, 'bin', 'python');
if (fs.existsSync(bundled)) {
return bundled;
}
return 'python';
}

async function validateViamePath(settings: Settings): Promise<true | string> {
Expand Down
2 changes: 1 addition & 1 deletion client/platform/desktop/frontend/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -440,7 +440,7 @@ interface StereoTransferPointsResponse {
async function stereoEnable(
calibration?: StereoCalibration,
calibrationFile?: string,
): Promise<{ success: boolean; error?: string }> {
): Promise<{ success: boolean; error?: string; launchFailed?: boolean }> {
return window.diveDesktop.invoke('stereo-enable', { calibration, calibrationFile });
}

Expand Down
22 changes: 16 additions & 6 deletions client/platform/desktop/frontend/components/ViewerLoader.vue
Original file line number Diff line number Diff line change
Expand Up @@ -442,7 +442,14 @@ export default defineComponent({
}
const result = await stereoEnable(undefined, stereoCalibrationFile);
if (!result.success) {
throw new Error(result.error || 'Failed to enable stereo service');
// launchFailed means the backend service couldn't even start (e.g.
// missing python interpreter or a broken import). That is a real
// error and must always be surfaced, even on the load-time
// auto-enable, instead of degrading silently like a missing
// calibration would.
const err = new Error(result.error || 'Failed to enable stereo service');
(err as Error & { launchFailed?: boolean }).launchFailed = result.launchFailed;
throw err;
}
stereoEnabled.value = true;
stereoLoadingDialog.value = false;
Expand All @@ -454,15 +461,18 @@ export default defineComponent({
} catch (err) {
stereoEnabled.value = false;
console.error('[Stereo] Failed to enable interactive stereo:', err);
if (userInitiated) {
// The user explicitly enabled a feature: revert the toggles and
// surface the failure in a dialog.
const launchFailed = (err as Error & { launchFailed?: boolean }).launchFailed === true;
if (userInitiated || launchFailed) {
// The user explicitly enabled a feature, or the backend service
// failed to launch (an infrastructure error). Either way, revert the
// toggles and surface the failure in a dialog.
disableStereoFeatureToggles();
stereoLoadingError.value = err instanceof Error ? err.message : String(err);
stereoLoadingDialog.value = true;
} else {
// Load-time auto-enable failed: degrade silently and keep the
// toggle states so a later calibrated dataset still works.
// Load-time auto-enable failed for a benign reason (e.g. an
// uncalibrated stereo dataset): degrade silently and keep the toggle
// states so a later calibrated dataset still works.
stereoLoadingDialog.value = false;
}
}
Expand Down
Loading