Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 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
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
298 changes: 292 additions & 6 deletions bun.lock

Large diffs are not rendered by default.

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
24 changes: 23 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -38,12 +38,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.21",
"dicomweb-client": "0.10.3",
"oidc-client": "^1.11.5",
"react": "^18.2.0",
"react-dom": "^18.2.0",
Expand Down Expand Up @@ -91,7 +105,15 @@
"typescript": "^4.7.4"
},
"overrides": {
"@types/minimatch": "5.1.2",
"@types/d3-dispatch": "3.0.6",
"@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",
"nth-check": "2.0.1",
"wrap-ansi": "7.0.0",
"make-dir": "3.1.0",
Expand Down
151 changes: 97 additions & 54 deletions src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
Navigate,
Route,
Routes,
useLocation,
useParams,
} from 'react-router-dom'

Expand Down Expand Up @@ -47,6 +48,8 @@ function ParametrizedCaseViewer({
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 @@ function ParametrizedCaseViewer({
app={app}
enableAnnotationTools={enableAnnotationTools}
studyInstanceUID={studyInstanceUID}
isVivRoute={isVivRoute}
vivSettings={config.vivSettings}
/>
</ValidationProvider>
)
Expand Down Expand Up @@ -462,6 +467,52 @@ class App extends React.Component<AppProps, AppState> {
this.signIn()
}

/** Shared layout for study viewer routes (classic and `/viv/...`). */
private renderCaseViewerShell(
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 @@ -564,63 +615,55 @@ class App extends React.Component<AppProps, AppState> {
/>
<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
19 changes: 19 additions & 0 deletions src/AppConfig.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,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 +124,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