Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
47 commits
Select commit Hold shift + click to select a range
f2926c5
Add viv loader
igoroctaviano Apr 7, 2026
de332d5
Improve viewport displaying
igoroctaviano Apr 7, 2026
f7ebea7
Fixing 8-bit: Rendering images correctly (wrong position)
igoroctaviano Apr 7, 2026
42dbd68
Fixing 8-bit: Rendering images correctly (correct position)
igoroctaviano Apr 7, 2026
b2dad70
Add request abort
igoroctaviano Apr 7, 2026
29d36d1
Working: Bulk ann (polygons)
igoroctaviano Apr 8, 2026
457f48e
Working: Bulk ann (polygons)
igoroctaviano Apr 8, 2026
bd5bd55
Linting
igoroctaviano Apr 8, 2026
3fd745d
Linting and padding
igoroctaviano Apr 8, 2026
4fd23eb
Linting and padding and styling
igoroctaviano Apr 8, 2026
c03494a
Merge branch 'master' of github.com:ImagingDataCommons/slim into feat…
igoroctaviano Apr 14, 2026
d4decc8
Fix icc profile for viv mode
igoroctaviano Apr 14, 2026
5f0ea90
Merge branch 'master' into feat/viv-loader
igoroctaviano Apr 14, 2026
c231a79
Merge branch 'master' of github.com:ImagingDataCommons/slim into feat…
igoroctaviano Apr 17, 2026
3277448
Merge branch 'feat/viv-loader' of github.com:ImagingDataCommons/slim …
igoroctaviano Apr 17, 2026
d136f1d
Merge branch 'master' of github.com:ImagingDataCommons/slim into feat…
igoroctaviano May 7, 2026
13bdd9a
Improve performance
igoroctaviano May 7, 2026
a39ec69
Improve performance
igoroctaviano May 7, 2026
d76573d
Add logging and chunk annotations processing
igoroctaviano May 19, 2026
a3147c4
Load images from center outwards
igoroctaviano May 19, 2026
74973e5
Load annotations from center
igoroctaviano May 19, 2026
93987de
Use points to improve memory usage
igoroctaviano May 19, 2026
0614d09
Scale points
igoroctaviano May 19, 2026
6b131ba
Hardening
igoroctaviano May 20, 2026
a312e9e
Add loading indicator for bulk annotation retrieve and processing.
igoroctaviano May 20, 2026
8e803a1
Add tooltip
igoroctaviano May 20, 2026
5def528
Improving image load and caching
igoroctaviano May 20, 2026
7e86241
Update comments
igoroctaviano May 20, 2026
772dbd5
Clean up ci
igoroctaviano May 20, 2026
0dcd397
Add tooltip back
igoroctaviano May 20, 2026
5e21695
Update point size
igoroctaviano May 20, 2026
44a3699
Load ann during retrieval
igoroctaviano Jun 4, 2026
44eef6f
Fix final rendering
igoroctaviano Jun 4, 2026
148a143
Merge branch 'master' into feat/viv-loader
igoroctaviano Jun 5, 2026
61f353c
Merge branch 'master' of github.com:ImagingDataCommons/slim into feat…
igoroctaviano Jul 2, 2026
c86d8d2
Merge branch 'master' of githubt push origin feat/viv-`:w
igoroctaviano Jul 2, 2026
5698f5d
Update lock
igoroctaviano Jul 2, 2026
3552687
Merge branch 'master' into feat/viv-loader
igoroctaviano Jul 6, 2026
d7c322f
Merge branch 'master' into feat/viv-loader
igoroctaviano Jul 9, 2026
e0afffc
Address stream / load and pan
igoroctaviano Jul 13, 2026
983240d
Adjust stroke
igoroctaviano Jul 13, 2026
1751074
Adjust stroke
igoroctaviano Jul 13, 2026
498139d
Merge branch 'master' of github.com:ImagingDataCommons/slim into feat…
igoroctaviano Jul 13, 2026
bc00df8
Address errors
igoroctaviano Jul 13, 2026
08c16c1
Merge branch 'master' of github.com:ImagingDataCommons/slim into feat…
igoroctaviano Jul 13, 2026
e1abe00
Address errors
igoroctaviano Jul 13, 2026
f79338d
Update point vs polygon defaults
igoroctaviano Jul 14, 2026
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: 14 additions & 0 deletions craco.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,20 @@ module.exports = {
config.experiments = {
asyncWebAssembly: true
}

/* Viv / Deck.gl / Luma ship modern JS; transpile for CRA 5 / webpack 5. */
config.module.rules.push({
test: /\.m?js$/,
include: /node_modules[\\/](@deck\.gl|@luma\.gl|@math\.gl|@probe\.gl|@hms-dbmi[\\/]viv)[\\/]/,
use: {
loader: require.resolve('babel-loader'),
options: {
presets: [require.resolve('babel-preset-react-app/dependencies')],
cacheDirectory: true
}
}
})

return config
}
},
Expand Down
16 changes: 15 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -37,12 +37,26 @@
]
},
"dependencies": {
"@deck.gl/core": "9.1.15",
"@deck.gl/extensions": "9.1.15",
"@deck.gl/geo-layers": "9.1.15",
"@deck.gl/layers": "9.1.15",
"@deck.gl/mesh-layers": "9.1.15",
"@deck.gl/react": "9.1.15",
"@deck.gl/widgets": "9.1.15",
"@vivjs/layers": "^0.20.0",
"@vivjs/loaders": "^0.20.0",
"@luma.gl/constants": "~9.1.10",
"@luma.gl/core": "~9.1.10",
"@luma.gl/engine": "~9.1.10",
"@luma.gl/shadertools": "~9.1.10",
"@luma.gl/webgl": "~9.1.10",
"antd": "^4.22.8",
"classnames": "^2.2.6",
"dcmjs": "^0.35.0",
"detect-browser": "^5.2.1",
"dicomweb-client": "^0.11.2",
"dicom-microscopy-viewer": "^0.48.22",
"dicomweb-client": "0.10.3",
"oidc-client": "^1.11.5",
"ol": "^10.7.0",
"react": "^18.2.0",
Expand Down
1,255 changes: 1,253 additions & 2 deletions pnpm-lock.yaml

Large diffs are not rendered by default.

161 changes: 104 additions & 57 deletions src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
Navigate,
Route,
Routes,
useLocation,
useParams,
} from 'react-router-dom'

Expand Down Expand Up @@ -47,6 +48,8 @@
config: AppConfig
}): JSX.Element {
const { studyInstanceUID } = useParams()
const location = useLocation()
const isVivRoute = location.pathname.startsWith('/viv/')

if (studyInstanceUID === undefined) {
return <Navigate to="/" replace />
Expand All @@ -64,6 +67,8 @@
app={app}
enableAnnotationTools={enableAnnotationTools}
studyInstanceUID={studyInstanceUID}
isVivRoute={isVivRoute}
vivSettings={config.vivSettings}
/>
</ValidationProvider>
)
Expand Down Expand Up @@ -462,6 +467,52 @@
this.signIn()
}

/** Shared layout for study viewer routes (classic and `/viv/...`). */
private renderCaseViewerShell(

Check warning on line 471 in src/App.tsx

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Method 'renderCaseViewerShell' has too many parameters (8). Maximum allowed is 7.

See more on https://sonarcloud.io/project/issues?id=ImagingDataCommons_slim&issues=AZ8j04DXQNFxXeYuiD2q&open=AZ8j04DXQNFxXeYuiD2q&pullRequest=381
appInfo: {
name: string
version: string
homepage: string
uid: string
organization?: string
},
enableWorklist: boolean,
enableServerSelection: boolean,
enableMemoryMonitoring: boolean,
isLogoutPossible: boolean,
onLogout: () => void,
layoutStyle: { height: string },
layoutContentStyle: { height: string },
): JSX.Element {
return (
<SettingsProvider>
<Layout style={layoutStyle}>
<Header
app={appInfo}
user={this.state.user}
showWorklistButton={enableWorklist}
onServerSelection={this.handleServerSelection}
onUserLogout={isLogoutPossible ? onLogout : undefined}
showServerSelectionButton={enableServerSelection}
clients={this.state.clients}
defaultClients={this.state.defaultClients}
/>
<Layout.Content style={layoutContentStyle}>
<ParametrizedCaseViewer
clients={this.state.clients}
user={this.state.user}
config={this.props.config}
app={appInfo}
/>
</Layout.Content>
{enableMemoryMonitoring && (
<MemoryFooter enabled={enableMemoryMonitoring} />
)}
</Layout>
</SettingsProvider>
)
}

render(): React.ReactNode {
const appInfo = {
name: this.props.name,
Expand Down Expand Up @@ -505,16 +556,20 @@

const layoutStyle = { height: '100vh' }
const layoutContentStyle = { height: '100%' }
const routerFuture = {
v7_startTransition: true,
v7_relativeSplatPath: true,
} as const

if (this.state.redirectTo !== undefined) {
return (
<BrowserRouter basename={this.props.config.path}>
<BrowserRouter basename={this.props.config.path} future={routerFuture}>
<Navigate to={this.state.redirectTo} replace />
</BrowserRouter>
)
} else if (this.state.isLoading) {
return (
<BrowserRouter basename={this.props.config.path}>
<BrowserRouter basename={this.props.config.path} future={routerFuture}>
<Layout style={layoutStyle}>
<Header
app={appInfo}
Expand All @@ -537,7 +592,7 @@
return <InfoPage type="error" message={this.state.error.message} />
} else {
return (
<BrowserRouter basename={this.props.config.path}>
<BrowserRouter basename={this.props.config.path} future={routerFuture}>
<Routes>
<Route
path="/"
Expand All @@ -564,63 +619,55 @@
/>
<Route
path="/studies/:studyInstanceUID/*"
element={
<SettingsProvider>
<Layout style={layoutStyle}>
<Header
app={appInfo}
user={this.state.user}
showWorklistButton={enableWorklist}
onServerSelection={this.handleServerSelection}
onUserLogout={isLogoutPossible ? onLogout : undefined}
showServerSelectionButton={enableServerSelection}
clients={this.state.clients}
defaultClients={this.state.defaultClients}
/>
<Layout.Content style={layoutContentStyle}>
<ParametrizedCaseViewer
clients={this.state.clients}
user={this.state.user}
config={this.props.config}
app={appInfo}
/>
</Layout.Content>
{enableMemoryMonitoring && (
<MemoryFooter enabled={enableMemoryMonitoring} />
)}
</Layout>
</SettingsProvider>
}
element={this.renderCaseViewerShell(
appInfo,
enableWorklist,
enableServerSelection,
enableMemoryMonitoring,
isLogoutPossible,
onLogout,
layoutStyle,
layoutContentStyle,
)}
/>
<Route
path="/projects/:project/locations/:location/datasets/:dataset/dicomStores/:dicomStore/study/:studyInstanceUID/*"
element={
<SettingsProvider>
<Layout style={layoutStyle}>
<Header
app={appInfo}
user={this.state.user}
showWorklistButton={enableWorklist}
onServerSelection={this.handleServerSelection}
onUserLogout={isLogoutPossible ? onLogout : undefined}
showServerSelectionButton={enableServerSelection}
clients={this.state.clients}
defaultClients={this.state.defaultClients}
/>
<Layout.Content style={layoutContentStyle}>
<ParametrizedCaseViewer
clients={this.state.clients}
user={this.state.user}
config={this.props.config}
app={appInfo}
/>
</Layout.Content>
{enableMemoryMonitoring && (
<MemoryFooter enabled={enableMemoryMonitoring} />
)}
</Layout>
</SettingsProvider>
}
element={this.renderCaseViewerShell(
appInfo,
enableWorklist,
enableServerSelection,
enableMemoryMonitoring,
isLogoutPossible,
onLogout,
layoutStyle,
layoutContentStyle,
)}
/>
<Route
path="/viv/studies/:studyInstanceUID/*"
element={this.renderCaseViewerShell(
appInfo,
enableWorklist,
enableServerSelection,
enableMemoryMonitoring,
isLogoutPossible,
onLogout,
layoutStyle,
layoutContentStyle,
)}
/>
<Route
path="/viv/projects/:project/locations/:location/datasets/:dataset/dicomStores/:dicomStore/study/:studyInstanceUID/*"
element={this.renderCaseViewerShell(
appInfo,
enableWorklist,
enableServerSelection,
enableMemoryMonitoring,
isLogoutPossible,
onLogout,
layoutStyle,
layoutContentStyle,
)}
/>
<Route
path="/logout"
Expand Down
21 changes: 21 additions & 0 deletions src/AppConfig.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ export type DicomWebManagerErrorHandler = (
export interface DICOMwebClientRequestHookMetadata {
url: string
method: string
/** Combined request headers from dicomweb-client (needed to re-apply after retry open()). */
headers?: Record<string, string>
}

export interface RetryRequestSettings {
Expand Down Expand Up @@ -77,6 +79,23 @@ export interface OidcSettings {
endSessionEndpoint?: string
}

export interface VivChannelSelection {
c: number
t?: number
z?: number
}

export interface VivSettings {
selections?: VivChannelSelection[]
channelsVisible?: boolean[]
contrastLimits?: Array<[number, number]>
colors?: Array<[number, number, number]>
initialViewState?: {
target: [number, number, number]
zoom?: number
}
}

export default interface AppConfig {
/**
* Currently, only one server is supported. However, support for multiple
Expand Down Expand Up @@ -107,4 +126,6 @@ export default interface AppConfig {
enableInDevelopment?: boolean
}
enableMemoryMonitoring?: boolean
/** Optional display overrides for the Viv slide viewer at `/viv/...` routes (channels, contrast, etc.). */
vivSettings?: VivSettings
}
14 changes: 14 additions & 0 deletions src/DicomWebManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,20 @@ export default class DicomWebManager implements dwc.api.DICOMwebClient {
return this.stores[0].client.headers
}

/**
* Run a callback on the primary {@link dwc.api.DICOMwebClient} (the store used for reads).
* Used e.g. to append Viv tile {@link dwc.api.DICOMwebClientOptions.requestHooks} that must
* see the same XHR pipeline as {@link dmv.viewer.VolumeImageViewer}.
*/
applyToPrimaryDicomwebClient(
fn: (client: dwc.api.DICOMwebClient) => void,
): void {
if (this.stores.length === 0) {
return
}
fn(this.stores[0].client)
}

storeInstances = async (
options: dwc.api.StoreInstancesOptions,
): Promise<void> => {
Expand Down
Loading
Loading