Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
58 commits
Select commit Hold shift + click to select a range
c6741ed
Revision of orchestration engine
schote Sep 3, 2025
7f21a83
Fixed asset based dagster job definition
schote Sep 4, 2025
bc4f2db
Added dagster resource definitions to base image (can be used in mana…
schote Sep 4, 2025
9095bfd
Fixed dagster asset and job definitions for reco and calibration
schote Sep 4, 2025
d6ffedb
Added sensors to orchestration engine
schote Sep 4, 2025
7805cc0
Added comments to sensors
schote Sep 5, 2025
3b0191b
Documentation of orchestration engine
schote Sep 5, 2025
579138f
Fixed sensors
schote Sep 5, 2025
d5fa369
Fixed device parameter notifier endpoint
schote Sep 7, 2025
e8e4224
Added helper function to compare keys of json object
schote Oct 8, 2025
ecd8464
Editing of device parameter
schote Oct 8, 2025
4ae5084
Prefetch device status in status bar
schote Oct 8, 2025
7f7233c
Added state machine to device sdk
schote Oct 9, 2025
ec1fe29
device sdk client revision: split ws i/o and scan callback
schote Oct 9, 2025
700a28e
Improved ScanHub device communication resilience
schote Oct 10, 2025
e30c96a
Make results selectable in raw data viewer
schote Oct 10, 2025
8c47461
Reduce height of navigation bar
schote Oct 15, 2025
c171c42
Cornerstone4 upgrade, viewport grid
schote Oct 15, 2025
2687dba
Use single eneinge with mount/unmount for rendering
schote Oct 15, 2025
72efbff
Added viewport grid with synchronization and volume rendering
schote Oct 16, 2025
6a4d44b
Add some volume rendering code
schote Oct 16, 2025
b4dac74
Disable WS size limit
schote Oct 17, 2025
f81c421
Added calibration to acquisition task
schote Jan 14, 2026
eab7b02
Regenerated open api client
schote Jan 14, 2026
2bae88f
Added frontend calibration selector
schote Jan 14, 2026
16cc978
Added calibration list
schote Jan 16, 2026
1abccb9
Updated task info
schote Jan 16, 2026
f9b666a
Updated open api client
schote Jan 16, 2026
71e078c
Refactored task model, added calibration configuration
schote Jan 16, 2026
53263ab
Extended exam requests by get result
schote Jan 19, 2026
e04d91f
Improvement of dicom viewer
schote Jan 19, 2026
6a93ac3
Fixed check for calibrations
schote Jan 19, 2026
043683c
Improved dagster io handling
schote Jan 19, 2026
0cc2e6f
Added device status
schote Jan 19, 2026
8ee2eb2
Added manager status
schote Jan 19, 2026
6d9b228
Updated navigation, added library page
schote Jan 19, 2026
a3e1849
Generalized mrd provider
schote Jan 19, 2026
47205bc
Added data api
schote Jan 19, 2026
b284fd3
Added filename to raw data selection, api update
schote Jan 19, 2026
dfc3d4f
Updated dicom viewer layout grid
schote Jan 19, 2026
f2fdb7f
Added dicom download to viewer
schote Jan 19, 2026
79cc872
Fixed example device
schote Jan 19, 2026
d3d9b1c
Refinement of UI
schote Jan 19, 2026
ee8fc7e
Allow hosts
schote Jan 19, 2026
85f1313
Update urls
schote Jan 19, 2026
81e5852
Fixed viewer layouts
schote Jan 19, 2026
b64c20e
Update gitignore
schote Jan 19, 2026
e8487c5
Implemented xnat export
schote Jan 20, 2026
6391bcc
Added task ordering + drag and drop
schote Jan 20, 2026
3c85c26
Add mrd download
schote Jan 20, 2026
c2b6a99
Merge branch 'dagster-assets' of github.com:brain-link/scanhub into d…
schote Jan 22, 2026
c83acf4
Fixed frontend linter errors
schote Jan 22, 2026
ea1399b
Fixed exam manager linter findings
schote Jan 22, 2026
91c4acc
Fixed device manager linter findings
schote Jan 22, 2026
1bd46ff
Fixed user manager linter findings
schote Jan 22, 2026
74e00ae
Fixed linter findings in workflow manager
schote Jan 22, 2026
735650a
Enhance type hints for device state management and client operations,…
schote Jan 22, 2026
2ce9468
Updated readme
schote Jan 22, 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
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,8 @@ orthanc-db
*.pyc
*settings.json
*.seq
*.dcm
*.mrd
*/build
/infrastructure/monai/.md/*
.examples
Expand Down
7 changes: 4 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
<p align="center">
<a href="https://brain-link.de/wp-content/uploads/2022/03/ScanHub.svg"><img src="https://brain-link.de/wp-content/uploads/2022/03/ScanHub.svg" width="300" height="150" alt="ScanHub"></a>
<img src="scanhub-ui/src/media/logo.png" width="100" alt="ScanHub">
<h1 align="center">ScanHub</h3>
</p>

<p align="center">
Expand All @@ -15,9 +16,9 @@
</a>
</p>

---

# Scanhub

# About

ScanHub is intended to be a multi modal acquisition software, which allows individualizable, modular and cloud-based processing of functional and anatomical medical images.
It seamlessly merges the acquisition with the processing of complex data on a single platform.
Expand Down
109 changes: 60 additions & 49 deletions docker-compose.yml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
x-dagster-common: &dagster_common
image: scanhub-orchestration-engine:latest
pull_policy: never # Don't pull the image, use the local build
pull_policy: never # Don't pull the image, use the local build
build:
context: ./services/orchestration-engine
args:
Expand All @@ -27,18 +27,19 @@ services:
# Workflow manager, device manager and exam manager create data tables in postgres DB
workflow-manager:
container_name: workflow-manager
build:
build:
context: ./services/workflow-manager
args:
args:
- BASE_IMG=${SCANHUB_BASE_IMAGE}
volumes:
- ./services/workflow-manager/app/:/app/
- ${DATA_LAKE_DIR}:/data
environment:
DATA_LAKE_DIRECTORY: /data
SCANHUB_ALLOWED_ORIGINS: ${SCANHUB_ALLOWED_ORIGINS}
healthcheck:
# TODO: host.docker.internal is only working with docker desktop, must be replaced in product
test: ["CMD", "curl", "http://workflow-manager:8000/api/v1/workflowmanager/health/readiness"]
test: [ "CMD", "curl", "http://workflow-manager:8000/api/v1/workflowmanager/health/readiness" ]
interval: 5s
timeout: 5s
retries: 10
Expand All @@ -54,16 +55,17 @@ services:
container_name: device-manager
build:
context: ./services/device-manager
args:
args:
- BASE_IMG=${SCANHUB_BASE_IMAGE}
volumes:
- ./services/device-manager/app/:/app/
- ${DATA_LAKE_DIR}:/data
environment:
DATA_LAKE_DIRECTORY: /data
SCANHUB_ALLOWED_ORIGINS: ${SCANHUB_ALLOWED_ORIGINS}
healthcheck:
# TODO: host.docker.internal is only working with docker desktop, must be replaced in product
test: ["CMD", "curl", "http://device-manager:8000/api/v1/device/health/readiness"]
test: [ "CMD", "curl", "http://device-manager:8000/api/v1/device/health/readiness" ]
interval: 5s
timeout: 5s
retries: 10
Expand All @@ -77,16 +79,24 @@ services:

exam-manager:
container_name: exam-manager
build:
build:
context: ./services/exam-manager
args:
args:
- BASE_IMG=${SCANHUB_BASE_IMAGE}
volumes:
- ./services/exam-manager/app/:/app/
- ${DATA_LAKE_DIR}:/data
environment:
SCANHUB_ALLOWED_ORIGINS: ${SCANHUB_ALLOWED_ORIGINS}
XNAT_HOST: ${XNAT_HOST:-http://host.docker.internal:8081}
XNAT_USER: ${XNAT_USER:-admin}
XNAT_PASSWORD: ${XNAT_PASSWORD:-admin}
XNAT_PROJECT_ID: ${XNAT_PROJECT_ID:-A4IM}
extra_hosts:
- 'host.docker.internal:host-gateway'
healthcheck:
# TODO: host.docker.internal is only working with docker desktop, must be replaced in product
test: ["CMD", "curl", "http://exam-manager:8000/api/v1/exam/health/readiness"]
test: [ "CMD", "curl", "http://exam-manager:8000/api/v1/exam/health/readiness" ]
interval: 5s
timeout: 5s
retries: 10
Expand All @@ -108,15 +118,17 @@ services:

patient-manager:
container_name: patient-manager
build:
build:
context: ./services/patient-manager
args:
- BASE_IMG=${SCANHUB_BASE_IMAGE}
args:
- BASE_IMG=${SCANHUB_BASE_IMAGE}
volumes:
- ./services/patient-manager/app:/app/
environment:
SCANHUB_ALLOWED_ORIGINS: ${SCANHUB_ALLOWED_ORIGINS}
healthcheck:
# TODO: host.docker.internal is only working with docker desktop, must be replaced in product
test: ["CMD", "curl", "http://patient-manager:8100/api/v1/patient/health/readiness"]
test: [ "CMD", "curl", "http://patient-manager:8100/api/v1/patient/health/readiness" ]
interval: 5s
timeout: 5s
retries: 10
Expand All @@ -130,15 +142,17 @@ services:

user-login-manager:
container_name: user-login-manager
build:
build:
context: ./services/user-login-manager
args:
args:
- BASE_IMG=${SCANHUB_BASE_IMAGE}
volumes:
- ./services/user-login-manager/app/:/app/
environment:
SCANHUB_ALLOWED_ORIGINS: ${SCANHUB_ALLOWED_ORIGINS}
healthcheck:
# TODO: host.docker.internal is only working with docker desktop, must be replaced in product
test: ["CMD", "curl", "http://user-login-manager:8000/api/v1/userlogin/health/readiness"]
test: [ "CMD", "curl", "http://user-login-manager:8000/api/v1/userlogin/health/readiness" ]
interval: 5s
timeout: 5s
retries: 10
Expand Down Expand Up @@ -179,13 +193,13 @@ services:
POSTGRES_USER_FILE: /run/secrets/scanhub_database_postgres_user
POSTGRES_PASSWORD_FILE: /run/secrets/scanhub_database_postgres_password
POSTGRES_DB_FILE: /run/secrets/scanhub_database_postgres_db_name
healthcheck:
healthcheck:
# Workflow, device and exam microservices depend on healthy postgres database
test: ["CMD-SHELL", "pg_isready"]
test: [ "CMD-SHELL", "pg_isready" ]
interval: 5s
timeout: 5s
retries: 10
start_period: 5s
start_period: 5s
secrets:
- scanhub_database_postgres_user
- scanhub_database_postgres_password
Expand All @@ -202,9 +216,9 @@ services:
POSTGRES_USER_FILE: /run/secrets/patient_database_postgres_user
POSTGRES_PASSWORD_FILE: /run/secrets/patient_database_postgres_password
POSTGRES_DB_FILE: /run/secrets/patient_database_postgres_db_name
healthcheck:
healthcheck:
# init-db container is started, after health check passed
test: ["CMD-SHELL", "pg_isready", "-d", "db_prod"]
test: [ "CMD-SHELL", "pg_isready", "-d", "db_prod" ]
interval: 10s
timeout: 5s
retries: 5
Expand All @@ -214,7 +228,6 @@ services:
- patient_database_postgres_password
- patient_database_postgres_db_name


# MRI Sequence mongo database: Contains (pulseq) mri sequence files
sequence-database:
container_name: db-sequence
Expand All @@ -235,7 +248,6 @@ services:
- sequence_database_root_username
- sequence_database_root_password


# API Gateway
nginx:
container_name: api-gateway
Expand All @@ -250,7 +262,7 @@ services:
volumes:
- ./infrastructure/nginx_config.conf:/etc/nginx/conf.d/default.conf
healthcheck:
test: ["CMD", "curl", "http://api-gateway/health"]
test: [ "CMD", "curl", "http://api-gateway/health" ]
interval: 5s
timeout: 5s
retries: 10
Expand All @@ -259,7 +271,7 @@ services:
condition: service_healthy
workflow-manager:
condition: service_healthy
exam-manager:
exam-manager:
condition: service_healthy
patient-manager:
condition: service_healthy
Expand All @@ -269,37 +281,37 @@ services:
condition: service_healthy
# scanhub-ui:
# condition: service_healthy
extra_hosts: # somehow needed to find host.docker.internal under linux
- 'host.docker.internal:host-gateway' # for the access to host.docker.internal to work the firewall of the host needs to allow incoming connections
extra_hosts:
# somehow needed to find host.docker.internal under linux
- 'host.docker.internal:host-gateway' # for the access to host.docker.internal to work the firewall of the host needs to allow incoming connections
secrets:
- certificate
- privatekey


# FRONTEND/SCANHUB-UI
scanhub-ui:
container_name: scanhub-ui
build:
context: ./scanhub-ui
dockerfile: ./Dockerfile
args:
NODE_ENV: "production" # or "development"
volumes:
- ./scanhub-ui/src:/app/src
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:3000/"]
interval: 10s
timeout: 5s
retries: 10
start_period: 30s
# scanhub-ui:
# container_name: scanhub-ui
# build:
# context: ./scanhub-ui
# dockerfile: ./Dockerfile
# args:
# NODE_ENV: "production" # or "development"
# volumes:
# - ./scanhub-ui/src:/app/src
# healthcheck:
# test: ["CMD", "curl", "-f", "http://localhost:3000/"]
# interval: 10s
# timeout: 5s
# retries: 10
# start_period: 30s

# DAGSTER
dagster-webserver:
<<: *dagster_common
container_name: dagster-webserver
command: ["dagster-webserver", "-h", "0.0.0.0", "-p", "3000", "--path-prefix", "/dagster", "-w", "/opt/dagster/app/workspace.yaml"]
command: [ "dagster-webserver", "-h", "0.0.0.0", "-p", "3000", "--path-prefix", "/dagster", "-w", "/opt/dagster/app/workspace.yaml" ]
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:3000/dagster/server-status"]
test: [ "CMD", "curl", "-f", "http://localhost:3000/dagster/server-status" ]
interval: 10s
timeout: 5s
retries: 5
Expand All @@ -311,14 +323,13 @@ services:
<<: *dagster_common
container_name: dagster-daemon
user: "0:0"
entrypoint: ["dagster-daemon", "run", "-w", "/opt/dagster/app/workspace.yaml"]
entrypoint: [ "dagster-daemon", "run", "-w", "/opt/dagster/app/workspace.yaml" ]
healthcheck:
test: ["CMD", "pgrep", "-f", "dagster-daemon"]
test: [ "CMD", "pgrep", "-f", "dagster-daemon" ]
interval: 10s
timeout: 5s
retries: 5


volumes:
# data_lake: # Use bind-mount to inspect files, make sure data_lake/ exists at project root
mongodb_data_sequence:
Expand Down Expand Up @@ -347,4 +358,4 @@ secrets:
certificate:
file: secrets/certificate.pem
privatekey:
file: secrets/privatekey.pem
file: secrets/privatekey.pem
9 changes: 5 additions & 4 deletions scanhub-ui/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,16 +7,17 @@
"source": "index.html",
"dependencies": {
"@cornerstonejs/codec-charls": "^1.2.3",
"@cornerstonejs/codec-libjpeg-turbo-8bit": "1.2.2",
"@cornerstonejs/codec-libjpeg-turbo-8bit": "^1.2.2",
"@cornerstonejs/codec-openjpeg": "^1.2.4",
"@cornerstonejs/codec-openjph": "^2.4.7",
"@cornerstonejs/core": "^3.32.10",
"@cornerstonejs/dicom-image-loader": "^3.32.10",
"@cornerstonejs/tools": "^3.32.10",
"@cornerstonejs/core": "^4",
"@cornerstonejs/dicom-image-loader": "^4",
"@cornerstonejs/tools": "^4",
"@emotion/react": "^11.11.1",
"@emotion/styled": "^11.11.0",
"@fontsource/roboto": "^5.0.5",
"@kitware/vtk.js": "32.12.1",
"@monaco-editor/react": "^4.7.0",
"@mui/icons-material": "^7.1.1",
"@mui/joy": "^5.0.0-beta.52",
"@mui/material": "^7.1.1",
Expand Down
7 changes: 5 additions & 2 deletions scanhub-ui/src/Routes.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import TemplatesView from './pages/TemplatesView'
import UserManagementView from './pages/UserManagementView'
import SequenceView from './pages/SequenceView'
import DeviceView from './pages/DeviceView'
import LibraryView from './pages/Library';

const materialTheme = materialExtendTheme()

Expand All @@ -46,16 +47,18 @@ export function RouteConfiguration() {
'--Form-maxWidth': '700px',
'--Transition-duration': '0.4s', // set to `none` to disable transition
'--Sidebar-width': '360px',
'--Navigation-height': '60px',
'--Navigation-height': '50px',
'--Status-height': '22px',
'--IconFontSize': '22px',
},
}}
/>

<Routes>
<Route path='/' element={user ? <App /> : <Navigate to='/login' state={{from: location}} />}>
<Route path='/' element={user ? <App /> : <Navigate to='/login' state={{ from: location }} />}>
<Route index element={<PatientListView />} />
<Route path=':patientId' element={<AcquisitionView />} />
<Route path='/library' element={<LibraryView />} />
<Route path='/templates' element={<TemplatesView />} />
<Route path='/devices' element={<DeviceView />} />
<Route path='/sequences' element={<SequenceView />} />
Expand Down
3 changes: 2 additions & 1 deletion scanhub-ui/src/api.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
* api.tsx contains instances of the different apis.
*/
import { PatientsApi, HealthApi as PatientManagerHealthApi } from './openapi/generated-client/patient'
import { ExamsApi, WorkflowsApi, TasksApi, ResultsApi, MriSequencesApi, HealthApi as ExamManagerHealthApi } from './openapi/generated-client/exam'
import { ExamsApi, WorkflowsApi, TasksApi, ResultsApi, MriSequencesApi, DataApi, HealthApi as ExamManagerHealthApi } from './openapi/generated-client/exam'
import { WorkflowManagerApi, HealthApi as WorkflowManagerHealthApi } from './openapi/generated-client/workflowmanager'
import { UserApi, LoginApi, HealthApi as UserLoginManagerHealthApi } from './openapi/generated-client/userlogin'
import { DevicesApi, HealthApi as DeviceManagerHealthApi } from './openapi/generated-client/device'
Expand All @@ -20,6 +20,7 @@ export const workflowsApi = new WorkflowsApi(undefined, baseUrls.examService)
export const taskApi = new TasksApi(undefined, baseUrls.examService)
export const resultApi = new ResultsApi(undefined, baseUrls.examService)
export const sequenceApi = new MriSequencesApi(undefined, baseUrls.examService)
export const dataApi = new DataApi(undefined, baseUrls.examService)
export const examManagerHealthApi = new ExamManagerHealthApi(undefined, baseUrls.examService)

export const workflowManagerApi = new WorkflowManagerApi(undefined, baseUrls.workflowManagerService)
Expand Down
Loading
Loading