Skip to content
Closed
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
14 changes: 13 additions & 1 deletion mrd-viz/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,16 @@

Lightweight MRD inspection and preview VS Code extension.

The current focus is direct inspection of existing `.mrd` files rather than reconstruction orchestration. See `docs/TECHNICAL_DESIGN.md` for detailed project context and implementation plan.
The current focus is direct inspection of existing `.mrd` files rather than reconstruction orchestration. See `docs/TECHNICAL_DESIGN.md` for detailed project context and implementation plan.

## Functionality / UX progression

| Track | Before UX | After UX | Status |
| --- | --- | --- | --- |
| **PR78 checkpoint** | Dev-container-first onboarding (`just mrd-viz-container-setup`) | Extension/backend become usable across windows attached to the same dev container | Delivered |
| **PR79 + D3 consolidated** | Users could end up in Python/PyPI/manual interpreter flows | **Stable install path added as the primary UX** via `https://github.com/ismrmrd/mrd/releases/latest`, with in-product “Open Stable Install Link” guidance | Delivered |
| **D1 local fallback** | Backend setup was mostly manual | Command-driven managed setup + manual override remain available for development/debug paths | Delivered |
| **D3 workflow surface** | File-first custom editor only | Added workflow/session-level panel scaffold (`Open Workflow View (D3 Scaffold)`) to iterate on run-level UX | Scaffolded |
| **D2** | No dedicated multi-file comparison workflow | Planned deeper multi-file comparison UX on top of current viewer contracts | Planned |
| **Marketplace** | Side-load/release flow primary | Planned one-click marketplace install/update once publisher path is finalized | Planned |
| **F10 onboarding polish** | Onboarding still split across docs + commands | Planned final simplification from researcher feedback rounds | Planned |
4 changes: 3 additions & 1 deletion mrd-viz/extension/mrd-viz/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,6 @@ Check [Keep a Changelog](http://keepachangelog.com/) for recommendations on how

## [Unreleased]

- Initial release
- Added a stable install command (`MRD Viz: Open Stable Install Link`) that opens the latest MRD release URL.
- Updated backend-missing UX to prioritize the stable release install path before Python fallback setup.
- Added `MRD Viz: Open Workflow View (D3 Scaffold)` as the first workflow/session-level D3 panel scaffold.
13 changes: 9 additions & 4 deletions mrd-viz/extension/mrd-viz/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,13 @@ A Visual Studio Code extension for inspecting [MRD](https://github.com/ismrmrd/m

## Requirements

MRD Viz relies on a Python backend (the `mrd_viz` package) to read `.mrd` files. You need:
For researcher onboarding, use the stable release installer flow first:

- Python 3.12
- The `mrd_viz` backend installed into an environment the extension can find.
- Open: `https://github.com/ismrmrd/mrd/releases/latest`
- Install the latest MRD Viz release artifact

Point the extension at the interpreter with the `mrdViz.backendPath` setting (see below). During local development (the F5 dev host) the extension also auto-detects a virtual environment at `mrd-viz/backend/.venv`.
That path is intended to avoid local Python/PyPI setup.
For development/debug fallback, MRD Viz can use a Python backend (the `mrd_viz` package) via `mrdViz.backendPath`. During local development (the F5 dev host) the extension also auto-detects `mrd-viz/backend/.venv`.

## Extension Settings

Expand All @@ -28,6 +29,10 @@ This extension contributes the following settings:
## Commands

- `MRD Viz: Open File` — open the selected or picked `.mrd` file in MRD Viz.
- `MRD Viz: Open Stable Install Link` — open the latest MRD release page for the stable installer path.
- `MRD Viz: Set Up Backend` — choose stable installer, managed local setup, or manual interpreter selection.
- `MRD Viz: Select Python Interpreter` — set `mrdViz.backendPath` manually.
- `MRD Viz: Open Workflow View (D3 Scaffold)` — open the first workflow/session-level D3 panel scaffold.

## Known Issues

Expand Down
10 changes: 10 additions & 0 deletions mrd-viz/extension/mrd-viz/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,16 @@
"command": "mrd-viz.selectInterpreter",
"title": "Select Python Interpreter",
"category": "MRD Viz"
},
{
"command": "mrd-viz.openStableInstallLink",
"title": "Open Stable Install Link",
"category": "MRD Viz"
},
{
"command": "mrd-viz.openWorkflowView",
"title": "Open Workflow View (D3 Scaffold)",
"category": "MRD Viz"
}
],
"customEditors": [
Expand Down
50 changes: 49 additions & 1 deletion mrd-viz/extension/mrd-viz/src/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@ import {
import { invalidateBackendCache, managedVenvDirectory, managedVenvPythonPath } from './backendResolver';
import { MrdEditorProvider, MRD_VIEW_TYPE } from './mrdEditorProvider';
import { runProcess } from './subprocess';
import { showWorkflowScaffoldPanel } from './workflowPanel';

const STABLE_RELEASE_URL = 'https://github.com/ismrmrd/mrd/releases/latest';

export function activate(context: vscode.ExtensionContext) {
const outputChannel = vscode.window.createOutputChannel('MRD Viz');
Expand All @@ -28,6 +31,8 @@ export function activate(context: vscode.ExtensionContext) {
context.subscriptions.push(
vscode.commands.registerCommand('mrd-viz.setUpBackend', () => setUpBackend(context, outputChannel)),
vscode.commands.registerCommand('mrd-viz.selectInterpreter', () => selectInterpreter()),
vscode.commands.registerCommand('mrd-viz.openStableInstallLink', () => openStableInstallLink()),
vscode.commands.registerCommand('mrd-viz.openWorkflowView', () => showWorkflowScaffoldPanel()),
vscode.workspace.onDidChangeConfiguration(event => {
if (event.affectsConfiguration('mrdViz.backendPath')) {
invalidateBackendCache();
Expand Down Expand Up @@ -106,6 +111,46 @@ async function pickTargetUri(resource?: vscode.Uri, selectedResources?: vscode.U
}

async function setUpBackend(context: vscode.ExtensionContext, outputChannel: vscode.OutputChannel): Promise<void> {
const quickPick = await vscode.window.showQuickPick(
[
{
label: 'Use stable release installer (recommended)',
description: 'No Python/PyPI setup steps. Open the latest GitHub release install link.',
action: 'stable' as const,
},
{
label: 'Set up backend automatically here',
description: 'Creates a managed Python environment and installs mrd-viz.',
action: 'managed' as const,
},
{
label: 'Select backend interpreter manually',
description: 'Point mrdViz.backendPath at an existing backend environment.',
action: 'manual' as const,
},
],
{
placeHolder: 'Choose backend setup mode',
ignoreFocusOut: true,
},
);
if (!quickPick) {
return;
}

if (quickPick.action === 'stable') {
await openStableInstallLink();
return;
}
if (quickPick.action === 'manual') {
await selectInterpreter();
return;
}

await setUpManagedBackend(context, outputChannel);
}

async function setUpManagedBackend(context: vscode.ExtensionContext, outputChannel: vscode.OutputChannel): Promise<void> {
// If a managed backend is already provisioned, this rebuilds it from scratch, so confirm the
// reinstall rather than silently clobbering a working environment.
const alreadyInstalled = existsSync(managedVenvPythonPath(context));
Expand Down Expand Up @@ -133,6 +178,10 @@ async function setUpBackend(context: vscode.ExtensionContext, outputChannel: vsc
}
}

async function openStableInstallLink(): Promise<void> {
await vscode.env.openExternal(vscode.Uri.parse(STABLE_RELEASE_URL));
}

async function selectInterpreter(): Promise<void> {
const picked = await vscode.window.showOpenDialog({
canSelectMany: false,
Expand Down Expand Up @@ -350,4 +399,3 @@ function appendIfPresent(outputChannel: vscode.OutputChannel, label: string, tex
function isMrdFile(filePath: string): boolean {
return filePath.toLowerCase().endsWith('.mrd');
}

6 changes: 5 additions & 1 deletion mrd-viz/extension/mrd-viz/src/mrdEditorProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,11 @@ import { appendIfPresent, bindViewerMessageHandling } from './viewerController';

export const MRD_VIEW_TYPE = 'mrd-viz.mrdFile';

const BACKEND_SETUP_COMMANDS = new Set(['mrd-viz.setUpBackend', 'mrd-viz.selectInterpreter']);
const BACKEND_SETUP_COMMANDS = new Set([
'mrd-viz.setUpBackend',
'mrd-viz.selectInterpreter',
'mrd-viz.openStableInstallLink',
]);

class MrdDocument implements vscode.CustomDocument {
constructor(readonly uri: vscode.Uri) {}
Expand Down
3 changes: 3 additions & 0 deletions mrd-viz/extension/mrd-viz/src/test/extension.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,8 @@ suite('MRD Viz Extension', () => {
assert.ok(commands.includes('mrd-viz.openFile'));
assert.ok(commands.includes('mrd-viz.setUpBackend'));
assert.ok(commands.includes('mrd-viz.selectInterpreter'));
assert.ok(commands.includes('mrd-viz.openStableInstallLink'));
assert.ok(commands.includes('mrd-viz.openWorkflowView'));
});

test('routes command opens through the custom editor view type', () => {
Expand Down Expand Up @@ -97,6 +99,7 @@ suite('MRD Viz Extension', () => {
assert.ok(html.includes('No module named &#39;mrd_viz&#39;') || html.includes("No module named 'mrd_viz'"));
assert.ok(html.includes('mrd-viz.setUpBackend'));
assert.ok(html.includes('mrd-viz.selectInterpreter'));
assert.ok(html.includes('mrd-viz.openStableInstallLink'));
});

test('tailors the backend-missing view when a developer override is broken', () => {
Expand Down
9 changes: 7 additions & 2 deletions mrd-viz/extension/mrd-viz/src/webviewHtml.ts
Original file line number Diff line number Diff line change
Expand Up @@ -103,12 +103,16 @@ export function getMrdBackendMissingHtml(webview: vscode.Webview, tried: Backend
<ul class="candidates">${triedItems}</ul>

<div class="actions">
<button type="button" id="stable">Open Stable Install Link</button>
<button type="button" id="select" class="${selectClass}">Select Python Interpreter…</button>
<button type="button" id="setup" class="${setupClass}">Set Up Backend Automatically…</button>
</div>

<h2>Point MRD Viz at a Python environment</h2>
<p>Create an environment that has the <code>mrd_viz</code> package, then set <code>mrdViz.backendPath</code> to its interpreter (or to a prebuilt <code>mrd-viz</code> binary):</p>
<h2>Recommended: stable release installer (no Python/PyPI steps)</h2>
<p>Use <b>Open Stable Install Link</b> to go to the latest MRD release page and install the researcher build artifact. That path is intended to avoid local Python/PyPI setup.</p>

<h2>Fallback: point MRD Viz at a Python environment</h2>
<p>If you are developing or debugging, create an environment that has the <code>mrd_viz</code> package, then set <code>mrdViz.backendPath</code> to its interpreter (or to a prebuilt <code>mrd-viz</code> binary):</p>
<pre class="setup">python3 -m venv ~/.mrd-viz-venv
# install the backend from your mrd checkout (mrd-viz is not published to PyPI):
~/.mrd-viz-venv/bin/pip install -e path/to/mrd/mrd-viz/backend
Expand All @@ -122,6 +126,7 @@ export function getMrdBackendMissingHtml(webview: vscode.Webview, tried: Backend
(function () {
const vscode = acquireVsCodeApi();
function send(command) { vscode.postMessage({ type: 'command', command: command }); }
document.getElementById('stable').addEventListener('click', function () { send('mrd-viz.openStableInstallLink'); });
document.getElementById('setup').addEventListener('click', function () { send('mrd-viz.setUpBackend'); });
document.getElementById('select').addEventListener('click', function () { send('mrd-viz.selectInterpreter'); });
})();
Expand Down
84 changes: 84 additions & 0 deletions mrd-viz/extension/mrd-viz/src/workflowPanel.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
import * as vscode from 'vscode';

const WORKFLOW_VIEW_TYPE = 'mrd-viz.workflowScaffold';

export function showWorkflowScaffoldPanel(): void {
const panel = vscode.window.createWebviewPanel(
WORKFLOW_VIEW_TYPE,
'MRD Viz: Workflow View (D3 Scaffold)',
vscode.ViewColumn.Beside,
{ enableScripts: false, retainContextWhenHidden: true },
);
panel.webview.html = workflowScaffoldHtml(panel.webview);
}

function workflowScaffoldHtml(webview: vscode.Webview): string {
const nonce = getNonce();
return `<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="Content-Security-Policy" content="default-src 'none'; style-src ${webview.cspSource} 'nonce-${nonce}';">
<title>MRD Viz Workflow Scaffold</title>
<style nonce="${nonce}">
body {
margin: 0;
padding: 20px;
color: var(--vscode-foreground);
background: var(--vscode-editor-background);
font-family: var(--vscode-font-family);
font-size: var(--vscode-font-size);
line-height: 1.45;
}
h1, h2 { margin: 0 0 8px; }
p { margin: 0 0 12px; color: var(--vscode-descriptionForeground); }
.grid { display: grid; gap: 12px; grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); }
.card {
padding: 10px;
border: 1px solid var(--vscode-panel-border);
border-radius: 6px;
background: var(--vscode-sideBar-background);
}
.card h2 { font-size: 13px; }
.card ul { margin: 0; padding-left: 18px; color: var(--vscode-descriptionForeground); }
</style>
</head>
<body>
<h1>D3 workflow-level view scaffold</h1>
<p>This panel is the initial D3 surface for workflow/session-level MRD navigation while file-first viewing remains stable.</p>
<div class="grid">
<section class="card">
<h2>Run timeline (placeholder)</h2>
<ul>
<li>Session/run sequence with stage markers</li>
<li>Filter by acquisition, image, and waveform events</li>
</ul>
</section>
<section class="card">
<h2>Artifact list (placeholder)</h2>
<ul>
<li>Grouped outputs per run</li>
<li>Open output artifacts directly in MRD Viz</li>
</ul>
</section>
<section class="card">
<h2>Comparison hooks (placeholder)</h2>
<ul>
<li>Metadata snapshots across runs</li>
<li>Bridge into D2 multi-file comparison workflows</li>
</ul>
</section>
</div>
</body>
</html>`;
}

function getNonce(): string {
const alphabet = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
let nonce = '';
for (let i = 0; i < 32; i += 1) {
nonce += alphabet.charAt(Math.floor(Math.random() * alphabet.length));
}
return nonce;
}