├── .gitattributes
├── .gitignore
├── README.md
├── docker-compose.dev.yml
├── docker-compose.yml
├── images
├── .gitignore
├── patient1
│ ├── 000000.dcm
│ ├── 000001.dcm
│ ├── 000002.dcm
│ ├── 000003.dcm
│ ├── 000004.dcm
│ ├── 000005.dcm
│ ├── 000006.dcm
│ ├── 000007.dcm
│ ├── 000008.dcm
│ ├── 000009.dcm
│ ├── 000010.dcm
│ ├── 000011.dcm
│ ├── 000012.dcm
│ ├── 000013.dcm
│ ├── 000014.dcm
│ ├── 000015.dcm
│ ├── 000016.dcm
│ ├── 000017.dcm
│ ├── 000018.dcm
│ ├── 000019.dcm
│ ├── 000020.dcm
│ ├── 000021.dcm
│ ├── 000022.dcm
│ ├── 000023.dcm
│ ├── 000024.dcm
│ ├── 000025.dcm
│ ├── 000026.dcm
│ ├── 000027.dcm
│ ├── 000028.dcm
│ ├── 000029.dcm
│ ├── 000030.dcm
│ ├── 000031.dcm
│ ├── 000032.dcm
│ ├── 000033.dcm
│ ├── 000034.dcm
│ ├── 000035.dcm
│ ├── 000036.dcm
│ ├── 000037.dcm
│ ├── 000038.dcm
│ └── 000039.dcm
├── patient2
│ ├── 000000.dcm
│ ├── 000001.dcm
│ ├── 000002.dcm
│ ├── 000003.dcm
│ ├── 000004.dcm
│ ├── 000005.dcm
│ ├── 000006.dcm
│ ├── 000007.dcm
│ ├── 000008.dcm
│ ├── 000009.dcm
│ ├── 000010.dcm
│ ├── 000011.dcm
│ ├── 000012.dcm
│ ├── 000013.dcm
│ ├── 000014.dcm
│ ├── 000015.dcm
│ ├── 000016.dcm
│ ├── 000017.dcm
│ ├── 000018.dcm
│ ├── 000019.dcm
│ ├── 000020.dcm
│ ├── 000021.dcm
│ ├── 000022.dcm
│ ├── 000023.dcm
│ ├── 000024.dcm
│ ├── 000025.dcm
│ ├── 000026.dcm
│ ├── 000027.dcm
│ ├── 000028.dcm
│ ├── 000029.dcm
│ ├── 000030.dcm
│ ├── 000031.dcm
│ ├── 000032.dcm
│ ├── 000033.dcm
│ ├── 000034.dcm
│ ├── 000035.dcm
│ ├── 000036.dcm
│ ├── 000037.dcm
│ ├── 000038.dcm
│ ├── 000039.dcm
│ ├── 000040.dcm
│ ├── 000041.dcm
│ ├── 000042.dcm
│ ├── 000043.dcm
│ ├── 000044.dcm
│ ├── 000045.dcm
│ ├── 000046.dcm
│ ├── 000047.dcm
│ ├── 000048.dcm
│ ├── 000049.dcm
│ ├── 000050.dcm
│ ├── 000051.dcm
│ ├── 000052.dcm
│ ├── 000053.dcm
│ ├── 000054.dcm
│ ├── 000055.dcm
│ ├── 000056.dcm
│ ├── 000057.dcm
│ ├── 000058.dcm
│ └── 000059.dcm
└── patient3
│ ├── 000000.dcm
│ ├── 000001.dcm
│ ├── 000002.dcm
│ ├── 000003.dcm
│ └── 000004.dcm
├── install_and_run.sh
├── ndicom_client
├── .gitignore
├── Dockerfile
├── README.md
├── build
│ ├── asset-manifest.json
│ ├── favicon.ico
│ ├── index.html
│ ├── manifest.json
│ ├── service-worker.js
│ └── static
│ │ ├── css
│ │ ├── main.f32fca34.css
│ │ └── main.f32fca34.css.map
│ │ └── media
│ │ ├── flags.9c74e172.png
│ │ ├── icons.263bfe56.eot
│ │ ├── icons.27c88389.woff2
│ │ ├── icons.7bc63d50.woff
│ │ ├── icons.b42b4467.svg
│ │ └── icons.d3490a32.ttf
├── package-lock.json
├── package.json
├── public
│ ├── favicon.ico
│ ├── index.html
│ └── manifest.json
└── src
│ ├── App.css
│ ├── App.js
│ ├── components
│ ├── common
│ │ ├── DicomViewer.css
│ │ ├── DicomViewer.js
│ │ └── MenuContainer.js
│ ├── dicomNodesPage
│ │ └── EchoButton.js
│ ├── pluginsPage
│ │ └── PluginItem.js
│ ├── processingPage
│ │ ├── BlendParamsDialog.js
│ │ ├── ControlPanel.js
│ │ ├── Dialog.css
│ │ ├── DicomViewer.css
│ │ ├── DicomViewer.js
│ │ └── ParamsDialog.js
│ └── seriesViewerPage
│ │ └── ControlPanel.js
│ ├── index.css
│ ├── index.js
│ ├── pages
│ ├── DicomNodesPage.js
│ ├── LoginPage.js
│ ├── PatientStudies.js
│ ├── PatientsPage.js
│ ├── PluginsPage.js
│ ├── ProcessingPage.js
│ ├── SeriesViewerPage.js
│ ├── StudiesPage.js
│ ├── StudySeriesPage.js
│ ├── UploadDicomPage.js
│ └── series_viewer_page.css
│ ├── registerServiceWorker.js
│ └── services
│ ├── DicomNodeService.js
│ ├── DicomService.js
│ └── PluginsService.js
├── ndicom_server
├── Dockerfile
├── app.py
├── apps
│ ├── __init__.py
│ ├── core
│ │ ├── __init__.py
│ │ ├── admin.py
│ │ ├── apps.py
│ │ ├── fields.py
│ │ ├── handlers.py
│ │ ├── management
│ │ │ ├── __init__.py
│ │ │ └── commands
│ │ │ │ ├── __init__.py
│ │ │ │ ├── clear_dicom.py
│ │ │ │ ├── clear_media.py
│ │ │ │ ├── clear_plugins.py
│ │ │ │ ├── install_plugins.py
│ │ │ │ ├── list_plugins.py
│ │ │ │ ├── plugins.py
│ │ │ │ ├── store_dicom.py
│ │ │ │ ├── store_plugins.py
│ │ │ │ └── uninstall_plugins.py
│ │ ├── managers.py
│ │ ├── migrations
│ │ │ ├── 0001_initial.py
│ │ │ ├── 0002_auto_20180608_1947.py
│ │ │ ├── 0003_auto_20180608_1951.py
│ │ │ ├── 0004_auto_20180608_2059.py
│ │ │ └── __init__.py
│ │ ├── models.py
│ │ ├── templates
│ │ │ └── core
│ │ │ │ └── index.html
│ │ ├── tests.py
│ │ ├── utils.py
│ │ └── views.py
│ ├── dicom_ws
│ │ ├── __init__.py
│ │ ├── admin.py
│ │ ├── apps.py
│ │ ├── handlers.py
│ │ ├── migrations
│ │ │ └── __init__.py
│ │ ├── models.py
│ │ ├── serializers.py
│ │ ├── tests.py
│ │ ├── urls.py
│ │ └── views.py
│ └── users
│ │ ├── __init__.py
│ │ ├── admin.py
│ │ ├── apps.py
│ │ ├── handlers.py
│ │ ├── migrations
│ │ └── __init__.py
│ │ ├── models.py
│ │ ├── serializers.py
│ │ ├── tests.py
│ │ └── views.py
├── examples
│ └── plugins
│ │ └── dicom_pixels
│ │ └── plugin.py
├── install_and_run.sh
├── manage.py
├── neurdicom
│ ├── __init__.py
│ ├── settings.py
│ ├── urls.py
│ └── wsgi.py
├── notebooks
│ ├── Brain.ipynb
│ ├── Untitled.ipynb
│ └── brain.dcm
├── requirements.txt
└── run.sh
├── neurdicom.conf
└── neurdicom.dev.conf
/.gitattributes:
--------------------------------------------------------------------------------
1 | * linguist-vendored
2 | *.py linguist-vendored=false
3 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | .idea
3 |
--------------------------------------------------------------------------------
/docker-compose.dev.yml:
--------------------------------------------------------------------------------
1 | version: '2'
2 | services:
3 | postgres:
4 | image: postgres:latest
5 | volumes:
6 | - pg-data:/var/lib/postgresql/data
7 | environment:
8 | - POSTGRES_USER=neurdicom
9 | - POSTGRES_PASSWORD=neurdicom
10 | - POSTGRES_DB=neurdicom
11 | ports:
12 | - "5432:5432"
13 | api:
14 | build: ./ndicom_server
15 | command: [sh, -c, "python ./app.py --rest_port=8080", "python ./app.py --rest_port=8081"]
16 | volumes:
17 | # - ./ndicom_server:/app
18 | - ./images:/images
19 | environment:
20 | - DB_NAME=neurdicom
21 | - DB_USER=neurdicom
22 | - DB_PASSWORD=neurdicom
23 | - DB_HOST=postgres
24 | - DB_PORT=5432
25 | depends_on:
26 | - postgres
27 | ports:
28 | - "8080:8080"
29 | - "8081:8081"
30 | - "8082:8082"
31 | - "8083:8083"
32 | # front:
33 | # build: ./ndicom_client
34 | # # command: npm start
35 | # volumes:
36 | # - ./ndicom_client:/app
37 | # - node-modules:/app/node_modules
38 | # environment:
39 | # - API_HOST=api
40 | # - API_URL_BROWSER=/api
41 | # - API_URL=http://api:3000/
42 | # - API_PORT=8080
43 | # ports:
44 | # - "3000:3000"
45 | nginx:
46 | image: nginx
47 | depends_on:
48 | - api
49 | # - front
50 | ports:
51 | - "80:80"
52 | volumes:
53 | - ./neurdicom.dev.conf:/etc/nginx/conf.d/default.conf
54 | - ./ndicom_client/build:/mnt/front
55 | expose:
56 | - "80"
57 | volumes:
58 | pg-data:
59 | # node-modules:
60 |
--------------------------------------------------------------------------------
/docker-compose.yml:
--------------------------------------------------------------------------------
1 | version: '2'
2 | services:
3 | postgres:
4 | image: postgres:latest
5 | volumes:
6 | - pg-data:/var/lib/postgresql/data
7 | environment:
8 | - POSTGRES_USER=neurdicom
9 | - POSTGRES_PASSWORD=neurdicom
10 | - POSTGRES_DB=neurdicom
11 | ports:
12 | - "5432:5432"
13 | api:
14 | build: ./ndicom_server
15 | command: python ./app.py
16 | volumes:
17 | # - ./ndicom_server:/app
18 | - ./images:/images
19 | environment:
20 | - DB_NAME=neurdicom
21 | - DB_USER=neurdicom
22 | - DB_PASSWORD=neurdicom
23 | - DB_HOST=postgres
24 | - DB_PORT=5432
25 | depends_on:
26 | - postgres
27 | ports:
28 | - "8080:8080"
29 | - "4242:4242"
30 | # front:
31 | # build: ./ndicom_client
32 | # # command: npm start
33 | # volumes:
34 | # - ./ndicom_client:/app
35 | # - node-modules:/app/node_modules
36 | # environment:
37 | # - API_HOST=api
38 | # - API_URL_BROWSER=/api
39 | # - API_URL=http://api:3000/
40 | # - API_PORT=8080
41 | # ports:
42 | # - "3000:3000"
43 | nginx:
44 | image: nginx
45 | depends_on:
46 | - api
47 | # - front
48 | ports:
49 | - "80:80"
50 | volumes:
51 | - ./neurdicom.conf:/etc/nginx/conf.d/default.conf
52 | - ./ndicom_client/build:/mnt/front
53 | expose:
54 | - "80"
55 | volumes:
56 | pg-data:
57 | # node-modules:
58 |
--------------------------------------------------------------------------------
/images/.gitignore:
--------------------------------------------------------------------------------
1 | # Created by .ignore support plugin (hsz.mobi)
2 | ### macOS template
3 | # General
4 | .DS_Store
5 | .AppleDouble
6 | .LSOverride
7 |
8 | # Icon must end with two \r
9 | Icon
10 |
11 | # Thumbnails
12 | ._*
13 |
14 | # Files that might appear in the root of a volume
15 | .DocumentRevisions-V100
16 | .fseventsd
17 | .Spotlight-V100
18 | .TemporaryItems
19 | .Trashes
20 | .VolumeIcon.icns
21 | .com.apple.timemachine.donotpresent
22 |
23 | # Directories potentially created on remote AFP share
24 | .AppleDB
25 | .AppleDesktop
26 | Network Trash Folder
27 | Temporary Items
28 | .apdisk
29 | orthanc/
30 | dicom/
--------------------------------------------------------------------------------
/images/patient1/000000.dcm:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/reactmed/neurdicom/1b3f659ebd4679a913d9ec95bf28e55c059696f1/images/patient1/000000.dcm
--------------------------------------------------------------------------------
/images/patient1/000001.dcm:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/reactmed/neurdicom/1b3f659ebd4679a913d9ec95bf28e55c059696f1/images/patient1/000001.dcm
--------------------------------------------------------------------------------
/images/patient1/000002.dcm:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/reactmed/neurdicom/1b3f659ebd4679a913d9ec95bf28e55c059696f1/images/patient1/000002.dcm
--------------------------------------------------------------------------------
/images/patient1/000003.dcm:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/reactmed/neurdicom/1b3f659ebd4679a913d9ec95bf28e55c059696f1/images/patient1/000003.dcm
--------------------------------------------------------------------------------
/images/patient1/000004.dcm:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/reactmed/neurdicom/1b3f659ebd4679a913d9ec95bf28e55c059696f1/images/patient1/000004.dcm
--------------------------------------------------------------------------------
/images/patient1/000005.dcm:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/reactmed/neurdicom/1b3f659ebd4679a913d9ec95bf28e55c059696f1/images/patient1/000005.dcm
--------------------------------------------------------------------------------
/images/patient1/000006.dcm:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/reactmed/neurdicom/1b3f659ebd4679a913d9ec95bf28e55c059696f1/images/patient1/000006.dcm
--------------------------------------------------------------------------------
/images/patient1/000007.dcm:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/reactmed/neurdicom/1b3f659ebd4679a913d9ec95bf28e55c059696f1/images/patient1/000007.dcm
--------------------------------------------------------------------------------
/images/patient1/000008.dcm:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/reactmed/neurdicom/1b3f659ebd4679a913d9ec95bf28e55c059696f1/images/patient1/000008.dcm
--------------------------------------------------------------------------------
/images/patient1/000009.dcm:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/reactmed/neurdicom/1b3f659ebd4679a913d9ec95bf28e55c059696f1/images/patient1/000009.dcm
--------------------------------------------------------------------------------
/images/patient1/000010.dcm:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/reactmed/neurdicom/1b3f659ebd4679a913d9ec95bf28e55c059696f1/images/patient1/000010.dcm
--------------------------------------------------------------------------------
/images/patient1/000011.dcm:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/reactmed/neurdicom/1b3f659ebd4679a913d9ec95bf28e55c059696f1/images/patient1/000011.dcm
--------------------------------------------------------------------------------
/images/patient1/000012.dcm:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/reactmed/neurdicom/1b3f659ebd4679a913d9ec95bf28e55c059696f1/images/patient1/000012.dcm
--------------------------------------------------------------------------------
/images/patient1/000013.dcm:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/reactmed/neurdicom/1b3f659ebd4679a913d9ec95bf28e55c059696f1/images/patient1/000013.dcm
--------------------------------------------------------------------------------
/images/patient1/000014.dcm:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/reactmed/neurdicom/1b3f659ebd4679a913d9ec95bf28e55c059696f1/images/patient1/000014.dcm
--------------------------------------------------------------------------------
/images/patient1/000015.dcm:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/reactmed/neurdicom/1b3f659ebd4679a913d9ec95bf28e55c059696f1/images/patient1/000015.dcm
--------------------------------------------------------------------------------
/images/patient1/000016.dcm:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/reactmed/neurdicom/1b3f659ebd4679a913d9ec95bf28e55c059696f1/images/patient1/000016.dcm
--------------------------------------------------------------------------------
/images/patient1/000017.dcm:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/reactmed/neurdicom/1b3f659ebd4679a913d9ec95bf28e55c059696f1/images/patient1/000017.dcm
--------------------------------------------------------------------------------
/images/patient1/000018.dcm:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/reactmed/neurdicom/1b3f659ebd4679a913d9ec95bf28e55c059696f1/images/patient1/000018.dcm
--------------------------------------------------------------------------------
/images/patient1/000019.dcm:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/reactmed/neurdicom/1b3f659ebd4679a913d9ec95bf28e55c059696f1/images/patient1/000019.dcm
--------------------------------------------------------------------------------
/images/patient1/000020.dcm:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/reactmed/neurdicom/1b3f659ebd4679a913d9ec95bf28e55c059696f1/images/patient1/000020.dcm
--------------------------------------------------------------------------------
/images/patient1/000021.dcm:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/reactmed/neurdicom/1b3f659ebd4679a913d9ec95bf28e55c059696f1/images/patient1/000021.dcm
--------------------------------------------------------------------------------
/images/patient1/000022.dcm:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/reactmed/neurdicom/1b3f659ebd4679a913d9ec95bf28e55c059696f1/images/patient1/000022.dcm
--------------------------------------------------------------------------------
/images/patient1/000023.dcm:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/reactmed/neurdicom/1b3f659ebd4679a913d9ec95bf28e55c059696f1/images/patient1/000023.dcm
--------------------------------------------------------------------------------
/images/patient1/000024.dcm:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/reactmed/neurdicom/1b3f659ebd4679a913d9ec95bf28e55c059696f1/images/patient1/000024.dcm
--------------------------------------------------------------------------------
/images/patient1/000025.dcm:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/reactmed/neurdicom/1b3f659ebd4679a913d9ec95bf28e55c059696f1/images/patient1/000025.dcm
--------------------------------------------------------------------------------
/images/patient1/000026.dcm:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/reactmed/neurdicom/1b3f659ebd4679a913d9ec95bf28e55c059696f1/images/patient1/000026.dcm
--------------------------------------------------------------------------------
/images/patient1/000027.dcm:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/reactmed/neurdicom/1b3f659ebd4679a913d9ec95bf28e55c059696f1/images/patient1/000027.dcm
--------------------------------------------------------------------------------
/images/patient1/000028.dcm:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/reactmed/neurdicom/1b3f659ebd4679a913d9ec95bf28e55c059696f1/images/patient1/000028.dcm
--------------------------------------------------------------------------------
/images/patient1/000029.dcm:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/reactmed/neurdicom/1b3f659ebd4679a913d9ec95bf28e55c059696f1/images/patient1/000029.dcm
--------------------------------------------------------------------------------
/images/patient1/000030.dcm:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/reactmed/neurdicom/1b3f659ebd4679a913d9ec95bf28e55c059696f1/images/patient1/000030.dcm
--------------------------------------------------------------------------------
/images/patient1/000031.dcm:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/reactmed/neurdicom/1b3f659ebd4679a913d9ec95bf28e55c059696f1/images/patient1/000031.dcm
--------------------------------------------------------------------------------
/images/patient1/000032.dcm:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/reactmed/neurdicom/1b3f659ebd4679a913d9ec95bf28e55c059696f1/images/patient1/000032.dcm
--------------------------------------------------------------------------------
/images/patient1/000033.dcm:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/reactmed/neurdicom/1b3f659ebd4679a913d9ec95bf28e55c059696f1/images/patient1/000033.dcm
--------------------------------------------------------------------------------
/images/patient1/000034.dcm:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/reactmed/neurdicom/1b3f659ebd4679a913d9ec95bf28e55c059696f1/images/patient1/000034.dcm
--------------------------------------------------------------------------------
/images/patient1/000035.dcm:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/reactmed/neurdicom/1b3f659ebd4679a913d9ec95bf28e55c059696f1/images/patient1/000035.dcm
--------------------------------------------------------------------------------
/images/patient1/000036.dcm:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/reactmed/neurdicom/1b3f659ebd4679a913d9ec95bf28e55c059696f1/images/patient1/000036.dcm
--------------------------------------------------------------------------------
/images/patient1/000037.dcm:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/reactmed/neurdicom/1b3f659ebd4679a913d9ec95bf28e55c059696f1/images/patient1/000037.dcm
--------------------------------------------------------------------------------
/images/patient1/000038.dcm:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/reactmed/neurdicom/1b3f659ebd4679a913d9ec95bf28e55c059696f1/images/patient1/000038.dcm
--------------------------------------------------------------------------------
/images/patient1/000039.dcm:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/reactmed/neurdicom/1b3f659ebd4679a913d9ec95bf28e55c059696f1/images/patient1/000039.dcm
--------------------------------------------------------------------------------
/images/patient2/000000.dcm:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/reactmed/neurdicom/1b3f659ebd4679a913d9ec95bf28e55c059696f1/images/patient2/000000.dcm
--------------------------------------------------------------------------------
/images/patient2/000001.dcm:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/reactmed/neurdicom/1b3f659ebd4679a913d9ec95bf28e55c059696f1/images/patient2/000001.dcm
--------------------------------------------------------------------------------
/images/patient2/000002.dcm:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/reactmed/neurdicom/1b3f659ebd4679a913d9ec95bf28e55c059696f1/images/patient2/000002.dcm
--------------------------------------------------------------------------------
/images/patient2/000003.dcm:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/reactmed/neurdicom/1b3f659ebd4679a913d9ec95bf28e55c059696f1/images/patient2/000003.dcm
--------------------------------------------------------------------------------
/images/patient2/000004.dcm:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/reactmed/neurdicom/1b3f659ebd4679a913d9ec95bf28e55c059696f1/images/patient2/000004.dcm
--------------------------------------------------------------------------------
/images/patient2/000005.dcm:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/reactmed/neurdicom/1b3f659ebd4679a913d9ec95bf28e55c059696f1/images/patient2/000005.dcm
--------------------------------------------------------------------------------
/images/patient2/000006.dcm:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/reactmed/neurdicom/1b3f659ebd4679a913d9ec95bf28e55c059696f1/images/patient2/000006.dcm
--------------------------------------------------------------------------------
/images/patient2/000007.dcm:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/reactmed/neurdicom/1b3f659ebd4679a913d9ec95bf28e55c059696f1/images/patient2/000007.dcm
--------------------------------------------------------------------------------
/images/patient2/000008.dcm:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/reactmed/neurdicom/1b3f659ebd4679a913d9ec95bf28e55c059696f1/images/patient2/000008.dcm
--------------------------------------------------------------------------------
/images/patient2/000009.dcm:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/reactmed/neurdicom/1b3f659ebd4679a913d9ec95bf28e55c059696f1/images/patient2/000009.dcm
--------------------------------------------------------------------------------
/images/patient2/000010.dcm:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/reactmed/neurdicom/1b3f659ebd4679a913d9ec95bf28e55c059696f1/images/patient2/000010.dcm
--------------------------------------------------------------------------------
/images/patient2/000011.dcm:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/reactmed/neurdicom/1b3f659ebd4679a913d9ec95bf28e55c059696f1/images/patient2/000011.dcm
--------------------------------------------------------------------------------
/images/patient2/000012.dcm:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/reactmed/neurdicom/1b3f659ebd4679a913d9ec95bf28e55c059696f1/images/patient2/000012.dcm
--------------------------------------------------------------------------------
/images/patient2/000013.dcm:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/reactmed/neurdicom/1b3f659ebd4679a913d9ec95bf28e55c059696f1/images/patient2/000013.dcm
--------------------------------------------------------------------------------
/images/patient2/000014.dcm:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/reactmed/neurdicom/1b3f659ebd4679a913d9ec95bf28e55c059696f1/images/patient2/000014.dcm
--------------------------------------------------------------------------------
/images/patient2/000015.dcm:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/reactmed/neurdicom/1b3f659ebd4679a913d9ec95bf28e55c059696f1/images/patient2/000015.dcm
--------------------------------------------------------------------------------
/images/patient2/000016.dcm:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/reactmed/neurdicom/1b3f659ebd4679a913d9ec95bf28e55c059696f1/images/patient2/000016.dcm
--------------------------------------------------------------------------------
/images/patient2/000017.dcm:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/reactmed/neurdicom/1b3f659ebd4679a913d9ec95bf28e55c059696f1/images/patient2/000017.dcm
--------------------------------------------------------------------------------
/images/patient2/000018.dcm:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/reactmed/neurdicom/1b3f659ebd4679a913d9ec95bf28e55c059696f1/images/patient2/000018.dcm
--------------------------------------------------------------------------------
/images/patient2/000019.dcm:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/reactmed/neurdicom/1b3f659ebd4679a913d9ec95bf28e55c059696f1/images/patient2/000019.dcm
--------------------------------------------------------------------------------
/images/patient2/000020.dcm:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/reactmed/neurdicom/1b3f659ebd4679a913d9ec95bf28e55c059696f1/images/patient2/000020.dcm
--------------------------------------------------------------------------------
/images/patient2/000021.dcm:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/reactmed/neurdicom/1b3f659ebd4679a913d9ec95bf28e55c059696f1/images/patient2/000021.dcm
--------------------------------------------------------------------------------
/images/patient2/000022.dcm:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/reactmed/neurdicom/1b3f659ebd4679a913d9ec95bf28e55c059696f1/images/patient2/000022.dcm
--------------------------------------------------------------------------------
/images/patient2/000023.dcm:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/reactmed/neurdicom/1b3f659ebd4679a913d9ec95bf28e55c059696f1/images/patient2/000023.dcm
--------------------------------------------------------------------------------
/images/patient2/000024.dcm:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/reactmed/neurdicom/1b3f659ebd4679a913d9ec95bf28e55c059696f1/images/patient2/000024.dcm
--------------------------------------------------------------------------------
/images/patient2/000025.dcm:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/reactmed/neurdicom/1b3f659ebd4679a913d9ec95bf28e55c059696f1/images/patient2/000025.dcm
--------------------------------------------------------------------------------
/images/patient2/000026.dcm:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/reactmed/neurdicom/1b3f659ebd4679a913d9ec95bf28e55c059696f1/images/patient2/000026.dcm
--------------------------------------------------------------------------------
/images/patient2/000027.dcm:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/reactmed/neurdicom/1b3f659ebd4679a913d9ec95bf28e55c059696f1/images/patient2/000027.dcm
--------------------------------------------------------------------------------
/images/patient2/000028.dcm:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/reactmed/neurdicom/1b3f659ebd4679a913d9ec95bf28e55c059696f1/images/patient2/000028.dcm
--------------------------------------------------------------------------------
/images/patient2/000029.dcm:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/reactmed/neurdicom/1b3f659ebd4679a913d9ec95bf28e55c059696f1/images/patient2/000029.dcm
--------------------------------------------------------------------------------
/images/patient2/000030.dcm:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/reactmed/neurdicom/1b3f659ebd4679a913d9ec95bf28e55c059696f1/images/patient2/000030.dcm
--------------------------------------------------------------------------------
/images/patient2/000031.dcm:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/reactmed/neurdicom/1b3f659ebd4679a913d9ec95bf28e55c059696f1/images/patient2/000031.dcm
--------------------------------------------------------------------------------
/images/patient2/000032.dcm:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/reactmed/neurdicom/1b3f659ebd4679a913d9ec95bf28e55c059696f1/images/patient2/000032.dcm
--------------------------------------------------------------------------------
/images/patient2/000033.dcm:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/reactmed/neurdicom/1b3f659ebd4679a913d9ec95bf28e55c059696f1/images/patient2/000033.dcm
--------------------------------------------------------------------------------
/images/patient2/000034.dcm:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/reactmed/neurdicom/1b3f659ebd4679a913d9ec95bf28e55c059696f1/images/patient2/000034.dcm
--------------------------------------------------------------------------------
/images/patient2/000035.dcm:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/reactmed/neurdicom/1b3f659ebd4679a913d9ec95bf28e55c059696f1/images/patient2/000035.dcm
--------------------------------------------------------------------------------
/images/patient2/000036.dcm:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/reactmed/neurdicom/1b3f659ebd4679a913d9ec95bf28e55c059696f1/images/patient2/000036.dcm
--------------------------------------------------------------------------------
/images/patient2/000037.dcm:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/reactmed/neurdicom/1b3f659ebd4679a913d9ec95bf28e55c059696f1/images/patient2/000037.dcm
--------------------------------------------------------------------------------
/images/patient2/000038.dcm:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/reactmed/neurdicom/1b3f659ebd4679a913d9ec95bf28e55c059696f1/images/patient2/000038.dcm
--------------------------------------------------------------------------------
/images/patient2/000039.dcm:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/reactmed/neurdicom/1b3f659ebd4679a913d9ec95bf28e55c059696f1/images/patient2/000039.dcm
--------------------------------------------------------------------------------
/images/patient2/000040.dcm:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/reactmed/neurdicom/1b3f659ebd4679a913d9ec95bf28e55c059696f1/images/patient2/000040.dcm
--------------------------------------------------------------------------------
/images/patient2/000041.dcm:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/reactmed/neurdicom/1b3f659ebd4679a913d9ec95bf28e55c059696f1/images/patient2/000041.dcm
--------------------------------------------------------------------------------
/images/patient2/000042.dcm:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/reactmed/neurdicom/1b3f659ebd4679a913d9ec95bf28e55c059696f1/images/patient2/000042.dcm
--------------------------------------------------------------------------------
/images/patient2/000043.dcm:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/reactmed/neurdicom/1b3f659ebd4679a913d9ec95bf28e55c059696f1/images/patient2/000043.dcm
--------------------------------------------------------------------------------
/images/patient2/000044.dcm:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/reactmed/neurdicom/1b3f659ebd4679a913d9ec95bf28e55c059696f1/images/patient2/000044.dcm
--------------------------------------------------------------------------------
/images/patient2/000045.dcm:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/reactmed/neurdicom/1b3f659ebd4679a913d9ec95bf28e55c059696f1/images/patient2/000045.dcm
--------------------------------------------------------------------------------
/images/patient2/000046.dcm:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/reactmed/neurdicom/1b3f659ebd4679a913d9ec95bf28e55c059696f1/images/patient2/000046.dcm
--------------------------------------------------------------------------------
/images/patient2/000047.dcm:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/reactmed/neurdicom/1b3f659ebd4679a913d9ec95bf28e55c059696f1/images/patient2/000047.dcm
--------------------------------------------------------------------------------
/images/patient2/000048.dcm:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/reactmed/neurdicom/1b3f659ebd4679a913d9ec95bf28e55c059696f1/images/patient2/000048.dcm
--------------------------------------------------------------------------------
/images/patient2/000049.dcm:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/reactmed/neurdicom/1b3f659ebd4679a913d9ec95bf28e55c059696f1/images/patient2/000049.dcm
--------------------------------------------------------------------------------
/images/patient2/000050.dcm:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/reactmed/neurdicom/1b3f659ebd4679a913d9ec95bf28e55c059696f1/images/patient2/000050.dcm
--------------------------------------------------------------------------------
/images/patient2/000051.dcm:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/reactmed/neurdicom/1b3f659ebd4679a913d9ec95bf28e55c059696f1/images/patient2/000051.dcm
--------------------------------------------------------------------------------
/images/patient2/000052.dcm:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/reactmed/neurdicom/1b3f659ebd4679a913d9ec95bf28e55c059696f1/images/patient2/000052.dcm
--------------------------------------------------------------------------------
/images/patient2/000053.dcm:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/reactmed/neurdicom/1b3f659ebd4679a913d9ec95bf28e55c059696f1/images/patient2/000053.dcm
--------------------------------------------------------------------------------
/images/patient2/000054.dcm:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/reactmed/neurdicom/1b3f659ebd4679a913d9ec95bf28e55c059696f1/images/patient2/000054.dcm
--------------------------------------------------------------------------------
/images/patient2/000055.dcm:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/reactmed/neurdicom/1b3f659ebd4679a913d9ec95bf28e55c059696f1/images/patient2/000055.dcm
--------------------------------------------------------------------------------
/images/patient2/000056.dcm:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/reactmed/neurdicom/1b3f659ebd4679a913d9ec95bf28e55c059696f1/images/patient2/000056.dcm
--------------------------------------------------------------------------------
/images/patient2/000057.dcm:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/reactmed/neurdicom/1b3f659ebd4679a913d9ec95bf28e55c059696f1/images/patient2/000057.dcm
--------------------------------------------------------------------------------
/images/patient2/000058.dcm:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/reactmed/neurdicom/1b3f659ebd4679a913d9ec95bf28e55c059696f1/images/patient2/000058.dcm
--------------------------------------------------------------------------------
/images/patient2/000059.dcm:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/reactmed/neurdicom/1b3f659ebd4679a913d9ec95bf28e55c059696f1/images/patient2/000059.dcm
--------------------------------------------------------------------------------
/images/patient3/000000.dcm:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/reactmed/neurdicom/1b3f659ebd4679a913d9ec95bf28e55c059696f1/images/patient3/000000.dcm
--------------------------------------------------------------------------------
/images/patient3/000001.dcm:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/reactmed/neurdicom/1b3f659ebd4679a913d9ec95bf28e55c059696f1/images/patient3/000001.dcm
--------------------------------------------------------------------------------
/images/patient3/000002.dcm:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/reactmed/neurdicom/1b3f659ebd4679a913d9ec95bf28e55c059696f1/images/patient3/000002.dcm
--------------------------------------------------------------------------------
/images/patient3/000003.dcm:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/reactmed/neurdicom/1b3f659ebd4679a913d9ec95bf28e55c059696f1/images/patient3/000003.dcm
--------------------------------------------------------------------------------
/images/patient3/000004.dcm:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/reactmed/neurdicom/1b3f659ebd4679a913d9ec95bf28e55c059696f1/images/patient3/000004.dcm
--------------------------------------------------------------------------------
/install_and_run.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 | RED='\033[1;31m'
3 | GREEN='\033[1;32m'
4 | YELLOW='\033[1;33m'
5 | BLUE='\033[1;34m'
6 | CYAN='\033[1;36m'
7 | NC='\033[0m'
8 |
9 | OS="$(uname)"
10 |
11 | logInfo(){
12 | echo -e "${CYAN}`date "+%Y-%m-%d %H:%M:%S"`${NC} ${BLUE}[INFO]${NC}: $1"
13 | }
14 |
15 | logInfoStep(){
16 | echo -e "${CYAN}`date "+%Y-%m-%d %H:%M:%S"`${NC} ${BLUE}[INFO]${NC}: $1 ${GREEN}[OK]${NC}"
17 | }
18 |
19 | logErr(){
20 | echo -e "${CYAN}`date "+%Y-%m-%d %H:%M:%S"`${NC} ${RED}[ERR]${NC}: $1"
21 | }
22 |
23 | logWarn(){
24 | echo -e "${CYAN}`date "+%Y-%m-%d %H:%M:%S"`${NC} ${YELLOW}[WARN]${NC}: $1"
25 | }
26 | #
27 | # buildAndRun(){
28 | # if [[ $1 -eq 1]]; then
29 | # docker-compose up --build
30 | # else
31 | # docker-compose up
32 | # fi
33 | # }
34 |
35 | # buildAndRunWithCUDA(){
36 | #
37 | # if [[ $1 -eq 1]]; then
38 | # mkdir venv
39 | # virtualenv venv --no-site-packages
40 | # logInfo "Activate virtual env"
41 | # source venv/bin/activate
42 | #
43 | # logInfo "Install requirements"
44 | # pip install -r requirements.txt
45 | # pip install git+git://github.com/pydicom/pydicom.git
46 | # pip install git+git://github.com/pydicom/pynetdicom3.git
47 | # logInfo "Start Docker container"
48 | # docker-compose up --build &
49 | #
50 | # logInfo "Waiting PostgreSQL to launch on 5432..."
51 | #
52 | # while ! nc -z localhost 5432; do
53 | # sleep 0.1
54 | # done
55 | #
56 | # logInfo "PostgreSQL launched"
57 | #
58 | # sleep 10
59 | # logInfo "Migrate database"
60 | # python ./manage.py migrate
61 | #
62 | # logInfo "Store DICOM images"
63 | # python ./manage.py clear_dicom
64 | # python ./manage.py store_dicom ../images
65 | # #python ./manage.py install_plugins --mode=PYPI ndicom_kmeans ndicom_cuda_kmeans ndicom_fcm ndicom_thresholding \
66 | # # ndicom_region_growing ndicom_meanshift ndicom_gauss_kernel_kmeans ndicom_improved_kmeans ndicom_gaussian_mixture --clear --upgrade
67 | # python app.py &
68 | #
69 | # while ! nc -z localhost 8080; do
70 | # sleep 0.1
71 | # done
72 | #
73 | # sleep 10
74 | #
75 | # cd ../ndicom_client
76 | # rm -rf build
77 | # npm run build
78 | # npm install -g serve
79 | # server -s build
80 | # docker-compose up --build
81 | # else
82 | # logInfo "Start Docker container"
83 | # docker-compose up --build &
84 | #
85 | # logInfo "Waiting PostgreSQL to launch on 5432..."
86 | #
87 | # while ! nc -z localhost 5432; do
88 | # sleep 0.1
89 | # done
90 | #
91 | # logInfo "PostgreSQL launched"
92 | #
93 | # sleep 10
94 | # python app.py &
95 | #
96 | # while ! nc -z localhost 8080; do
97 | # sleep 0.1
98 | # done
99 | #
100 | # sleep 10
101 | # fi
102 | # }
103 |
104 | logInfo "Verification all required components"
105 |
106 | # Check if python3 is istalled
107 | if command -v python3 &>/dev/null; then
108 | logInfoStep "Step 1/3 - Python 3 is installed"
109 | else
110 | logErr "Python 3 is not installed"
111 | exit -1
112 | fi
113 |
114 | # Check if Docker is installed
115 | if command -v docker &>/dev/null; then
116 | logInfoStep "Step 2/3 - Docker is installed"
117 | else
118 | logErr "Docker is not installed"
119 | exit -1
120 | fi
121 |
122 | # Check if Git is installed
123 |
124 | if command -v git &>/dev/null; then
125 | logInfoStep "Step 3/3 - Git is installed"
126 | else
127 | logErr "Git is not installed"
128 | exit -1
129 | fi
130 |
131 | use_cuda=0
132 | # Use CUDA?
133 | if command -v whiptail &>/dev/null; then
134 | if whiptail --title "Use CUDA" --yesno "Would you like to use NVIDIA CUDA?" 10 60; then
135 | logWarn "Application server can not be run inside Docker container"
136 | use_cuda=1
137 | fi
138 | else
139 | read -p "Would you like to use NVIDIA CUDA [Y/N]? " use_cuda
140 | if [[ "$use_cuda" == "Y" ]]; then
141 | logWarn "Application server can not be run inside Docker container"
142 | use_cuda=1
143 | fi
144 | fi
145 |
146 | # Clone project if there is only build script
147 | if command -v whiptail &>/dev/null; then
148 | if whiptail --title "Install?" --yesno "Would you like to clone application from repository?" 10 60; then
149 | git clone https://github.com/reactmed/neurdicom.git
150 | logInfo "Project cloned to local directory"
151 | fi
152 | else
153 | read -p "Would you like to clone application from repository [Y/N]? " to_clone
154 | if [[ "$to_clone" == "Y" ]]; then
155 | git clone https://github.com/reactmed/neurdicom.git
156 | logInfo "Project cloned to local directory"
157 | fi
158 | fi
159 |
160 | cd neurdicom
161 |
162 | to_install=0
163 |
164 | #Install?
165 | if command -v whiptail &>/dev/null; then
166 | if whiptail --title "Install?" --yesno "Would you like to install or only run?" 10 60; then
167 | logWarn "Application will be built again"
168 | to_install=1
169 | fi
170 | else
171 | read -p "Would you like to install or only run? " to_install
172 | if [[ "$to_install" == "Y" ]]; then
173 | logWarn "Application will be built again"
174 | to_install=1
175 | fi
176 | fi
177 |
178 | logInfo "Running application"
179 | if [[ $use_cuda -eq 1 ]]; then
180 | logInfo "CUDA installed"
181 | else
182 | if [[ $to_install -eq 1]]; then
183 | docker-compose up --build
184 | else
185 | docker-compose up
186 | fi
187 | fi
188 |
--------------------------------------------------------------------------------
/ndicom_client/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/ignore-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 |
6 | # testing
7 | /coverage
8 |
9 | ## production
10 | /build
11 |
12 | # misc
13 | .DS_Store
14 | .env.local
15 | .env.development.local
16 | .env.test.local
17 | .env.production.local
18 |
19 | npm-debug.log*
20 | yarn-debug.log*
21 | yarn-error.log*
22 | .idea
23 |
--------------------------------------------------------------------------------
/ndicom_client/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM node
2 |
3 | WORKDIR /app
4 |
5 | COPY package.json package-lock.json ./
6 |
7 | CMD npm i npm@latest -g
8 | CMD npm install
9 | #CMD npm run build
--------------------------------------------------------------------------------
/ndicom_client/build/asset-manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "main.css": "static/css/main.f32fca34.css",
3 | "main.css.map": "static/css/main.f32fca34.css.map",
4 | "main.js": "static/js/main.1d75f13c.js",
5 | "main.js.map": "static/js/main.1d75f13c.js.map",
6 | "static/media/flags.png": "static/media/flags.9c74e172.png",
7 | "static/media/icons.eot": "static/media/icons.263bfe56.eot",
8 | "static/media/icons.svg": "static/media/icons.b42b4467.svg",
9 | "static/media/icons.ttf": "static/media/icons.d3490a32.ttf",
10 | "static/media/icons.woff": "static/media/icons.7bc63d50.woff",
11 | "static/media/icons.woff2": "static/media/icons.27c88389.woff2"
12 | }
--------------------------------------------------------------------------------
/ndicom_client/build/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/reactmed/neurdicom/1b3f659ebd4679a913d9ec95bf28e55c059696f1/ndicom_client/build/favicon.ico
--------------------------------------------------------------------------------
/ndicom_client/build/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "short_name": "React App",
3 | "name": "Create React App Sample",
4 | "icons": [
5 | {
6 | "src": "favicon.ico",
7 | "sizes": "64x64 32x32 24x24 16x16",
8 | "type": "image/x-icon"
9 | }
10 | ],
11 | "start_url": "./index.html",
12 | "display": "standalone",
13 | "theme_color": "#000000",
14 | "background_color": "#ffffff"
15 | }
16 |
--------------------------------------------------------------------------------
/ndicom_client/build/service-worker.js:
--------------------------------------------------------------------------------
1 | "use strict";var precacheConfig=[["/index.html","9acc12de51511eab74b2d288b89e0f57"],["/static/css/main.f32fca34.css","423a36f87b425fb6ee5e451cd2eac8ed"],["/static/js/main.1d75f13c.js","c7389828c0dbe58b8b1459205937780b"],["/static/media/flags.9c74e172.png","9c74e172f87984c48ddf5c8108cabe67"],["/static/media/icons.263bfe56.eot","263bfe56755cefdc9e6c3d8070e0868d"],["/static/media/icons.27c88389.woff2","27c88389f00e0d8bfeebebae81f240b6"],["/static/media/icons.7bc63d50.woff","7bc63d50d4158c6bfd4b30a99c9f9460"],["/static/media/icons.b42b4467.svg","b42b446772f84a277ee29a0615b38800"],["/static/media/icons.d3490a32.ttf","d3490a32350db6c9c41e69cc9ce99cd0"]],cacheName="sw-precache-v3-sw-precache-webpack-plugin-"+(self.registration?self.registration.scope:""),ignoreUrlParametersMatching=[/^utm_/],addDirectoryIndex=function(e,t){var n=new URL(e);return"/"===n.pathname.slice(-1)&&(n.pathname+=t),n.toString()},cleanResponse=function(e){return e.redirected?("body"in e?Promise.resolve(e.body):e.blob()).then(function(t){return new Response(t,{headers:e.headers,status:e.status,statusText:e.statusText})}):Promise.resolve(e)},createCacheKey=function(e,t,n,r){var a=new URL(e);return r&&a.pathname.match(r)||(a.search+=(a.search?"&":"")+encodeURIComponent(t)+"="+encodeURIComponent(n)),a.toString()},isPathWhitelisted=function(e,t){if(0===e.length)return!0;var n=new URL(t).pathname;return e.some(function(e){return n.match(e)})},stripIgnoredUrlParameters=function(e,t){var n=new URL(e);return n.hash="",n.search=n.search.slice(1).split("&").map(function(e){return e.split("=")}).filter(function(e){return t.every(function(t){return!t.test(e[0])})}).map(function(e){return e.join("=")}).join("&"),n.toString()},hashParamName="_sw-precache",urlsToCacheKeys=new Map(precacheConfig.map(function(e){var t=e[0],n=e[1],r=new URL(t,self.location),a=createCacheKey(r,hashParamName,n,/\.\w{8}\./);return[r.toString(),a]}));function setOfCachedUrls(e){return e.keys().then(function(e){return e.map(function(e){return e.url})}).then(function(e){return new Set(e)})}self.addEventListener("install",function(e){e.waitUntil(caches.open(cacheName).then(function(e){return setOfCachedUrls(e).then(function(t){return Promise.all(Array.from(urlsToCacheKeys.values()).map(function(n){if(!t.has(n)){var r=new Request(n,{credentials:"same-origin"});return fetch(r).then(function(t){if(!t.ok)throw new Error("Request for "+n+" returned a response with status "+t.status);return cleanResponse(t).then(function(t){return e.put(n,t)})})}}))})}).then(function(){return self.skipWaiting()}))}),self.addEventListener("activate",function(e){var t=new Set(urlsToCacheKeys.values());e.waitUntil(caches.open(cacheName).then(function(e){return e.keys().then(function(n){return Promise.all(n.map(function(n){if(!t.has(n.url))return e.delete(n)}))})}).then(function(){return self.clients.claim()}))}),self.addEventListener("fetch",function(e){if("GET"===e.request.method){var t,n=stripIgnoredUrlParameters(e.request.url,ignoreUrlParametersMatching),r="index.html";(t=urlsToCacheKeys.has(n))||(n=addDirectoryIndex(n,r),t=urlsToCacheKeys.has(n));var a="/index.html";!t&&"navigate"===e.request.mode&&isPathWhitelisted(["^(?!\\/__).*"],e.request.url)&&(n=new URL(a,self.location).toString(),t=urlsToCacheKeys.has(n)),t&&e.respondWith(caches.open(cacheName).then(function(e){return e.match(urlsToCacheKeys.get(n)).then(function(e){if(e)return e;throw Error("The cached response that was expected is missing.")})}).catch(function(t){return console.warn('Couldn\'t serve response for "%s" from cache: %O',e.request.url,t),fetch(e.request)}))}});
--------------------------------------------------------------------------------
/ndicom_client/build/static/media/flags.9c74e172.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/reactmed/neurdicom/1b3f659ebd4679a913d9ec95bf28e55c059696f1/ndicom_client/build/static/media/flags.9c74e172.png
--------------------------------------------------------------------------------
/ndicom_client/build/static/media/icons.263bfe56.eot:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/reactmed/neurdicom/1b3f659ebd4679a913d9ec95bf28e55c059696f1/ndicom_client/build/static/media/icons.263bfe56.eot
--------------------------------------------------------------------------------
/ndicom_client/build/static/media/icons.27c88389.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/reactmed/neurdicom/1b3f659ebd4679a913d9ec95bf28e55c059696f1/ndicom_client/build/static/media/icons.27c88389.woff2
--------------------------------------------------------------------------------
/ndicom_client/build/static/media/icons.7bc63d50.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/reactmed/neurdicom/1b3f659ebd4679a913d9ec95bf28e55c059696f1/ndicom_client/build/static/media/icons.7bc63d50.woff
--------------------------------------------------------------------------------
/ndicom_client/build/static/media/icons.d3490a32.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/reactmed/neurdicom/1b3f659ebd4679a913d9ec95bf28e55c059696f1/ndicom_client/build/static/media/icons.d3490a32.ttf
--------------------------------------------------------------------------------
/ndicom_client/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "neurdicom_client",
3 | "version": "0.1.0",
4 | "private": true,
5 | "dependencies": {
6 | "ami": "0.0.2",
7 | "axios": "^0.18.0",
8 | "cornerstone-core": "^2.0.0",
9 | "cornerstone-wado-image-loader": "^2.0.0",
10 | "file-save": "^0.2.0",
11 | "file-saver": "^1.3.3",
12 | "nifti-reader-js": "^0.5.4",
13 | "prop-types": "latest",
14 | "query-string": "^5.1.0",
15 | "react": "^16.2.0",
16 | "react-dom": "^16.2.0",
17 | "react-dropzone": "^4.2.9",
18 | "react-localize-redux": "^2.17.5",
19 | "react-redux": "^5.0.7",
20 | "react-router": "^4.2.0",
21 | "react-router-dom": "^4.2.2",
22 | "react-scripts": "1.1.1",
23 | "redux": "^4.0.0",
24 | "semantic-ui-css": "^2.3.0",
25 | "semantic-ui-react": "^0.78.2",
26 | "three": "^0.91.0"
27 | },
28 | "scripts": {
29 | "start": "react-scripts start",
30 | "build": "react-scripts build",
31 | "test": "react-scripts test --env=jsdom",
32 | "eject": "react-scripts eject"
33 | },
34 | "proxy": "http://localhost:8080/"
35 | }
36 |
--------------------------------------------------------------------------------
/ndicom_client/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/reactmed/neurdicom/1b3f659ebd4679a913d9ec95bf28e55c059696f1/ndicom_client/public/favicon.ico
--------------------------------------------------------------------------------
/ndicom_client/public/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "short_name": "React App",
3 | "name": "Create React App Sample",
4 | "icons": [
5 | {
6 | "src": "favicon.ico",
7 | "sizes": "64x64 32x32 24x24 16x16",
8 | "type": "image/x-icon"
9 | }
10 | ],
11 | "start_url": "./index.html",
12 | "display": "standalone",
13 | "theme_color": "#000000",
14 | "background_color": "#ffffff"
15 | }
16 |
--------------------------------------------------------------------------------
/ndicom_client/src/App.css:
--------------------------------------------------------------------------------
1 | table, tr, td{
2 | border: 1px solid black;
3 | }
--------------------------------------------------------------------------------
/ndicom_client/src/App.js:
--------------------------------------------------------------------------------
1 | import React, {Component} from 'react';
2 | import './App.css';
3 | import 'semantic-ui-css/semantic.css';
4 | import { Sidebar, Segment, Button, Menu, Image, Icon, Header } from 'semantic-ui-react'
5 |
6 | class App extends Component {
7 | state = { visible: false };
8 |
9 | toggleVisibility = () => this.setState({ visible: !this.state.visible });
10 |
11 | render() {
12 | const { visible } = this.state;
13 | return (
14 |
15 |
16 |
17 |
18 |
19 |
20 | Home
21 |
22 |
23 |
24 | Games
25 |
26 |
27 |
28 | Channels
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 | )
40 | }
41 | }
42 |
43 | export default App;
44 |
--------------------------------------------------------------------------------
/ndicom_client/src/components/common/DicomViewer.css:
--------------------------------------------------------------------------------
1 | .leftTop{
2 | position: absolute;
3 | top: 80px;
4 | /*padding-top: 40px;*/
5 | /*padding-left: 10px;*/
6 | left: 40px;
7 | z-index: 5;
8 | display:block;
9 | text-align: left;
10 | color: white;
11 | background: black;
12 | }
13 |
14 | .leftTop2{
15 | position: absolute;
16 | top: 30%;
17 | left: 10px;
18 | z-index: 5;
19 | display:block;
20 | text-align: left;
21 | color: white;
22 | }
23 |
24 | .leftTop3{
25 | position: absolute;
26 | top: 40%;
27 | left: 10px;
28 | z-index: 5;
29 | display:block;
30 | text-align: left;
31 | color: white;
32 | }
33 |
34 | .leftTop4{
35 | position: absolute;
36 | top: 80%;
37 | left: 10px;
38 | z-index: 5;
39 | display:block;
40 | text-align: left;
41 | color: white;
42 | }
43 |
44 | .leftTop5{
45 | position: absolute;
46 | top: 100%;
47 | left: 10px;
48 | z-index: 5;
49 | display:block;
50 | text-align: left;
51 | color: white;
52 | }
53 |
54 |
55 | .leftTop6{
56 | position: absolute;
57 | top: 120%;
58 | left: 10px;
59 | z-index: 5;
60 | display:block;
61 | text-align: left;
62 | color: white;
63 | }
64 |
--------------------------------------------------------------------------------
/ndicom_client/src/components/common/DicomViewer.js:
--------------------------------------------------------------------------------
1 | import React, {Component} from 'react';
2 | import * as THREE from 'three';
3 | import './DicomViewer.css';
4 |
5 | class DicomViewer extends Component {
6 | constructor(props) {
7 | super(props);
8 | this.rayCaster = new THREE.Raycaster();
9 | this.state = {
10 | seedPoint: new THREE.Vector2(-1, -1)
11 | };
12 | this.setState = this.setState.bind(this);
13 | }
14 |
15 | onWindowResize = () => {
16 | this.camera.aspect = this.node.clientWidth / this.node.clientHeight;
17 | this.camera.updateProjectionMatrix();
18 | this.renderer.setSize(this.node.clientWidth, this.node.clientHeight);
19 | };
20 |
21 | onMouseClick = (e) => {
22 | const scene = this.scene;
23 | const camera = this.camera;
24 | const rayCaster = this.rayCaster;
25 | const clientX = e.clientX;
26 | const clientY = e.clientY;
27 | const array = DicomViewer.getMousePosition(e.target, clientX, clientY);
28 | const vecPos = new THREE.Vector2(array[0] * 2 - 1, -(array[1] * 2) + 1);
29 | rayCaster.setFromCamera(vecPos, camera);
30 | const intersects = rayCaster.intersectObjects(scene.children);
31 | if (intersects && intersects.length > 0) {
32 | const intersectedImg = intersects[0];
33 | const uv = intersectedImg.uv;
34 | if (uv) {
35 | console.log(uv);
36 | this.setState({seedPoint: new THREE.Vector2(uv.x, uv.y)});
37 | }
38 | }
39 | };
40 |
41 | static getMousePosition(dom, x, y) {
42 | const boundingBox = dom.getBoundingClientRect();
43 | return [
44 | (x - boundingBox.left) / boundingBox.width, (y - boundingBox.top) / boundingBox.height
45 | ];
46 | }
47 |
48 | componentDidMount() {
49 | const instance = this.props.instance;
50 | const w = parseFloat(instance['columns']);
51 | const h = parseFloat(instance['rows']);
52 | const url = `/api/instances/${this.props.instance.id}/image`;
53 | this.scene = new THREE.Scene();
54 | this.camera = new THREE.PerspectiveCamera(45, this.node.clientWidth / this.node.clientHeight, 0.1, 100);
55 | this.renderer = new THREE.WebGLRenderer();
56 | this.renderer.setSize(this.node.clientWidth, this.node.clientHeight);
57 | this.node.appendChild(this.renderer.domElement);
58 |
59 | this.node.addEventListener('resize', this.onWindowResize, false);
60 | this.node.onclick = this.onMouseClick;
61 |
62 | const seedPoint = this.state.seedPoint;
63 |
64 | const vertShader = document.getElementById('mainVert').textContent;
65 | const fragShader = document.getElementById(this.props.colorScale + 'Frag').textContent;
66 |
67 | new THREE.TextureLoader().load(url, (texture) => {
68 | const uniforms = {
69 | texture: {
70 | type: 't', value: texture
71 | },
72 | seedPoint: {
73 | value: seedPoint
74 | },
75 | imgSize: {
76 | value: new THREE.Vector2(w, h)
77 | }
78 | };
79 | const geometry = new THREE.PlaneGeometry(3, 3);
80 | const material = new THREE.ShaderMaterial({
81 | uniforms: uniforms,
82 | vertexShader: vertShader,
83 | fragmentShader: fragShader
84 | });
85 | this.rect = new THREE.Mesh(geometry, material);
86 | if (this.props.rotation === 'left')
87 | this.rect.rotation.z -= 0.5;
88 | else if (this.props.rotation === 'right')
89 | this.rect.rotation.z += 0.5;
90 | else
91 | this.rect.rotation.z = 0.0;
92 | this.scene.add(this.rect);
93 | this.camera.position.z = 3.5;
94 | this.renderer.render(this.scene, this.camera);
95 | });
96 | }
97 |
98 | componentDidUpdate() {
99 | const instance = this.props.instance;
100 | const w = parseFloat(instance['columns']);
101 | const h = parseFloat(instance['rows']);
102 | const url = `/api/instances/${this.props.instance.id}/image`;
103 | const vertShader = document.getElementById('mainVert').textContent;
104 | const fragShader = document.getElementById(this.props.colorScale + 'Frag').textContent;
105 | const seedPoint = this.state.seedPoint;
106 | new THREE.TextureLoader().load(url, (texture) => {
107 | const uniforms = {
108 | texture: {
109 | type: 't', value: texture
110 | },
111 | seedPoint: {
112 | value: seedPoint
113 | },
114 | imgSize: {
115 | value: new THREE.Vector2(w, h)
116 | }
117 | };
118 | if (this.props.rotation === 'left')
119 | this.rect.rotation.z -= 0.5;
120 | else if (this.props.rotation === 'right')
121 | this.rect.rotation.z += 0.5;
122 | else
123 | this.rect.rotation.z = 0.0;
124 | const material = new THREE.ShaderMaterial({
125 | uniforms: uniforms,
126 | vertexShader: vertShader,
127 | fragmentShader: fragShader
128 | });
129 | this.rect.material = material;
130 | this.rect.needsUpdate = true;
131 | this.renderer.render(this.scene, this.camera);
132 | });
133 | }
134 |
135 | render() {
136 | const instance = this.props.instance;
137 | return (
138 | this.node = node} style={{height: window.innerHeight}}>
139 |
140 |
141 | Patient ID: {instance.parent.patient['patient_id']}
142 |
143 |
144 | Patient Name: {instance.parent.patient['patient_name']}
145 |
146 |
147 | Series ID: {instance.parent.series['series_id']}
148 |
149 |
150 |
151 | Instance: {instance['instance_number']}
152 |
153 |
154 | Modality: {instance.parent.series['modality']}
155 |
156 |
157 | Size: {instance['columns']}x{instance['rows']}
158 |
159 |
160 | Color Scheme: {instance['photometric_interpretation']}
161 |
162 |
163 |
164 | )
165 | }
166 | }
167 |
168 | export default DicomViewer;
--------------------------------------------------------------------------------
/ndicom_client/src/components/common/MenuContainer.js:
--------------------------------------------------------------------------------
1 | import React, {Component} from 'react';
2 | import {Link} from "react-router-dom";
3 | import {connect} from 'react-redux';
4 | import {Button, Dropdown, Icon, Menu, Segment} from "semantic-ui-react";
5 | import {Translate, setActiveLanguage} from "react-localize-redux";
6 |
7 | const languagesOptions = {
8 | 'Русский': 'ru',
9 | 'English': 'en'
10 | };
11 |
12 | class MenuContainer extends Component {
13 | constructor(props) {
14 | super(props);
15 | this.setState = this.setState.bind(this);
16 | this.changeLanguage = this.props.changeLanguage;
17 | }
18 |
19 | onChangeLanguage = (e) => {
20 | this.changeLanguage(languagesOptions[e.target.innerHTML]);
21 | };
22 |
23 | render() {
24 | return (
25 |
26 | {
27 | (translate) => {
28 | return (
29 |
30 |
31 |
63 |
64 |
65 | {this.props.children}
66 |
67 |
68 | );
69 | }
70 | }
71 |
72 | )
73 | }
74 | }
75 |
76 | MenuContainer.defaultProps = {
77 | activeItem: ''
78 | };
79 |
80 | const mapStateToProps = (state) => (
81 | {}
82 | );
83 |
84 | const mapDispatchToProps = (dispatch) => (
85 | {
86 | changeLanguage(languageCode) {
87 | console.log(languageCode);
88 | dispatch(setActiveLanguage(languageCode));
89 | }
90 | }
91 | );
92 |
93 | export default connect(mapStateToProps, mapDispatchToProps)(MenuContainer);
--------------------------------------------------------------------------------
/ndicom_client/src/components/dicomNodesPage/EchoButton.js:
--------------------------------------------------------------------------------
1 | import React, {Component} from 'react';
2 | import {Button, Label} from "semantic-ui-react";
3 | import PropTypes from 'prop-types';
4 | import * as axios from "axios/index";
5 | import {Translate} from "react-localize-redux";
6 |
7 | class EchoButton extends Component {
8 | constructor(props) {
9 | super(props);
10 | this.state = {
11 | status: null
12 | };
13 | this.setState = this.setState.bind(this);
14 | this.onClick = this.onClick.bind(this);
15 | }
16 |
17 | onClick() {
18 | axios.get(this.props.echoUrl).then(
19 | () => {
20 | this.setState({status: true});
21 | },
22 | () => {
23 | this.setState({status: false});
24 | }
25 | )
26 | }
27 |
28 | render() {
29 | console.log(this.state.status);
30 | return (
31 |
32 | {
33 | (translate) => (
34 |
35 |
38 | {
39 | (this.state.status !== undefined && this.state.status !== null) && (
40 |
46 | {
47 | this.state.status ? translate('success') : translate('fail')
48 | }
49 |
50 | )
51 | }
52 |
53 | )
54 | }
55 |
56 | );
57 | }
58 | }
59 |
60 | EchoButton.propTypes = {
61 | echoUrl: PropTypes.string
62 | };
63 |
64 | export default EchoButton;
--------------------------------------------------------------------------------
/ndicom_client/src/components/pluginsPage/PluginItem.js:
--------------------------------------------------------------------------------
1 | import React, {Component} from 'react';
2 | import {Translate} from 'react-localize-redux';
3 | import {Button, Divider, Header, Loader, Segment} from "semantic-ui-react";
4 | import PropTypes from 'prop-types';
5 |
6 | class PluginItem extends Component {
7 | constructor(props) {
8 | super(props);
9 | this.state = {
10 | isInstalling: false
11 | };
12 | this.setState = this.setState.bind(this);
13 | }
14 |
15 | onDeletePlugin = () => {
16 | const plugin = this.props.plugin;
17 | const callback = this.props.onDeletePlugin;
18 | callback(plugin);
19 | };
20 |
21 | onInstallPlugin = () => {
22 | const plugin = this.props.plugin;
23 | const callback = this.props.onInstallPlugin;
24 | callback(plugin);
25 | this.setState({isInstalling: true});
26 | };
27 |
28 | render() {
29 | const plugin = this.props.plugin;
30 | console.log('PLUGIN ITEM');
31 | console.log(plugin);
32 | return (
33 |
34 | {
35 | (translate) => (
36 |
37 | {plugin['display_name']}
38 |
39 | {translate('plugin.author')}: {plugin['author']}
40 |
41 | {translate('plugin.version')}: {plugin['version']}
42 |
43 | {translate('plugin.tags')}: {(plugin['tags'] || []).join(', ')}
44 |
45 | {translate('plugin.modalities')}: {plugin['modalities'].join(', ')}
46 |
47 |
48 | {
49 | plugin['is_installed'] ? (
50 |
53 | ) : (
54 |
57 | )
58 | }
59 |
60 |
61 | )
62 | }
63 |
64 |
65 | );
66 | }
67 | }
68 |
69 | PluginItem.propTypes = {
70 | plugin: PropTypes.object,
71 | onDeletePlugin: PropTypes.func,
72 | onInstallPlugin: PropTypes.func,
73 | isInstalling: PropTypes.bool
74 | };
75 |
76 | export default PluginItem;
--------------------------------------------------------------------------------
/ndicom_client/src/components/processingPage/BlendParamsDialog.js:
--------------------------------------------------------------------------------
1 | import React, {Component} from 'react';
2 | import PropTypes from 'prop-types';
3 | import {Button, Form, Modal} from "semantic-ui-react";
4 | import './Dialog.css';
5 |
6 | class BlendParamsDialog extends Component {
7 | constructor(props) {
8 | super(props);
9 | this.onClose = this.props.onClose || function () {
10 |
11 | };
12 | this.onApply = this.props.onApply || function () {
13 |
14 | };
15 | this.state = {
16 | params: {
17 | alpha: 0.5
18 | }
19 | };
20 | }
21 |
22 | onApplyCallback = () => {
23 | const onApply = this.onApply;
24 | const params = this.state.params;
25 | const onClose = this.onClose;
26 | onApply(params);
27 | onClose();
28 | };
29 |
30 | onChange = (e) => {
31 | const value = e.target.value;
32 | const name = e.target.name;
33 | const params = this.state.params;
34 | params[name] = value;
35 | this.setState({
36 | params: params
37 | })
38 | };
39 |
40 | render() {
41 | const isOpen = this.props.open;
42 | return (
43 |
44 |
45 | Blend
46 |
47 |
48 |
50 |
51 |
53 |
54 |
55 |
56 |
57 |
58 | )
59 | }
60 | }
61 |
62 | BlendParamsDialog.prototypes = {
63 | open: PropTypes.bool,
64 | onClose: PropTypes.func,
65 | onApply: PropTypes.func
66 | };
67 |
68 | export default BlendParamsDialog;
--------------------------------------------------------------------------------
/ndicom_client/src/components/processingPage/ControlPanel.js:
--------------------------------------------------------------------------------
1 | import React, {Component} from 'react';
2 | import {Button, Dropdown, Icon, Menu, Modal, Form, Input, Label} from "semantic-ui-react";
3 | import PropTypes from 'prop-types';
4 | import PluginsService from "../../services/PluginsService";
5 |
6 | const colorScaleOptions = [
7 | {
8 | 'key': 'main',
9 | 'value': 'main',
10 | 'text': 'Исходное изображение'
11 | },
12 | {
13 | 'key': 'heatmap',
14 | 'value': 'heatmap',
15 | 'text': 'Тепловая карта'
16 | },
17 | {
18 | 'key': 'inverseHeatmap',
19 | 'value': 'inverseHeatmap',
20 | 'text': 'Инвертированная тепловая карта'
21 | },
22 | {
23 | 'key': 'hotRed',
24 | 'value': 'hotRed',
25 | 'text': 'Красная схема'
26 | },
27 | {
28 | 'key': 'hotGreen',
29 | 'value': 'hotGreen',
30 | 'text': 'Зеленая схема'
31 | },
32 | {
33 | 'key': 'hotBlue',
34 | 'value': 'hotBlue',
35 | 'text': 'Синяя схема'
36 | },
37 | {
38 | 'key': 'inverse',
39 | 'value': 'inverse',
40 | 'text': 'Инвертирование'
41 | },
42 | {
43 | 'key': 'sobel',
44 | 'value': 'sobel',
45 | 'text': 'Оператор Собеля'
46 | },
47 | {
48 | 'key': 'sharpen',
49 | 'value': 'sharpen',
50 | 'text': 'Резкость'
51 | },
52 | {
53 | 'key': 'emboss',
54 | 'value': 'emboss',
55 | 'text': 'Тиснение'
56 | },
57 | {
58 | 'key': 'laplacian',
59 | 'value': 'laplacian',
60 | 'text': 'Оператор Лапласа'
61 | }
62 | ];
63 |
64 | const viewModeOptions = [
65 | {
66 | 'key': 'main',
67 | 'value': 'main',
68 | 'text': 'Нет маски'
69 | },
70 | {
71 | 'key': 'blend',
72 | 'value': 'blend',
73 | 'text': 'Смешивание'
74 | },
75 | {
76 | 'key': 'mix',
77 | 'value': 'mix',
78 | 'text': 'Полное смешивание'
79 | },
80 | {
81 | 'key': 'crop',
82 | 'value': 'crop',
83 | 'text': 'Вырезать фрагмент'
84 | },
85 | {
86 | 'key': 'contour',
87 | 'value': 'contour',
88 | 'text': 'Контурное выделение'
89 | }
90 | ];
91 |
92 | class ControlPanel extends Component {
93 | constructor(props) {
94 | super(props);
95 | this.onHome = this.props.onHome || function () {
96 | };
97 | this.onSetColorScale = this.props.onSetColorScale || function () {
98 | };
99 | this.onSetMode = this.props.onSetMode || function () {
100 | };
101 | this.onApplyPlugin = this.props.onApplyPlugin || function () {
102 | };
103 | this.onSetAlpha = this.props.onSetAlpha || function() {};
104 | this.setState = this.setState.bind(this);
105 | }
106 |
107 | render() {
108 | return (
109 |
134 | );
135 | }
136 | }
137 |
138 | ControlPanel.propTypes = {
139 | onHome: PropTypes.func,
140 | onSetColorScale: PropTypes.func,
141 | onSetMode: PropTypes.func,
142 | onSetAlpha: PropTypes.func,
143 | onApplyPlugin: PropTypes.func,
144 | alpha: PropTypes.primary
145 | };
146 |
147 | export default ControlPanel;
--------------------------------------------------------------------------------
/ndicom_client/src/components/processingPage/Dialog.css:
--------------------------------------------------------------------------------
1 | .ui.modal,
2 | .ui.active.modal {
3 | margin: 0 auto!important;
4 | top: auto !important;
5 | left: auto !important;
6 | transform-origin: center !important;
7 | transition: all ease .5s;
8 | }
--------------------------------------------------------------------------------
/ndicom_client/src/components/processingPage/DicomViewer.css:
--------------------------------------------------------------------------------
1 | .leftTop{
2 | position: absolute;
3 | top: 80px;
4 | /*padding-top: 40px;*/
5 | /*padding-left: 10px;*/
6 | left: 40px;
7 | z-index: 5;
8 | display:block;
9 | text-align: left;
10 | color: white;
11 | }
12 |
13 | .leftTop2{
14 | position: absolute;
15 | top: 30%;
16 | left: 10px;
17 | z-index: 5;
18 | display:block;
19 | text-align: left;
20 | color: white;
21 | }
22 |
23 | .leftTop3{
24 | position: absolute;
25 | top: 40%;
26 | left: 10px;
27 | z-index: 5;
28 | display:block;
29 | text-align: left;
30 | color: white;
31 | }
32 |
33 | .leftTop4{
34 | position: absolute;
35 | top: 80%;
36 | left: 10px;
37 | z-index: 5;
38 | display:block;
39 | text-align: left;
40 | color: white;
41 | }
42 |
43 | .leftTop5{
44 | position: absolute;
45 | top: 100%;
46 | left: 10px;
47 | z-index: 5;
48 | display:block;
49 | text-align: left;
50 | color: white;
51 | }
52 |
53 |
54 | .leftTop6{
55 | position: absolute;
56 | top: 120%;
57 | left: 10px;
58 | z-index: 5;
59 | display:block;
60 | text-align: left;
61 | color: white;
62 | }
63 |
--------------------------------------------------------------------------------
/ndicom_client/src/components/processingPage/ParamsDialog.js:
--------------------------------------------------------------------------------
1 | import React, {Component} from 'react';
2 | import PropTypes from 'prop-types';
3 | import {Button, Dropdown, Form, Modal} from "semantic-ui-react";
4 | import './Dialog.css';
5 | import Dropzone from "react-dropzone";
6 | import * as nifti from 'nifti-reader-js';
7 |
8 | class ParamsDialog extends Component {
9 | constructor(props) {
10 | super(props);
11 | this.onClose = this.props.onClose || function () {
12 |
13 | };
14 | this.onApply = this.props.onApply || function () {
15 |
16 | };
17 | this.setState = this.setState.bind(this);
18 | let params = {};
19 | Object.keys(this.props.plugin['params']).forEach(paramName => {
20 | params[paramName] = this.props.plugin['params'][paramName]['default'] || null;
21 | });
22 | this.state = {
23 | params: params
24 | };
25 | }
26 |
27 | onApplyCallback = () => {
28 | const onApply = this.onApply;
29 | const params = this.state.params;
30 | const onClose = this.onClose;
31 | Object.keys(params).forEach(paramName => {
32 | const pluginParam = this.props.plugin['params'][paramName];
33 | const type = pluginParam['type'];
34 | const isRequired = pluginParam['is_required'];
35 | const value = params[paramName];
36 | if ((value === null || value === undefined || value === '') && !isRequired)
37 | params[paramName] = null;
38 | else if (type === 'int')
39 | params[paramName] = parseInt(value);
40 | else if (type === 'float')
41 | params[paramName] = parseFloat(value);
42 | });
43 | onApply(params);
44 | onClose();
45 | };
46 |
47 | onChange = (e) => {
48 | const value = e.target.value;
49 | const name = e.target.name;
50 | const params = this.state.params;
51 | params[name] = value;
52 | this.setState({
53 | params: params
54 | })
55 | };
56 |
57 | addGroundTruth = (files) => {
58 | // console.log(files[0]);
59 | // const file = files[0];
60 | // const reader = new FileReader();
61 | // reader.addEventListener('loadend', (res) => {
62 | // const gtFile = res.target.result;
63 | // console.log('COMPRESSED');
64 | // const data = nifti.decompress(gtFile);
65 | // const header = nifti.readHeader(data);
66 | // const img = nifti.readImage(header, data);
67 | // console.log(img);
68 | // });
69 | // reader.readAsArrayBuffer(file);
70 | };
71 |
72 | render() {
73 | const plugin = this.props.plugin;
74 | const isOpen = this.props.open;
75 | const params = plugin['params'];
76 | return (
77 |
78 |
79 | {plugin['display_name']}
80 |
81 |
82 |
96 |
97 |
99 |
100 | );
101 | }
102 | if (range && range.length === 2) {
103 | return (
104 |
105 |
106 |
113 |
114 | );
115 | }
116 | // if (values) {
117 | // const options = Object.keys(values).map(key => {
118 | // return {"key": key, "value": key, "text": values[key]};
119 | // });
120 | // return (
121 | //
122 | //
123 | //
125 | //
126 | //
127 | // )
128 | // }
129 | return (
130 |
131 |
132 |
134 |
135 | );
136 | })
137 | }
138 |
139 | Перетащите файлы сегментации
140 |
141 |
142 |
143 |
144 |
145 | )
146 | }
147 | }
148 |
149 | ParamsDialog.prototypes = {
150 | plugin: PropTypes.object,
151 | open: PropTypes.bool,
152 | onClose: PropTypes.func,
153 | onApply: PropTypes.func
154 | };
155 |
156 | export default ParamsDialog;
--------------------------------------------------------------------------------
/ndicom_client/src/components/seriesViewerPage/ControlPanel.js:
--------------------------------------------------------------------------------
1 | import React, {Component} from 'react';
2 | import {Button, Dropdown, Icon, Menu, Modal} from "semantic-ui-react";
3 | import PropTypes from 'prop-types';
4 | import PluginsService from "../../services/PluginsService";
5 |
6 | const filterOptions = [
7 | {
8 | 'key': 'sobel',
9 | 'value': 'sobel',
10 | 'text': 'Оператор Собеля'
11 | },
12 | {
13 | 'key': 'sharpen',
14 | 'value': 'sharpen',
15 | 'text': 'Резкость'
16 | },
17 | {
18 | 'key': 'emboss',
19 | 'value': 'emboss',
20 | 'text': 'Тиснение'
21 | },
22 | {
23 | 'key': 'laplacian',
24 | 'value': 'laplacian',
25 | 'text': 'Оператор Лапласа'
26 | },
27 | {
28 | 'key': 'medianBlur',
29 | 'value': 'medianBlur',
30 | 'text': 'Медианный фильтр'
31 | }
32 | ];
33 | const colorScaleOptions = [
34 | {
35 | 'key': 'main',
36 | 'value': 'main',
37 | 'text': 'Исходное изображение'
38 | },
39 | {
40 | 'key': 'heatmap',
41 | 'value': 'heatmap',
42 | 'text': 'Тепловая схема'
43 | },
44 | {
45 | 'key': 'inverseHeatmap',
46 | 'value': 'inverseHeatmap',
47 | 'text': 'Инвертированная тепловая схема'
48 | },
49 | {
50 | 'key': 'hotRed',
51 | 'value': 'hotRed',
52 | 'text': 'Красная схема'
53 | },
54 | {
55 | 'key': 'hotGreen',
56 | 'value': 'hotGreen',
57 | 'text': 'Зеленая схема'
58 | },
59 | {
60 | 'key': 'hotBlue',
61 | 'value': 'hotBlue',
62 | 'text': 'Синяя схема'
63 | },
64 | {
65 | 'key': 'inverse',
66 | 'value': 'inverse',
67 | 'text': 'Инвертирование'
68 | }
69 | ];
70 |
71 | const viewModeOptions = [
72 | {
73 | 'key': 'one',
74 | 'value': 'one',
75 | 'text': 'Один снимок'
76 | },
77 | {
78 | 'key': 'two',
79 | 'value': 'two',
80 | 'text': 'Два снимка'
81 | }
82 | ];
83 |
84 | class ControlPanel extends Component {
85 | constructor(props) {
86 | super(props);
87 | this.onHome = this.props.onHome || function () {
88 | };
89 | this.onNextInstance = this.props.onNextInstance || function () {
90 | };
91 | this.onPrevInstance = this.props.onPrevInstance || function () {
92 | };
93 | this.onPlay = this.props.onPlay || function () {
94 | };
95 | this.onStop = this.props.onStop || function () {
96 | };
97 | this.onSetColorScale = this.props.onSetColorScale || function () {
98 | };
99 | this.onSetViewMode = this.props.onSetViewMode || function () {
100 | };
101 | this.onRotateLeft = this.props.onRotateLeft || function () {
102 | };
103 | this.onRotateRight = this.props.onRotateRight || function () {
104 | };
105 | this.onApplyPlugin = this.props.onApplyPlugin || function () {
106 | };
107 | this.setState = this.setState.bind(this);
108 | this.state = {
109 | pluginOptions: [],
110 | pluginId: null
111 | }
112 | }
113 |
114 | componentDidMount = () => {
115 | PluginsService.findPlugins((plugins) => {
116 | console.log(plugins);
117 | const pluginOptions = plugins.filter(plugin => plugin['is_installed'])
118 | .map(plugin => {
119 | return {key: plugin.id, value: plugin.id, text: plugin['display_name']};
120 | }
121 | );
122 | this.setState({pluginOptions: pluginOptions})
123 | });
124 | }
125 |
126 | onSelectPlugin = (e, o) => {
127 | // this.setState({
128 | // pluginId: o.value
129 | // });
130 | };
131 |
132 | onApplyPluginCallback = (e, o) => {
133 | const onApplyPlugin = this.onApplyPlugin;
134 | const pluginId = o.value;
135 | if(pluginId) {
136 | onApplyPlugin(pluginId);
137 | }
138 | };
139 |
140 | render = () => {
141 | const pluginOptions = this.state.pluginOptions;
142 | return (
143 |
206 | );
207 | }
208 | }
209 |
210 | ControlPanel.propTypes = {
211 | onHome: PropTypes.func,
212 | onPrevInstance: PropTypes.func,
213 | onNextInstance: PropTypes.func,
214 | onPlay: PropTypes.func,
215 | onStop: PropTypes.func,
216 | onSetColorScale: PropTypes.func,
217 | onSetViewMode: PropTypes.func,
218 | onRotateLeft: PropTypes.func,
219 | onRotateRight: PropTypes.func,
220 | onApplyPlugin: PropTypes.func
221 | };
222 |
223 | export default ControlPanel;
--------------------------------------------------------------------------------
/ndicom_client/src/index.css:
--------------------------------------------------------------------------------
1 | body {
2 | margin: 0;
3 | padding: 0;
4 | font-family: sans-serif;
5 | }
6 |
--------------------------------------------------------------------------------
/ndicom_client/src/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 | import {createStore, combineReducers} from 'redux';
4 | import {Provider} from 'react-redux';
5 | import {localeReducer as locale, initialize, addTranslation} from 'react-localize-redux';
6 | import {BrowserRouter, Route} from 'react-router-dom';
7 | import registerServiceWorker from './registerServiceWorker';
8 | import StudiesPage from "./pages/StudiesPage";
9 | import StudySeriesPage from "./pages/StudySeriesPage";
10 | import 'semantic-ui-css/semantic.css';
11 | import PatientsPage from "./pages/PatientsPage";
12 | import PluginsPage from "./pages/PluginsPage";
13 | import SeriesViewerPage from "./pages/SeriesViewerPage";
14 | import LoginPage from "./pages/LoginPage";
15 | import DicomNodesPage from "./pages/DicomNodesPage";
16 | import UploadDicomPage from "./pages/UploadDicomPage";
17 | import ProcessingPage from "./pages/ProcessingPage";
18 | import PatientStudiesPage from "./pages/PatientStudies";
19 |
20 | const store = createStore(combineReducers({locale}));
21 |
22 | const languages = [
23 | {name: 'English', code: 'en'},
24 | {name: 'Русский', code: 'ru'}
25 | ];
26 |
27 | const translations = {
28 | patient: {
29 | id: ['Patient ID', 'Идентификатор пациента'],
30 | patient: ['Patient', 'Пациент'],
31 | patients: ['Patients', 'Пациенты'],
32 | name: ['Patient Name', 'ФИО'],
33 | age: ['Patient Age', 'Возраст'],
34 | birthdate: ['Patient Birthdate', 'Дата рождения'],
35 | gender: ['Patient Gender', 'Пол'],
36 | anonymized: ['Anonymized', 'Анонимизирован'],
37 | imagesCount: ['Images Count', 'Кол-во снимков']
38 | },
39 |
40 | study: {
41 | study: ['Study', 'Обследование'],
42 | studies: ['Studies', 'Обследования'],
43 | id: ['Study ID', 'Идентификатор Обследования'],
44 | date: ['Study Date', 'Дата проведения'],
45 | description: ['Study Description', 'Описание'],
46 | modality: ['Modality', 'Диагностическое оборудование'],
47 | imagesCount: ['Images Count', 'Кол-во изображений'],
48 | referringPhysician: ['Referring Physician', 'Главный врач'],
49 |
50 | },
51 |
52 | series: {
53 | series: ['Series', 'Серия обследований'],
54 | description: ['Description', 'Описание'],
55 | modality: ['Modality', 'Диагностическое оборудование'],
56 | bodyPartExamined: ['Body Part Examined', 'Часть тела'],
57 | patientPosition: ['Patient Position', 'Положение пациента'],
58 | seriesNumber: ['Series Number', 'Номер серии']
59 | },
60 |
61 | instance: ['Instance', 'Изображение'],
62 | plugin: {
63 | plugin: ['Plugin', 'Плагин'],
64 | plugins: ['Plugins', 'Плагины'],
65 | name: ['Name', 'Название'],
66 | author: ['Author', 'Автор'],
67 | version: ['Version', 'Версия'],
68 | modalities: ['Modalities', 'Тип диагностики'],
69 | tags: ['Tags', 'Теги']
70 | },
71 |
72 | dicomNode: {
73 | dicomNodes: ['DICOM Servers', 'Сервера DICOM'],
74 | name: ['Name', 'Название'],
75 | protocol: ['Protocol', 'Протокол'],
76 | aet: ['AET', 'AET'],
77 | remoteAet: ['Remote AET', 'Удаленный AET'],
78 | remoteHost: ['Remote Host', 'Удаленный хост'],
79 | remotePort: ['Remote Port', 'Удаленный порт'],
80 | add: ['Add', 'Добавить'],
81 | echo: ['Echo', 'Проверить доступность'],
82 | remoteUrl: ['Remote URL', 'Удаленный адрес'],
83 | instancesUrl: ['Instances URL', 'Адрес ресурса с изображениями'],
84 | instanceFileUrl: ['Instance File URL', 'Адрес изображения'],
85 | download: ['Download images', 'Скачать изображения']
86 | },
87 |
88 | uploadDicom: {
89 | uploadDicom: ['Upload images', 'Загрузить изображения']
90 | },
91 |
92 | auth: {
93 | logOut: ['Log Out', 'Выйти']
94 | },
95 |
96 | translation: {
97 | language: ['Language', 'Язык'],
98 | changeLanguage: ['Change language', 'Сменить язык']
99 | },
100 | open: ['Open', 'Открыть'],
101 | delete: ['Delete', 'Удалить'],
102 | install: ['Install', 'Установить'],
103 | success: ['Success', 'Успешно'],
104 | fail: ['Fail', 'Неудачно'],
105 | imagesCount: ['Images Count', 'Кол-во изображений']
106 | };
107 |
108 | store.dispatch(initialize(languages, {defaultLanguage: 'ru'}));
109 | store.dispatch(addTranslation(translations));
110 |
111 | ReactDOM.render(
112 |
113 |
114 |
115 |
116 |
117 |
118 |
119 |
120 |
121 |
122 |
123 |
124 |
125 |
126 |
127 |
128 |
129 | ,
130 | document.getElementById('root')
131 | );
132 | registerServiceWorker();
133 |
--------------------------------------------------------------------------------
/ndicom_client/src/pages/LoginPage.js:
--------------------------------------------------------------------------------
1 | import React, {Component} from 'react';
2 | import {Button, Form, Header, Segment} from "semantic-ui-react";
3 |
4 | class LoginPage extends Component {
5 | render() {
6 | return (
7 |
31 | )
32 | }
33 | }
34 |
35 | export default LoginPage;
--------------------------------------------------------------------------------
/ndicom_client/src/pages/PatientStudies.js:
--------------------------------------------------------------------------------
1 | import React, {Component} from 'react';
2 | import {
3 | Button, Checkbox, Container, Dropdown, Form, Icon, Input, Menu, Radio, Segment, Select, Sidebar, Table,
4 | TextArea, TransitionablePortal as visible
5 | } from "semantic-ui-react";
6 | import StudiesService from "../services/DicomService";
7 | import {Link} from "react-router-dom";
8 | import MenuContainer from "../components/common/MenuContainer";
9 | import {Translate} from "react-localize-redux";
10 |
11 | const patientMatcherOptions = [
12 | {key: 'EXACT', text: 'Точный поиск', value: 'EXACT'},
13 | {key: 'STARTS_WITH', text: 'Начинается с...', value: 'STARTS_WITH'},
14 | {key: 'ENDS_WITH', text: 'Заканчивается...', value: 'ENDS_WITH'},
15 | {key: 'FUZZY', text: 'Нечеткий поиск', value: 'FUZZY'},
16 | ];
17 | const options = [
18 | {key: 'DX', text: 'DX', value: 'DX'},
19 | {key: 'MR', text: 'MR', value: 'MR'},
20 | {key: 'CT', text: 'CT', value: 'CT'},
21 | {key: 'US', text: 'US', value: 'US'},
22 | {key: 'ECG', text: 'ECG', value: 'ECG'},
23 | {key: 'XA', text: 'XA', value: 'XA'},
24 | {key: 'OT', text: 'OT', value: 'OT'},
25 | ];
26 |
27 | class PatientStudiesPage extends Component {
28 | constructor(props) {
29 | super(props);
30 | this.state = {
31 | studies: []
32 | };
33 | this.setState.bind(this);
34 | }
35 |
36 | componentWillMount() {
37 | StudiesService.findStudiesByPatient(studyList => {
38 | this.setState({studies: studyList})
39 | }, this.props.match.params.id);
40 | }
41 |
42 | render() {
43 | return (
44 |
45 |
46 | {
47 | (translate) => (
48 |
49 |
51 | }
55 | icon='search'
56 | iconPosition='left'
57 | placeholder={translate('patient.name')}
58 | />
59 | }
63 | icon='search'
64 | iconPosition='left'
65 | placeholder={translate('patient.id')}
66 | />
67 |
70 |
71 |
72 |
73 |
74 |
75 | {translate('patient.name')}
76 | {translate('study.id')}
77 | {translate('study.date')}
78 | {translate('study.description')}
79 | {translate('study.modality')}
80 | {translate('study.imagesCount')}
81 |
82 |
83 |
84 | {
85 | this.state.studies.map(study => {
86 | return (
87 |
88 |
89 | {study['patient']['patient_name'] || translate('patient.anonymized')}
90 |
91 |
92 |
93 | {study['study_instance_uid']}
94 |
95 |
96 |
97 | {study['study_date']}
98 |
99 |
100 | {study['study_description']}
101 |
102 |
103 | {study['modalities'].join(', ')}
104 |
105 |
106 | {study['images_count']}
107 |
108 |
109 | );
110 | })
111 | }
112 |
113 |
114 |
115 | )
116 | }
117 |
118 |
119 | )
120 | }
121 | }
122 |
123 | export default PatientStudiesPage;
--------------------------------------------------------------------------------
/ndicom_client/src/pages/PatientsPage.js:
--------------------------------------------------------------------------------
1 | import React, {Component} from 'react';
2 | import {Translate} from 'react-localize-redux';
3 | import StudiesService from "../services/DicomService";
4 | import {Button, Dropdown, Select, Table, Form, Menu, Segment, Grid, Header} from "semantic-ui-react";
5 | import {Link} from "react-router-dom";
6 | import MenuContainer from "../components/common/MenuContainer";
7 |
8 | const patientMatcherOptions = [
9 | {key: 'exact', text: 'Точный поиск', value: 'exact'},
10 | {key: 'startswith', text: 'Начинается с', value: 'startswith'},
11 | {key: 'endswith', text: 'Заканчивается', value: 'endswith'},
12 | {key: 'contains', text: 'Нечеткий поиск', value: 'contains'},
13 | ];
14 |
15 | const filterTextToValue = {
16 | 'Exact equals': 'exact',
17 | 'Starts with': 'startswith',
18 | 'Ends with': 'endswith',
19 | 'Contains': 'contains'
20 | };
21 |
22 | class PatientsPage extends Component {
23 | constructor(props) {
24 | super(props);
25 | this.state = {
26 | patients: []
27 | };
28 | this.setState = this.setState.bind(this);
29 | this.handleFindInputOnChange = this.handleFindInputOnChange.bind(this);
30 | }
31 |
32 | componentWillMount() {
33 | StudiesService.findPatients(patients => {
34 | this.setState({patients: patients});
35 | });
36 | }
37 |
38 | handleFindInputOnChange(event) {
39 | if (event.key === 'Enter') {
40 | const patientName = document.getElementById('id_patient_name').value;
41 | const patientId = document.getElementById('id_patient_id').value;
42 | const patientNameFilter = filterTextToValue[document.getElementById('id_patient_name_filter').innerText.trim()];
43 | const patientIdFilter = filterTextToValue[document.getElementById('id_patient_id_filter').innerText.trim()];
44 | console.log(patientNameFilter);
45 | StudiesService.findPatients(patients => {
46 | console.log(this);
47 | this.setState({patients: patients});
48 | }, {
49 | 'patient_name': `${patientNameFilter}=${patientName}`,
50 | 'patient_id': `${patientIdFilter}=${patientId}`
51 | });
52 | }
53 | }
54 |
55 | render() {
56 | console.log(this.state.patients);
57 | return (
58 |
59 |
60 | {
61 | (translate) => (
62 |
63 |
64 |
65 |
67 | }
73 | icon='search'
74 | name='patient_name'
75 | iconPosition='left'
76 | placeholder={translate('patient.name')}
77 | onKeyPress={this.handleFindInputOnChange}
78 | />
79 | }
85 | icon='search'
86 | name='patient_id'
87 | iconPosition='left'
88 | placeholder={translate('patient.id')}
89 | onKeyPress={this.handleFindInputOnChange}
90 | />
91 |
92 |
93 |
94 |
95 |
96 | {translate('patient.id')}
97 | {translate('patient.name')}
98 | {translate('patient.gender')}
99 | {translate('patient.birthdate')}
100 | {translate('patient.age')}
101 | {translate('patient.imagesCount')}
102 |
103 |
104 |
105 |
106 | {
107 | this.state.patients.map(patient => {
108 | return (
109 |
110 |
111 | {patient['patient_id'] || translate('patient.anonymized')}
112 |
113 |
114 | {patient['patient_name'] || translate('patient.anonymized')}
115 |
116 |
117 | {patient['patient_sex'] || translate('patient.anonymized')}
118 |
119 |
120 | {patient['patient_birthdate'] || translate('patient.anonymized')}
121 |
122 |
123 | {patient['patient_age'] || translate('patient.anonymized')}
124 |
125 |
126 | {patient['images_count']}
127 |
128 |
129 |
131 |
132 |
133 | );
134 | })
135 | }
136 |
137 |
138 |
139 |
140 |
141 | )
142 | }
143 |
144 |
145 | );
146 | }
147 | }
148 |
149 | export default PatientsPage;
--------------------------------------------------------------------------------
/ndicom_client/src/pages/PluginsPage.js:
--------------------------------------------------------------------------------
1 | import MenuContainer from "../components/common/MenuContainer";
2 | import React, {Component} from "react";
3 | import {Translate} from 'react-localize-redux';
4 | import PluginsService from "../services/PluginsService";
5 | import {Button, Divider, Dropdown, Form, Header, Message, Segment, Select} from "semantic-ui-react";
6 | import 'semantic-ui-css/semantic.min.css';
7 | import PluginItem from "../components/pluginsPage/PluginItem";
8 | import * as axios from 'axios';
9 |
10 | const PLUGINS_REPO_META_URL = "https://raw.githubusercontent.com/reactmed/neurdicom-plugins/master/REPO_META.json";
11 |
12 | const patientMatcherOptions = [
13 | {key: 'EXACT', text: 'Exact equals', value: 'EXACT'},
14 | {key: 'STARTS_WITH', text: 'Starts with', value: 'STARTS_WITH'},
15 | {key: 'ENDS_WITH', text: 'Ends with', value: 'ENDS_WITH'},
16 | {key: 'FUZZY', text: 'Fuzzy matching', value: 'FUZZY'},
17 | ];
18 | const options = [
19 | {key: 'DX', text: 'DX (Digital Radiography)', value: 'DX'},
20 | {key: 'MR', text: 'MR (Magnetic Resonance)', value: 'MR'},
21 | {key: 'CT', text: 'CT (Computer Tomography)', value: 'CT'},
22 | {key: 'US', text: 'US (Ultrasound)', value: 'US'},
23 | {key: 'ECG', text: 'ECG (Electrocardiography)', value: 'ECG'},
24 | {key: 'XA', text: 'XA (X-Ray)', value: 'XA'},
25 | {key: 'OT', text: 'OT (Other)', value: 'OT'},
26 | ];
27 |
28 | class PluginsPage extends Component {
29 | constructor(props) {
30 | super(props);
31 | this.state = {
32 | plugins: []
33 | };
34 | this.setState = this.setState.bind(this);
35 | }
36 |
37 | componentWillMount = () => {
38 | PluginsService.findPlugins(installedPlugins => {
39 | installedPlugins = installedPlugins.reduce((pluginsMap, plugin) => {
40 | pluginsMap[plugin.name] = plugin;
41 | return pluginsMap;
42 | }, {});
43 | axios.get(PLUGINS_REPO_META_URL).then(allPlugins => {
44 | allPlugins = allPlugins.data.plugins.reduce((pluginsMap, plugin) => {
45 | pluginsMap[plugin.name] = plugin.meta;
46 | return pluginsMap;
47 | }, {});
48 | this.setState({plugins: {...allPlugins, ...installedPlugins}});
49 | });
50 | });
51 | };
52 |
53 | onDeletePlugin = (plugin) => {
54 | axios.delete(
55 | `/api/plugins/${plugin['id']}`
56 | ).then((response) => {
57 | PluginsService.findPlugins(installedPlugins => {
58 | installedPlugins = installedPlugins.reduce((pluginsMap, plugin) => {
59 | pluginsMap[plugin.name] = plugin;
60 | return pluginsMap;
61 | }, {});
62 | axios.get(PLUGINS_REPO_META_URL).then(allPlugins => {
63 | allPlugins = allPlugins.data.plugins.reduce((pluginsMap, plugin) => {
64 | pluginsMap[plugin.name] = plugin.meta;
65 | return pluginsMap;
66 | }, {});
67 | this.setState({plugins: {...allPlugins, ...installedPlugins}});
68 | });
69 | });
70 | }).catch((err) => {
71 | alert(err.response.data['message']);
72 | this.setState({});
73 | })
74 | };
75 |
76 | onInstallPlugin = (plugin) => {
77 | axios.post(
78 | `/api/plugins/${plugin['name']}/install`
79 | ).then((response) => {
80 | PluginsService.findPlugins(installedPlugins => {
81 | installedPlugins = installedPlugins.reduce((pluginsMap, plugin) => {
82 | pluginsMap[plugin.name] = plugin;
83 | return pluginsMap;
84 | }, {});
85 | axios.get(PLUGINS_REPO_META_URL).then(allPlugins => {
86 | allPlugins = allPlugins.data.plugins.reduce((pluginsMap, plugin) => {
87 | pluginsMap[plugin.name] = plugin.meta;
88 | return pluginsMap;
89 | }, {});
90 | this.setState({plugins: {...allPlugins, ...installedPlugins}});
91 | });
92 | });
93 | }).catch((err) => {
94 | alert(err.response.data['message']);
95 | this.setState({});
96 | })
97 | };
98 |
99 | render() {
100 | const plugins = this.state.plugins;
101 | if (plugins && Object.keys(plugins).length > 0) {
102 | return (
103 |
104 |
105 | {
106 | (translate) => (
107 |
108 |
110 |
116 |
122 |
123 |
124 | {
125 | Object.keys(plugins).map(pluginName => {
126 | const plugin = plugins[pluginName];
127 | plugin['name'] = pluginName;
128 | return (
129 |
131 | )
132 | })
133 | }
134 |
135 | )
136 | }
137 |
138 |
139 | )
140 | }
141 | else {
142 | return (
143 |
144 |
146 |
147 | )
148 | }
149 | }
150 | }
151 |
152 | export default PluginsPage;
--------------------------------------------------------------------------------
/ndicom_client/src/pages/SeriesViewerPage.js:
--------------------------------------------------------------------------------
1 | import React, {Component} from 'react';
2 | import {
3 | Grid
4 | } from "semantic-ui-react";
5 | import DicomService from "../services/DicomService";
6 | import DicomViewer from "../components/common/DicomViewer";
7 | import ControlPanel from "../components/seriesViewerPage/ControlPanel";
8 |
9 |
10 | class SeriesViewerPage extends Component {
11 | constructor(props) {
12 | super(props);
13 | this.state = {
14 | instances: [],
15 | seriesId: props.match.params.id,
16 | instanceTags: {},
17 | index: 0,
18 | instance: {},
19 | showTags: false,
20 | playTimerId: null,
21 | isLoaded: false,
22 | rotation: null,
23 | colorScale: 'main',
24 | animation: false,
25 | viewMode: 'one',
26 | animationId: undefined
27 | };
28 | this.setState = this.setState.bind(this);
29 | }
30 |
31 | componentWillMount() {
32 | const seriesId = this.state.seriesId;
33 | if (!this.state.isLoaded) {
34 | DicomService.findInstancesBySeriesId(seriesId, instances => {
35 | this.setState({instances: instances, isLoaded: true});
36 | });
37 | }
38 | }
39 |
40 |
41 | play = () => {
42 | if (!this.state.animationId) {
43 | const nextInstance = this.nextInstance;
44 | const state = this.state;
45 | const animate = (function () {
46 | if (!state.animation) {
47 | console.log('ATOP');
48 | return;
49 | }
50 | setTimeout(function () {
51 | nextInstance();
52 | state.animationId = requestAnimationFrame(animate);
53 | }, 1000 / 4)
54 | }).bind(this);
55 | state.animation = true;
56 | state.animationId = requestAnimationFrame(animate);
57 | }
58 | };
59 |
60 | stop = () => {
61 | if (this.state.animationId) {
62 | const state = this.state;
63 | state.animation = false;
64 | cancelAnimationFrame(state.animationId);
65 | state.animationId = undefined;
66 | }
67 | };
68 |
69 | prevInstance = () => {
70 | const currentInstanceId = this.state.index;
71 | const instancesCount = (this.state.instances || []).length;
72 | if (instancesCount === 0)
73 | return;
74 | if (currentInstanceId === 0) {
75 | this.setState({index: instancesCount - 1, rotation: null});
76 | }
77 | else {
78 | this.setState({index: currentInstanceId - 1, rotation: null});
79 | }
80 | };
81 |
82 | nextInstance = () => {
83 | const currentInstanceId = this.state.index;
84 | const instancesCount = (this.state.instances || []).length;
85 | if (instancesCount === 0)
86 | return;
87 | if (currentInstanceId + 1 === instancesCount)
88 | this.setState({index: 0, rotation: null});
89 | else
90 | this.setState({index: currentInstanceId + 1, rotation: null});
91 | };
92 |
93 | showTags = () => {
94 | const instances = this.state.instances;
95 | if (instances) {
96 | const instanceId = instances[this.state.index]['id'];
97 | DicomService.findTagsByInstanceId(instanceId, tags => {
98 | this.setState({instanceTags: tags});
99 | });
100 | }
101 | };
102 |
103 | rotateLeft = () => {
104 | this.setState({rotation: 'left'});
105 | };
106 |
107 | rotateRight = () => {
108 | this.setState({rotation: 'right'});
109 | };
110 |
111 | setColorScale = (e, d) => {
112 | this.setState({colorScale: d.value})
113 | };
114 |
115 | setViewMode = (e, d) => {
116 | this.setState({viewMode: d.value})
117 | }
118 |
119 | onApplyPlugin = (pluginId) => {
120 | if (pluginId) {
121 | const instance = this.state.instances[this.state.index];
122 | console.log(instance);
123 | this.props.history.push(`/instances/${instance['id']}/process/${pluginId}`);
124 | }
125 | };
126 |
127 |
128 | render() {
129 | const instances = this.state.instances;
130 | if (instances && instances.length > 0) {
131 | const index = this.state.index;
132 | const viewMode = this.state.viewMode;
133 | if (viewMode === 'one') {
134 | const viewerProps = {
135 | style: {
136 | height: window.innerHeight
137 | },
138 | instance: instances[index],
139 | rotation: this.state.rotation,
140 | colorScale: this.state.colorScale,
141 | };
142 | return (
143 | this.onKeyPress(event)}>
146 | {
147 | this.props.history.push('/studies')
148 | }} onNextInstance={this.nextInstance} onPrevInstance={this.prevInstance}
149 | onSetColorScale={this.setColorScale} onRotateLeft={this.rotateLeft}
150 | onRotateRight={this.rotateRight} onSetViewMode={this.setViewMode}
151 | onApplyPlugin={this.onApplyPlugin}
152 | />
153 |
154 |
155 | );
156 | }
157 | else if (viewMode === 'two') {
158 | const viewerProps = {
159 | style: {
160 | height: window.innerHeight
161 | },
162 | rotation: this.state.rotation,
163 | colorScale: this.state.colorScale,
164 | };
165 | const instance1 = instances[index];
166 | const instance2 = instances[(index + 1) % instances.length];
167 | return (
168 | this.onKeyPress(event)}>
171 | {
172 | this.props.history.push('/studies')
173 | }} onNextInstance={this.nextInstance} onPrevInstance={this.prevInstance}
174 | onSetColorScale={this.setColorScale} onRotateLeft={this.rotateLeft}
175 | onRotateRight={this.rotateRight} onSetViewMode={this.setViewMode}
176 | onApplyPlugin={this.onApplyPlugin}
177 | />
178 |
179 |
180 |
181 |
182 |
183 |
184 |
185 |
186 |
187 |
188 |
189 | );
190 | }
191 | }
192 | else {
193 | return (
194 | this.onKeyPress(event)}>
197 | {
198 | this.props.history.push('/studies')
199 | }} onNextInstance={this.nextInstance} onPrevInstance={this.prevInstance}
200 | onSetColorScale={this.setColorScale} onRotateLeft={this.rotateLeft}
201 | onRotateRight={this.rotateRight} onSetViewMode={this.setViewMode}
202 | onApplyPlugin={this.onApplyPlugin}
203 | />
204 |
205 | );
206 | }
207 | }
208 |
209 | onKeyPress = (event) => {
210 | if (event.key === 'ArrowLeft') {
211 | this.prevInstance();
212 | }
213 | else if (event.key === 'ArrowRight') {
214 | this.nextInstance();
215 | }
216 | };
217 | }
218 |
219 |
220 | export default SeriesViewerPage;
--------------------------------------------------------------------------------
/ndicom_client/src/pages/StudiesPage.js:
--------------------------------------------------------------------------------
1 | import React, {Component} from 'react';
2 | import {Translate} from 'react-localize-redux';
3 | import {connect} from 'react-redux';
4 | import {
5 | Button, Checkbox, Container, Dropdown, Form, Icon, Input, Menu, Radio, Segment, Select, Sidebar, Table,
6 | TextArea, TransitionablePortal as visible
7 | } from "semantic-ui-react";
8 | import StudiesService from "../services/DicomService";
9 | import {Link} from "react-router-dom";
10 | import MenuContainer from "../components/common/MenuContainer";
11 |
12 | const patientMatcherOptions = [
13 | {key: 'EXACT', text: 'Точный поиск', value: 'EXACT'},
14 | {key: 'STARTS_WITH', text: 'Начинается с...', value: 'STARTS_WITH'},
15 | {key: 'ENDS_WITH', text: 'Заканчивается...', value: 'ENDS_WITH'},
16 | {key: 'FUZZY', text: 'Нечеткий поиск', value: 'FUZZY'},
17 | ];
18 | const options = [
19 | {key: 'DX', text: 'DX', value: 'DX'},
20 | {key: 'MR', text: 'MR', value: 'MR'},
21 | {key: 'CT', text: 'CT', value: 'CT'},
22 | {key: 'US', text: 'US', value: 'US'},
23 | {key: 'ECG', text: 'ECG', value: 'ECG'},
24 | {key: 'XA', text: 'XA', value: 'XA'},
25 | {key: 'OT', text: 'OT', value: 'OT'},
26 | ];
27 |
28 | class StudiesPage extends Component {
29 | constructor(props) {
30 | super(props);
31 | this.state = {
32 | studies: []
33 | };
34 | this.setState.bind(this);
35 | }
36 |
37 | componentWillMount() {
38 | StudiesService.findStudies(studyList => {
39 | console.log(studyList);
40 | this.setState({studies: studyList})
41 | });
42 | }
43 |
44 | render() {
45 | const {activeItem} = this.state;
46 | return (
47 |
48 |
49 | {
50 | (translate) => (
51 |
52 |
54 | }
58 | icon='search'
59 | iconPosition='left'
60 | placeholder={translate('patient.name')}
61 | />
62 | }
66 | icon='search'
67 | iconPosition='left'
68 | placeholder={translate('study.id')}
69 | />
70 |
73 |
74 |
75 |
76 |
77 |
78 | {translate('patient.name')}
79 | {translate('study.id')}
80 | {translate('study.date')}
81 | {translate('study.description')}
82 | {translate('study.modality')}
83 | {translate('study.imagesCount')}
84 |
85 |
86 |
87 | {
88 | this.state.studies.map(study => {
89 | return (
90 |
91 |
92 | {study['patient']['patient_name']}
93 |
94 |
95 |
96 | {study['study_instance_uid']}
97 |
98 |
99 |
100 | {study['study_date']}
101 |
102 |
103 | {study['study_description']}
104 |
105 |
106 | {study['modalities'].join(', ')}
107 |
108 |
109 | {study['images_count']}
110 |
111 |
112 | );
113 | })
114 | }
115 |
116 |
117 |
118 | )
119 | }
120 |
121 |
122 | )
123 | }
124 | }
125 |
126 | const mapStateToProps = (state) => ({});
127 |
128 | const mapDispatchToProps = (dispatch) => ({});
129 |
130 | export default connect(mapStateToProps, mapDispatchToProps)(StudiesPage);
--------------------------------------------------------------------------------
/ndicom_client/src/pages/StudySeriesPage.js:
--------------------------------------------------------------------------------
1 | import React, {Component} from 'react';
2 | import {
3 | Button, Checkbox, Divider, Dropdown, Form, Grid, Header, Input, Message, Radio, Segment, Select, Table,
4 | TextArea
5 | } from "semantic-ui-react";
6 | import StudiesService from "../services/DicomService";
7 | import MenuContainer from "../components/common/MenuContainer";
8 | import {Link} from "react-router-dom";
9 | import {Translate} from "react-localize-redux";
10 |
11 | const options = [
12 | {key: 'DX', text: 'DX', value: 'DX'},
13 | {key: 'MR', text: 'MR', value: 'MR'},
14 | {key: 'CT', text: 'CT', value: 'CT'},
15 | {key: 'US', text: 'US', value: 'US'},
16 | {key: 'ECG', text: 'ECG', value: 'ECG'},
17 | {key: 'XA', text: 'XA', value: 'XA'},
18 | {key: 'OT', text: 'OT', value: 'OT'},
19 | ];
20 |
21 | const patientMatcherOptions = [
22 | {key: 'EXACT', text: 'Точный поиск', value: 'EXACT'},
23 | {key: 'STARTS_WITH', text: 'Начинается с', value: 'STARTS_WITH'},
24 | {key: 'ENDS_WITH', text: 'Заканчивается', value: 'ENDS_WITH'},
25 | {key: 'FUZZY', text: 'Нечеткий поиск', value: 'FUZZY'},
26 | ];
27 |
28 | export default class StudySeriesPage extends Component {
29 | constructor(props) {
30 | super(props);
31 | this.state = {
32 | study: {},
33 | studyId: props.match.params.id
34 | };
35 | this.setState.bind(this);
36 | }
37 |
38 | componentWillMount() {
39 | StudiesService.findStudyById(this.state.studyId, study => {
40 | this.setState({study: study})
41 | });
42 | }
43 |
44 | render() {
45 | const study = this.state.study;
46 | const series = this.state.study['series'];
47 | if (series && series.length > 0) {
48 | return (
49 |
50 |
51 | {
52 | (translate) => (
53 |
54 |
55 |
56 | {translate('patient.patient')}
58 |
59 | {study['patient']['patient_name'] || translate('patient.anonymized')}
60 | {translate('patient.id')}: {study['patient']['patient_id']}
61 |
62 | {translate('patient.gender')}: {study['patient']['patient_sex']}
63 |
64 | {translate('patient.age')}: {study['patient']['patient_age']}
65 |
66 | {translate('study.study')}
68 |
69 | {study['study_description']}
70 | {translate('study.date')}: {study['study_date'] || '––'}
71 |
72 | {translate('study.referringPhysician')}: {study['referring_physician'] || '––'}
73 |
74 |
75 |
76 |
78 | }
83 | icon='search'
84 | iconPosition='left'
85 | placeholder={translate('study.id')}
86 | />
87 |
90 |
91 |
92 | {
93 | series.map((seriesItem, index) => {
94 | return (
95 |
96 |
98 | {seriesItem['protocol_name'] || `${translate('series.series')} ${index + 1}`}
99 |
100 |
101 | {translate('series.description')}: {seriesItem['series_description'] || '––'}
102 |
103 | {translate('series.modality')}: {seriesItem['modality']}
104 |
105 | {translate('series.bodyPartExamined')}: {seriesItem['body_part_examined'] || '––'}
106 |
107 | {translate('series.patientPosition')}: {seriesItem['patient_position']}
108 |
109 | {translate('series.seriesNumber')}: {seriesItem['series_number']}
110 |
111 | {translate('imagesCount')}: {seriesItem['images_count']}
112 |
113 |
114 |
116 |
117 |
118 |
119 | );
120 | })
121 | }
122 |
123 |
124 |
125 | )
126 | }
127 |
128 |
129 | );
130 | }
131 | else {
132 | return (
133 |
134 |
135 |
136 | );
137 | }
138 | }
139 | }
--------------------------------------------------------------------------------
/ndicom_client/src/pages/UploadDicomPage.js:
--------------------------------------------------------------------------------
1 | import React, {Component} from 'react';
2 | import {Button, Dimmer, Divider, Form, Grid, Header, Segment, Loader} from "semantic-ui-react";
3 | import axios, {post} from 'axios';
4 | import MenuContainer from "../components/common/MenuContainer";
5 | import Dropzone from "react-dropzone";
6 |
7 | class UploadDicomPage extends Component {
8 | constructor(props) {
9 | super(props);
10 | this.setState = this.setState.bind(this);
11 | this.state = {
12 | files: [],
13 | isPending: false
14 | }
15 | }
16 |
17 | addFile = (files) => {
18 | console.log(files);
19 | this.setState(
20 | {
21 | files: files
22 | }
23 | )
24 | };
25 |
26 | uploadFiles = () => {
27 | const files = this.state.files;
28 | if(files.length <= 0)
29 | return;
30 | const form = new FormData();
31 | for(let i = 0; i < files.length; i++){
32 | const file = files[i];
33 | form.append(`file${i}`, file, file.name);
34 | }
35 | const config = {
36 | headers: { 'content-type': 'multipart/form-data' }
37 | };
38 | axios.post('/api/instances/upload', form, config).then((resp) => {
39 | alert('Все файлы загружены!');
40 | this.setState({
41 | isPending: false
42 | });
43 | }).catch((resp) => {
44 | alert('Файл не могут быть загружены!');
45 | this.setState({
46 | isPending: false
47 | });
48 | });
49 | this.setState({
50 | isPending: true
51 | });
52 | };
53 |
54 | render() {
55 | const files = this.state.files;
56 | const isPending = this.state.isPending;
57 | return (
58 |
59 |
60 | {
61 | isPending ? (
62 |
63 |
64 | Файлы загружаются...
65 |
66 |
67 | ) : (
68 |
69 | )
70 | }
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 | Перетащите снимки сюда
79 |
80 |
81 |
82 |
83 |
84 |
85 | {
86 | files.map(file => {
87 | return (
88 |
89 |
90 | {file.name} - {Math.round(file.size / (8.0 * 1024.0) * 100) / 100} Кб
91 |
92 |
93 | )
94 | })
95 | }
96 |
97 |
98 | )
99 | }
100 | }
101 |
102 | export default UploadDicomPage;
--------------------------------------------------------------------------------
/ndicom_client/src/pages/series_viewer_page.css:
--------------------------------------------------------------------------------
1 | body {
2 | background: black;
3 | }
--------------------------------------------------------------------------------
/ndicom_client/src/registerServiceWorker.js:
--------------------------------------------------------------------------------
1 | // In production, we register a service worker to serve assets from local cache.
2 |
3 | // This lets the app load faster on subsequent visits in production, and gives
4 | // it offline capabilities. However, it also means that developers (and users)
5 | // will only see deployed updates on the "N+1" visit to a page, since previously
6 | // cached resources are updated in the background.
7 |
8 | // To learn more about the benefits of this model, read https://goo.gl/KwvDNy.
9 | // This link also includes instructions on opting out of this behavior.
10 |
11 | const isLocalhost = Boolean(
12 | window.location.hostname === 'localhost' ||
13 | // [::1] is the IPv6 localhost address.
14 | window.location.hostname === '[::1]' ||
15 | // 127.0.0.1/8 is considered localhost for IPv4.
16 | window.location.hostname.match(
17 | /^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/
18 | )
19 | );
20 |
21 | export default function register() {
22 | if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) {
23 | // The URL constructor is available in all browsers that support SW.
24 | const publicUrl = new URL(process.env.PUBLIC_URL, window.location);
25 | if (publicUrl.origin !== window.location.origin) {
26 | // Our service worker won't work if PUBLIC_URL is on a different origin
27 | // from what our page is served on. This might happen if a CDN is used to
28 | // serve assets; see https://github.com/facebookincubator/create-react-app/issues/2374
29 | return;
30 | }
31 |
32 | window.addEventListener('load', () => {
33 | const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`;
34 |
35 | if (isLocalhost) {
36 | // This is running on localhost. Lets check if a service worker still exists or not.
37 | checkValidServiceWorker(swUrl);
38 |
39 | // Add some additional logging to localhost, pointing developers to the
40 | // service worker/PWA documentation.
41 | navigator.serviceWorker.ready.then(() => {
42 | console.log(
43 | 'This web app is being served cache-first by a service ' +
44 | 'worker. To learn more, visit https://goo.gl/SC7cgQ'
45 | );
46 | });
47 | } else {
48 | // Is not local host. Just register service worker
49 | registerValidSW(swUrl);
50 | }
51 | });
52 | }
53 | }
54 |
55 | function registerValidSW(swUrl) {
56 | navigator.serviceWorker
57 | .register(swUrl)
58 | .then(registration => {
59 | registration.onupdatefound = () => {
60 | const installingWorker = registration.installing;
61 | installingWorker.onstatechange = () => {
62 | if (installingWorker.state === 'installed') {
63 | if (navigator.serviceWorker.controller) {
64 | // At this point, the old content will have been purged and
65 | // the fresh content will have been added to the cache.
66 | // It's the perfect time to display a "New content is
67 | // available; please refresh." message in your web app.
68 | console.log('New content is available; please refresh.');
69 | } else {
70 | // At this point, everything has been precached.
71 | // It's the perfect time to display a
72 | // "Content is cached for offline use." message.
73 | console.log('Content is cached for offline use.');
74 | }
75 | }
76 | };
77 | };
78 | })
79 | .catch(error => {
80 | console.error('Error during service worker registration:', error);
81 | });
82 | }
83 |
84 | function checkValidServiceWorker(swUrl) {
85 | // Check if the service worker can be found. If it can't reload the page.
86 | fetch(swUrl)
87 | .then(response => {
88 | // Ensure service worker exists, and that we really are getting a JS file.
89 | if (
90 | response.status === 404 ||
91 | response.headers.get('content-type').indexOf('javascript') === -1
92 | ) {
93 | // No service worker found. Probably a different app. Reload the page.
94 | navigator.serviceWorker.ready.then(registration => {
95 | registration.unregister().then(() => {
96 | window.location.reload();
97 | });
98 | });
99 | } else {
100 | // Service worker found. Proceed as normal.
101 | registerValidSW(swUrl);
102 | }
103 | })
104 | .catch(() => {
105 | console.log(
106 | 'No internet connection found. App is running in offline mode.'
107 | );
108 | });
109 | }
110 |
111 | export function unregister() {
112 | if ('serviceWorker' in navigator) {
113 | navigator.serviceWorker.ready.then(registration => {
114 | registration.unregister();
115 | });
116 | }
117 | }
118 |
--------------------------------------------------------------------------------
/ndicom_client/src/services/DicomNodeService.js:
--------------------------------------------------------------------------------
1 | import * as queryString from 'query-string';
2 |
3 | const DICOM_NODES_ROOT_URL = '/api/dicom_nodes';
4 |
5 | export default class DicomNodeService {
6 | static findDicomNodes(f, params = {}) {
7 | fetch(
8 | `${DICOM_NODES_ROOT_URL}?${queryString.stringify(params)}`
9 | ).then(function (response) {
10 | if (response.status >= 200 && response.status < 300) {
11 | return response;
12 | }
13 | console.log(response.status);
14 | const error = new Error(`HTTP Error ${response.statusText}`);
15 | error.status = response.statusText;
16 | error.response = response;
17 | throw error;
18 | }).then(response => {
19 | return response.json();
20 | }).then(f);
21 | }
22 | }
--------------------------------------------------------------------------------
/ndicom_client/src/services/DicomService.js:
--------------------------------------------------------------------------------
1 | import * as queryString from 'query-string';
2 |
3 | const PATIENTS_ROOT_URL = '/api/patients';
4 | const STUDIES_ROOT_URL = '/api/studies';
5 | const SERIES_ROOT_URL = '/api/series';
6 | const INSTANCES_ROOT_URL = '/api/instances';
7 |
8 | export default class DicomService {
9 | static findPatients(f, params = {}) {
10 | fetch(
11 | `${PATIENTS_ROOT_URL}?${queryString.stringify(params)}`
12 | ).then(function (response) {
13 | if (response.status >= 200 && response.status < 300) {
14 | return response;
15 | }
16 | console.log(response.status);
17 | const error = new Error(`HTTP Error ${response.statusText}`);
18 | error.status = response.statusText;
19 | error.response = response;
20 | throw error;
21 | }).then(response => {
22 | return response.json();
23 | }).then(f);
24 | }
25 |
26 | static findStudies(f) {
27 | fetch(
28 | STUDIES_ROOT_URL
29 | ).then(function (response) {
30 | if (response.status >= 200 && response.status < 300) {
31 | return response;
32 | }
33 | console.log(response.status);
34 | const error = new Error(`HTTP Error ${response.statusText}`);
35 | error.status = response.statusText;
36 | error.response = response;
37 | throw error;
38 | }).then(response => {
39 | return response.json();
40 | }).then(f);
41 | }
42 |
43 | static findStudiesByPatient(f, patientId) {
44 | fetch(
45 | `/api/patients/${patientId}/studies`
46 | ).then(function (response) {
47 | if (response.status >= 200 && response.status < 300) {
48 | return response;
49 | }
50 | console.log(response.status);
51 | const error = new Error(`HTTP Error ${response.statusText}`);
52 | error.status = response.statusText;
53 | error.response = response;
54 | throw error;
55 | }).then(response => {
56 | return response.json();
57 | }).then(f);
58 | }
59 |
60 | static findStudyById(studyId, f) {
61 | fetch(
62 | `${STUDIES_ROOT_URL}/${studyId}`
63 | ).then(function (response) {
64 | if (response.status >= 200 && response.status < 300) {
65 | return response;
66 | }
67 | console.log(response.status);
68 | const error = new Error(`HTTP Error ${response.statusText}`);
69 | error.status = response.statusText;
70 | error.response = response;
71 | throw error;
72 | }).then(response => {
73 | return response.json();
74 | }).then(f);
75 | }
76 |
77 | static findSeries(f) {
78 | fetch(
79 | SERIES_ROOT_URL
80 | ).then(function (response) {
81 | if (response.status >= 200 && response.status < 300) {
82 | return response;
83 | }
84 | console.log(response.status);
85 | const error = new Error(`HTTP Error ${response.statusText}`);
86 | error.status = response.statusText;
87 | error.response = response;
88 | throw error;
89 | }).then(response => {
90 | return response.json();
91 | }).then(f);
92 | }
93 |
94 | static findInstances(f) {
95 | fetch(
96 | INSTANCES_ROOT_URL
97 | ).then(function (response) {
98 | if (response.status >= 200 && response.status < 300) {
99 | return response;
100 | }
101 | console.log(response.status);
102 | const error = new Error(`HTTP Error ${response.statusText}`);
103 | error.status = response.statusText;
104 | error.response = response;
105 | throw error;
106 | }).then(response => {
107 | return response.json();
108 | }).then(f);
109 | }
110 |
111 | static findStudiesByPatientId(patientId, f) {
112 | fetch(
113 | `${PATIENTS_ROOT_URL}/${patientId}/studies`
114 | ).then(function (response) {
115 | if (response.status >= 200 && response.status < 300) {
116 | return response;
117 | }
118 | console.log(response.status);
119 | const error = new Error(`HTTP Error ${response.statusText}`);
120 | error.status = response.statusText;
121 | error.response = response;
122 | throw error;
123 | }).then(response => {
124 | return response.json();
125 | }).then(f);
126 | }
127 |
128 | static findSeriesByStudyId(studyId, f) {
129 | fetch(
130 | `${STUDIES_ROOT_URL}/${studyId}/series`
131 | ).then(function (response) {
132 | if (response.status >= 200 && response.status < 300) {
133 | return response;
134 | }
135 | console.log(response.status);
136 | const error = new Error(`HTTP Error ${response.statusText}`);
137 | error.status = response.statusText;
138 | error.response = response;
139 | throw error;
140 | }).then(response => {
141 | return response.json();
142 | }).then(f);
143 | }
144 |
145 | static findInstancesBySeriesId(seriesId, f) {
146 | fetch(
147 | `${SERIES_ROOT_URL}/${seriesId}/instances`
148 | ).then(function (response) {
149 | if (response.status >= 200 && response.status < 300) {
150 | return response;
151 | }
152 | console.log(response.status);
153 | const error = new Error(`HTTP Error ${response.statusText}`);
154 | error.status = response.statusText;
155 | error.response = response;
156 | throw error;
157 | }).then(response => {
158 | return response.json();
159 | }).then(f);
160 | }
161 |
162 | static findInstancesById(instanceId, f) {
163 | fetch(
164 | `${INSTANCES_ROOT_URL}/${instanceId}`
165 | ).then(function (response) {
166 | if (response.status >= 200 && response.status < 300) {
167 | return response;
168 | }
169 | console.log(response.status);
170 | const error = new Error(`HTTP Error ${response.statusText}`);
171 | error.status = response.statusText;
172 | error.response = response;
173 | throw error;
174 | }).then(response => {
175 | return response.json();
176 | }).then(f);
177 | }
178 |
179 | static findTagsByInstanceId(instanceId, f) {
180 | fetch(
181 | `${INSTANCES_ROOT_URL}/${instanceId}/tags`
182 | ).then(function (response) {
183 | if (response.status >= 200 && response.status < 300) {
184 | return response;
185 | }
186 | console.log(response.status);
187 | const error = new Error(`HTTP Error ${response.statusText}`);
188 | error.status = response.statusText;
189 | error.response = response;
190 | throw error;
191 | }).then(response => {
192 | return response.json();
193 | }).then(f);
194 | }
195 | }
--------------------------------------------------------------------------------
/ndicom_client/src/services/PluginsService.js:
--------------------------------------------------------------------------------
1 | const PLUGINS_ROOT_URL = '/api/plugins';
2 |
3 | export default class PluginsService {
4 | static findPlugins(f) {
5 | return fetch(
6 | PLUGINS_ROOT_URL
7 | ).then(function (response) {
8 | if (response.status >= 200 && response.status < 300) {
9 | return response;
10 | }
11 | console.log(response.status);
12 | const error = new Error(`HTTP Error ${response.statusText}`);
13 | error.status = response.statusText;
14 | error.response = response;
15 | throw error;
16 | }).then(response => {
17 | return response.json();
18 | }).then(f);
19 | }
20 |
21 | static findPluginById(f, id) {
22 | fetch(
23 | `${PLUGINS_ROOT_URL}/${id}`
24 | ).then(function (response) {
25 | if (response.status >= 200 && response.status < 300) {
26 | return response;
27 | }
28 | console.log(response.status);
29 | const error = new Error(`HTTP Error ${response.statusText}`);
30 | error.status = response.statusText;
31 | error.response = response;
32 | throw error;
33 | }).then(response => {
34 | return response.json();
35 | }).then(f);
36 | }
37 | }
--------------------------------------------------------------------------------
/ndicom_server/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM python:3
2 | ENV PYTHONUNBUFFERED 1
3 |
4 | RUN mkdir /code
5 | WORKDIR /code
6 | ADD requirements.txt /code/
7 | RUN pip install -r requirements.txt
8 | RUN pip install git+git://github.com/pydicom/pydicom.git
9 | RUN pip install git+git://github.com/pydicom/pynetdicom3.git
10 | ADD . /code/
11 | CMD python manage.py migrate
12 | CMD python manage.py clear_dicom
13 | CMD python manage.py store_dicom /images
14 | EXPOSE 8080
15 | ENTRYPOINT ["python", "app.py"]
--------------------------------------------------------------------------------
/ndicom_server/app.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | import logging
3 | import signal
4 |
5 | from tornado.httpclient import AsyncHTTPClient
6 |
7 | logging.basicConfig(format='%(levelname)s: %(asctime)s - %(message)s', datefmt='%d.%m.%Y %I:%M:%S')
8 |
9 | import django
10 | import sys
11 | from pydicom.uid import *
12 | from pynetdicom3 import *
13 |
14 | os.environ['DJANGO_SETTINGS_MODULE'] = 'neurdicom.settings'
15 | django.setup()
16 |
17 | from neurdicom.urls import *
18 | import neurdicom.settings as settings
19 |
20 | from tornado.options import options, define, parse_command_line
21 | from apps.dicom_ws.handlers import *
22 | from apps.users.handlers import *
23 | import tornado.httpserver
24 | import tornado.ioloop
25 | import tornado.web
26 | import tornado.wsgi
27 |
28 | define('aet', type=str, default='NEURDICOM')
29 | define('rest_port', type=int, default=8080)
30 | define('dicom_port', type=int, default=4242)
31 |
32 |
33 | def main():
34 | parse_command_line()
35 |
36 | rest_app = tornado.web.Application(
37 | [
38 | # Users
39 | (USER_AUTH_URL, UserAuthHandler),
40 | (USER_CHECK_URL, UserCheckHandler),
41 | (USER_LOGOUT_URL, UserLogoutHandler),
42 | (USER_DETAIL_URL, UserDetailHandler),
43 | (USER_LIST_URL, UserListHandler),
44 |
45 | # Patients
46 | (PATIENT_STUDIES_URL, PatientStudiesHandler),
47 | (PATIENT_DETAIL_URL, PatientDetailHandler),
48 | (PATIENT_LIST_URL, PatientListHandler),
49 |
50 | # Studies
51 | (STUDY_SERIES_URL, StudySeriesHandler),
52 | (STUDY_DETAIL_URL, StudyDetailHandler),
53 | (STUDY_LIST_URL, StudyListHandler),
54 |
55 | # Series
56 | (SERIES_INSTANCES_URL, SeriesInstancesHandler),
57 | (SERIES_DETAIL_URL, SeriesDetailHandler),
58 | (SERIES_LIST_URL, SeriesListHandler),
59 |
60 | # Instances
61 | (INSTANCE_PROCESS_URL, InstanceProcessHandler),
62 | (INSTANCE_TAGS_URL, InstanceTagsHandler),
63 | (INSTANCE_IMAGE_URL, InstanceImageHandler),
64 | (INSTANCE_RAW_URL, InstanceRawHandler),
65 | (INSTANCE_DETAIL_URL, InstanceDetailHandler),
66 | (INSTANCE_LIST_URL, InstanceListHandler),
67 | (INSTANCE_UPLOAD_URL, InstanceUploadHandler),
68 |
69 | # Dicom Nodes
70 | (DICOM_NODE_DETAIL_URL, DicomNodeDetailHandler),
71 | (DICOM_NODE_LIST_URL, DicomNodeListHandler),
72 | # (DICOM_NODE_ECHO_URL, DicomNodeEchoHandler),
73 | (DICOM_NODE_INSTANCES_URL, DicomNodeInstancesLoadHandler),
74 |
75 | # Plugins
76 | (PLUGIN_DETAIL_URL, PluginDetailHandler),
77 | (PLUGIN_LIST_URL, PluginListHandler),
78 | (PLUGIN_INSTALL_URL, InstallPluginHandler),
79 |
80 | # Media download
81 | (MEDIA_URL, tornado.web.StaticFileHandler, {'path': 'media'})
82 | ], cookie_secret=settings.SECRET_KEY)
83 | rest_port = options.rest_port or settings.DICOMWEB_SERVER['port']
84 | if settings.RUN_DICOM:
85 | new_pid = os.fork()
86 | if new_pid == 0:
87 | try:
88 | logging.info('DICOM server starting at port = %d' % settings.DICOM_SERVER['port'])
89 | dicom_server = DICOMServer(ae_title=settings.DICOM_SERVER['aet'], port=settings.DICOM_SERVER['port'],
90 | scp_sop_class=StorageSOPClassList + [VerificationSOPClass],
91 | transfer_syntax=UncompressedPixelTransferSyntaxes)
92 | dicom_server.start()
93 | except (KeyboardInterrupt, SystemExit):
94 | logging.info('DICOM server finishing...')
95 | logging.info('Child process exiting...')
96 | sys.exit(0)
97 | elif new_pid > 0:
98 | try:
99 | rest_server = tornado.httpserver.HTTPServer(rest_app)
100 | rest_server.bind(rest_port)
101 | rest_server.start()
102 | logging.info('Rest server starting at port = %d' % settings.DICOMWEB_SERVER['port'])
103 | tornado.ioloop.IOLoop.current().start()
104 | except (KeyboardInterrupt, SystemExit):
105 | logging.info('Rest server finishing...')
106 | os.kill(new_pid, signal.SIGINT)
107 | logging.info('Parent process exiting...')
108 | sys.exit(0)
109 | else:
110 | logging.error('Can not fork any processes')
111 | else:
112 | try:
113 | rest_server = tornado.httpserver.HTTPServer(rest_app)
114 | rest_server.bind(rest_port)
115 | rest_server.start()
116 | logging.info('Rest server starting at port = %d' % rest_port)
117 | tornado.ioloop.IOLoop.current().start()
118 | except (KeyboardInterrupt, SystemExit):
119 | logging.info('Rest server finishing...')
120 | logging.info('Parent process exiting...')
121 | sys.exit(0)
122 |
123 |
124 | if __name__ == '__main__':
125 | main()
126 |
--------------------------------------------------------------------------------
/ndicom_server/apps/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/reactmed/neurdicom/1b3f659ebd4679a913d9ec95bf28e55c059696f1/ndicom_server/apps/__init__.py
--------------------------------------------------------------------------------
/ndicom_server/apps/core/__init__.py:
--------------------------------------------------------------------------------
1 | from django.db.backends.signals import connection_created
2 |
3 |
4 | def activate_foreign_keys(sender, connection, **kwargs):
5 | if connection.vendor == 'sqlite':
6 | cursor = connection.cursor()
7 | cursor.execute('PRAGMA foreign_keys = ON;')
8 |
9 |
10 | connection_created.connect(activate_foreign_keys)
11 |
--------------------------------------------------------------------------------
/ndicom_server/apps/core/admin.py:
--------------------------------------------------------------------------------
1 | from django.contrib import admin
2 | from .models import *
3 |
4 |
5 | @admin.register(Patient)
6 | class PatientAdmin(admin.ModelAdmin):
7 | list_display = ('id', 'patient_id', 'patient_name', 'patient_sex', 'patient_birthdate')
8 |
9 |
10 | @admin.register(Study)
11 | class StudyAdmin(admin.ModelAdmin):
12 | list_display = ('id', 'study_id', 'study_instance_uid', 'study_description')
13 |
14 |
15 | @admin.register(Series)
16 | class SeriesAdmin(admin.ModelAdmin):
17 | list_display = ('id', 'series_instance_uid', 'series_number', 'modality', 'patient_position')
18 | list_filter = ('modality',)
19 |
20 |
21 | @admin.register(Instance)
22 | class InstanceAdmin(admin.ModelAdmin):
23 | list_display = ('id', 'sop_instance_uid', 'rows', 'columns', 'photometric_interpretation', 'image')
24 |
25 |
26 | @admin.register(Plugin)
27 | class PluginAdmin(admin.ModelAdmin):
28 | list_display = ('id', 'name', 'author', 'info', 'plugin')
29 |
--------------------------------------------------------------------------------
/ndicom_server/apps/core/apps.py:
--------------------------------------------------------------------------------
1 | from django.apps import AppConfig
2 |
3 |
4 | class CoreConfig(AppConfig):
5 | name = 'apps.core'
6 |
--------------------------------------------------------------------------------
/ndicom_server/apps/core/fields.py:
--------------------------------------------------------------------------------
1 | from django.db.models import FileField
2 | from django.db.models.fields.files import FieldFile, FileDescriptor
3 |
4 |
5 | class ZipFieldFile(FieldFile):
6 | pass
7 |
8 |
9 | class ZipFieldDescriptor(FileDescriptor):
10 | pass
11 |
12 |
13 | class ZipField(FileField):
14 | attr_class = ZipFieldFile
15 | descriptor_class = ZipFieldDescriptor
16 |
17 | def get_internal_type(self):
18 | return 'ZipFieldFile'
19 |
--------------------------------------------------------------------------------
/ndicom_server/apps/core/management/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/reactmed/neurdicom/1b3f659ebd4679a913d9ec95bf28e55c059696f1/ndicom_server/apps/core/management/__init__.py
--------------------------------------------------------------------------------
/ndicom_server/apps/core/management/commands/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/reactmed/neurdicom/1b3f659ebd4679a913d9ec95bf28e55c059696f1/ndicom_server/apps/core/management/commands/__init__.py
--------------------------------------------------------------------------------
/ndicom_server/apps/core/management/commands/clear_dicom.py:
--------------------------------------------------------------------------------
1 | from django.core.management import BaseCommand
2 |
3 | from apps.core.models import Instance, Series, Study, Patient
4 |
5 |
6 | class Command(BaseCommand):
7 | help = "Clear DICOM database"
8 |
9 | def handle(self, *args, **options):
10 | for instance in Instance.objects.all():
11 | instance.image.delete()
12 | instance.delete()
13 | Series.objects.all().delete()
14 | Study.objects.all().delete()
15 | Patient.objects.all().delete()
16 | self.stdout.write('Completed!')
17 |
--------------------------------------------------------------------------------
/ndicom_server/apps/core/management/commands/clear_media.py:
--------------------------------------------------------------------------------
1 | import os
2 | import shutil
3 | from neurdicom.settings import BASE_DIR, MEDIA_ROOT
4 |
5 | from django.core.management import BaseCommand
6 |
7 |
8 | class Command(BaseCommand):
9 | help = "Clear DICOM directory"
10 |
11 | def handle(self, *args, **options):
12 | media_directory = os.path.join(BASE_DIR, MEDIA_ROOT)
13 | shutil.rmtree(media_directory)
14 | os.mkdir(media_directory)
15 | self.stdout.write('Completed!')
16 |
--------------------------------------------------------------------------------
/ndicom_server/apps/core/management/commands/clear_plugins.py:
--------------------------------------------------------------------------------
1 | from django.core.management import BaseCommand
2 |
3 | from apps.core.models import Plugin
4 |
5 |
6 | class Command(BaseCommand):
7 | help = "Clear plugins database"
8 |
9 | def handle(self, *args, **options):
10 | for plugin in Plugin.objects.all():
11 | plugin.plugin.delete()
12 | plugin.delete()
13 | self.stdout.write('Completed!')
14 |
--------------------------------------------------------------------------------
/ndicom_server/apps/core/management/commands/install_plugins.py:
--------------------------------------------------------------------------------
1 | # from importlib.util import find_spec
2 | # from json import loads
3 | # from urllib.request import urlopen
4 | #
5 | # import pip
6 | # from django.core.management import BaseCommand, CommandParser
7 | # from github import Github
8 | #
9 | # from apps.core.models import Plugin
10 | # import neurdicom.settings as settings
11 | #
12 | # ORG = 'reactmed'
13 | # REPO = 'neurdicom-plugins'
14 | # META = 'META.json'
15 | # REPO_URL = 'git+git://github.com/reactmed/neurdicom-plugins.git'
16 | #
17 | #
18 | # class Command(BaseCommand):
19 | # help = "Install plugins from repository"
20 | #
21 | # def add_arguments(self, parser: CommandParser):
22 | # parser.add_argument('plugins', nargs='+', type=str)
23 | # parser.add_argument('--upgrade', action='store_true', dest='upgrade',
24 | # help='Upgrade plugins')
25 | # parser.add_argument('--clear', action='store_true', dest='clear',
26 | # help='Clear plugins')
27 | # parser.add_argument('--validate', action='store_true', dest='validate',
28 | # help='Validate plugins')
29 | # parser.add_argument('--index', action='store_true', dest='index',
30 | # help='Index plugins')
31 | #
32 | # def handle(self, *args, **options):
33 | # upgrade = options.get('upgrade', True)
34 | # clear = options.get('clear', False)
35 | # validate = options.get('validate', False)
36 | # index = options.get('index', False)
37 | # if clear:
38 | # self.stdout.write('Clear plugins')
39 | # for plugin in Plugin.objects.all():
40 | # plugin.plugin.delete()
41 | # plugin.delete()
42 | # g = Github(settings.GITHUB_TOKEN)
43 | # repo = g.get_organization(ORG).get_repo(REPO)
44 | # if index:
45 | # root = repo.get_contents('')
46 | # for member in root:
47 | # if member.type == 'dir' and member.path not in options['plugins']:
48 | # meta_url = repo.get_contents('%s/META.json' % member.path).download_url
49 | # with urlopen(meta_url) as meta_file:
50 | # meta = loads(meta_file.read())
51 | # plugin = Plugin(
52 | # author=meta['author'],
53 | # name=meta['name'],
54 | # display_name=meta['display_name'],
55 | # version=meta.get('version', '1.0'),
56 | # info=meta.get('info', None),
57 | # docs=meta.get('docs', None),
58 | # modalities=meta.get('modalities', []),
59 | # tags=meta.get('tags', []),
60 | # params=meta.get('params', []),
61 | # result=meta['result']
62 | # )
63 | # plugin.save()
64 | #
65 | # for plugin in options['plugins']:
66 | # if upgrade:
67 | # pip.main(['install', '--upgrade', '%s#subdirectory=%s' % (REPO_URL, plugin)])
68 | # else:
69 | # pip.main(['install', '%s#subdirectory=%s' % (REPO_URL, plugin)])
70 | # if find_spec(plugin) is None:
71 | # raise ModuleNotFoundError('%s was not installed!' % plugin)
72 | # if validate:
73 | # try:
74 | # __import__(plugin).Plugin
75 | # except AttributeError:
76 | # raise RuntimeError('Plugin does not have main class "Plugin"')
77 | #
78 | # meta_url = repo.get_contents('%s/META.json' % plugin).download_url
79 | # with urlopen(meta_url) as meta_file:
80 | # meta = loads(meta_file.read())
81 | # plugin = Plugin()
82 | # plugin.author = meta['author']
83 | # plugin.name = meta['name']
84 | # plugin.display_name = meta['display_name']
85 | # plugin.version = meta.get('version', '1.0')
86 | # plugin.info = meta.get('info', None)
87 | # plugin.docs = meta.get('docs', None)
88 | # plugin.modalities = meta.get('modalities', [])
89 | # plugin.tags = meta.get('tags', [])
90 | # plugin.params = meta.get('params', [])
91 | # plugin.result = meta['result']
92 | # plugin.is_installed = True
93 | # plugin.save()
94 | # self.stdout.write('Installing plugins completed!')
95 |
--------------------------------------------------------------------------------
/ndicom_server/apps/core/management/commands/list_plugins.py:
--------------------------------------------------------------------------------
1 | from django.core.management import BaseCommand
2 | import urllib.request
3 | import json
4 |
5 |
6 | class Command(BaseCommand):
7 | help = "List plugins"
8 |
9 | def handle(self, *args, **options):
10 | with urllib.request.urlopen(
11 | 'https://raw.githubusercontent.com/reactmed/neurdicom-plugins/master/REPO_META.json') as response:
12 | content = response.read()
13 | content = json.loads(content)
14 | for plugin in content['plugins']:
15 | self.stdout.write('%s\t[v%s]\t-\t%s' % (plugin['name'], plugin['meta']['version'],
16 | plugin['meta']['display_name']))
17 |
--------------------------------------------------------------------------------
/ndicom_server/apps/core/management/commands/store_dicom.py:
--------------------------------------------------------------------------------
1 | from django.core.management import BaseCommand, CommandParser
2 | import os
3 | from apps.core.utils import DicomSaver
4 | from apps.core.models import Patient, Instance
5 |
6 |
7 | class Command(BaseCommand):
8 | help = "Store DICOM files"
9 |
10 | def _store(self, name):
11 | if not os.path.exists(name):
12 | return
13 | if os.path.isfile(name) and (name.endswith('.dicom') or name.endswith('.dcm')):
14 | DicomSaver.save(name)
15 | self.stdout.write('%s stored' % name)
16 | elif os.path.isdir(name):
17 | files = [os.path.join(name, f) for f in os.listdir(name)]
18 | for f in files:
19 | self._store(f)
20 | return
21 |
22 | def add_arguments(self, parser: CommandParser):
23 | parser.add_argument('locations', nargs='+', type=str)
24 | parser.add_argument('--clear', action='store_true', dest='clear',
25 | help='Clear database')
26 |
27 | def handle(self, *args, **options):
28 |
29 | if options.get('clear', False):
30 | self.stdout.write('Clear database')
31 | for instance in Instance.objects.all():
32 | instance.image.delete()
33 | instance.delete()
34 | Patient.objects.all().delete()
35 |
36 | for name in options['locations']:
37 | self._store(name)
38 | self.stdout.write('Completed!')
39 |
--------------------------------------------------------------------------------
/ndicom_server/apps/core/management/commands/store_plugins.py:
--------------------------------------------------------------------------------
1 | import os
2 | import zipfile
3 |
4 | from django.core.management import BaseCommand, CommandParser
5 |
6 | from apps.core.models import Plugin
7 | from apps.core.utils import PluginSaver
8 |
9 |
10 | class Command(BaseCommand):
11 | help = "Install plugins"
12 |
13 | def _store(self, name, is_native=False):
14 | if not os.path.exists(name):
15 | return
16 | if os.path.isfile(name) and zipfile.is_zipfile(name):
17 | PluginSaver.save(fp=name, is_native=is_native)
18 | self.stdout.write('%s stored' % name)
19 | elif os.path.isdir(name):
20 | files = [os.path.join(name, f) for f in os.listdir(name)]
21 | for f in files:
22 | self._store(f)
23 | return
24 |
25 | def add_arguments(self, parser: CommandParser):
26 | parser.add_argument('locations', nargs='+', type=str)
27 | parser.add_argument('--clear', action='store_true', dest='clear',
28 | help='Clear database')
29 | parser.add_argument('--native', action='store_true', dest='native',
30 | help='Store plugins as native')
31 |
32 | def handle(self, *args, **options):
33 | if options.get('clear', False):
34 | self.stdout.write('Clear')
35 | for plugin in Plugin.objects.all():
36 | plugin.plugin.delete()
37 | plugin.delete()
38 | for name in options['locations']:
39 | self._store(name, is_native=options.get('native', False))
40 | self.stdout.write('Completed!')
41 |
--------------------------------------------------------------------------------
/ndicom_server/apps/core/management/commands/uninstall_plugins.py:
--------------------------------------------------------------------------------
1 | from importlib.util import find_spec
2 | from json import loads
3 | from urllib.request import urlopen
4 |
5 | import pip
6 | from django.core.management import BaseCommand, CommandParser
7 | from github import Github
8 |
9 | from apps.core.models import Plugin
10 |
11 | ORG = 'reactmed'
12 | REPO = 'neurdicom-plugins'
13 | META = 'META.json'
14 | REPO_URL = 'git+git://github.com/reactmed/neurdicom-plugins.git'
15 |
16 |
17 | class Command(BaseCommand):
18 | help = "Uninstall plugins"
19 |
20 | def add_arguments(self, parser: CommandParser):
21 | parser.add_argument('plugins', nargs='*', type=str)
22 | parser.add_argument('--all', action='store_true', dest='all',
23 | help='Uninstall all plugins')
24 |
25 | def handle(self, *args, **options):
26 | all_option = options.get('all', False)
27 | if all_option:
28 | self.stdout.write('Uninstall all plugins')
29 | for plugin in Plugin.objects.all():
30 | pip.main(['uninstall', '--yes', plugin.name])
31 | plugin.delete()
32 | else:
33 | for plugin_name in options['plugins']:
34 | plugin = Plugin.objects.filter(name=plugin_name)
35 | pip.main(['uninstall', '--yes', plugin.name])
36 | plugin.delete()
37 | self.stdout.write('Plugin % is uninstalled' % plugin_name)
38 | self.stdout.write('Uninstalling plugins completed!')
39 |
--------------------------------------------------------------------------------
/ndicom_server/apps/core/managers.py:
--------------------------------------------------------------------------------
1 | from django.contrib.auth.base_user import BaseUserManager
2 |
3 |
4 | class UserManager(BaseUserManager):
5 | use_in_migrations = True
6 |
7 | def _create_user(self, email, password, **extra_fields):
8 | if email is None or email == '':
9 | raise ValueError('Email is not valid!')
10 | email = self.normalize_email(email)
11 | user = self.model(email=email, **extra_fields)
12 | user.set_password(password)
13 | user.save(using=self._db)
14 |
15 | return user
16 |
17 | def create_user(self, email, password, **extra_fields):
18 | extra_fields.setdefault('is_superuser', False)
19 | extra_fields.setdefault('is_staff', False)
20 | return self._create_user(email=email, password=password, **extra_fields)
21 |
22 | def create_superuser(self, email, password, **extra_fields):
23 | extra_fields.setdefault('is_superuser', True)
24 | extra_fields.setdefault('is_staff', True)
25 | # extra_fields.setdefault('role', 'ADMIN')
26 |
27 | if extra_fields.get('is_superuser') is not True:
28 | raise ValueError('Superuser must have is_superuser=True.')
29 |
30 | return self._create_user(email, password, **extra_fields)
31 |
--------------------------------------------------------------------------------
/ndicom_server/apps/core/migrations/0002_auto_20180608_1947.py:
--------------------------------------------------------------------------------
1 | # Generated by Django 2.0.6 on 2018-06-08 19:47
2 |
3 | import datetime
4 | from django.db import migrations, models
5 | from django.utils.timezone import utc
6 |
7 |
8 | class Migration(migrations.Migration):
9 |
10 | dependencies = [
11 | ('core', '0001_initial'),
12 | ]
13 |
14 | operations = [
15 | migrations.AlterField(
16 | model_name='user',
17 | name='date_joined',
18 | field=models.DateTimeField(default=datetime.datetime(2018, 6, 8, 19, 47, 8, 773971, tzinfo=utc)),
19 | ),
20 | ]
21 |
--------------------------------------------------------------------------------
/ndicom_server/apps/core/migrations/0003_auto_20180608_1951.py:
--------------------------------------------------------------------------------
1 | # Generated by Django 2.0.6 on 2018-06-08 19:51
2 |
3 | import datetime
4 | from django.db import migrations, models
5 | from django.utils.timezone import utc
6 |
7 |
8 | class Migration(migrations.Migration):
9 |
10 | dependencies = [
11 | ('core', '0002_auto_20180608_1947'),
12 | ]
13 |
14 | operations = [
15 | migrations.AlterField(
16 | model_name='user',
17 | name='date_joined',
18 | field=models.DateTimeField(default=datetime.datetime(2018, 6, 8, 19, 51, 33, 155178, tzinfo=utc)),
19 | ),
20 | ]
21 |
--------------------------------------------------------------------------------
/ndicom_server/apps/core/migrations/0004_auto_20180608_2059.py:
--------------------------------------------------------------------------------
1 | # Generated by Django 2.0.6 on 2018-06-08 20:59
2 |
3 | import datetime
4 | from django.db import migrations, models
5 | from django.utils.timezone import utc
6 |
7 |
8 | class Migration(migrations.Migration):
9 |
10 | dependencies = [
11 | ('core', '0003_auto_20180608_1951'),
12 | ]
13 |
14 | operations = [
15 | migrations.RenameField(
16 | model_name='dicomnode',
17 | old_name='peer_url',
18 | new_name='remote_url',
19 | ),
20 | migrations.AlterField(
21 | model_name='user',
22 | name='date_joined',
23 | field=models.DateTimeField(default=datetime.datetime(2018, 6, 8, 20, 59, 31, 663705, tzinfo=utc)),
24 | ),
25 | ]
26 |
--------------------------------------------------------------------------------
/ndicom_server/apps/core/migrations/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/reactmed/neurdicom/1b3f659ebd4679a913d9ec95bf28e55c059696f1/ndicom_server/apps/core/migrations/__init__.py
--------------------------------------------------------------------------------
/ndicom_server/apps/core/templates/core/index.html:
--------------------------------------------------------------------------------
1 | {% load staticfiles %}
2 | NeurDICOM
3 |
4 |
--------------------------------------------------------------------------------
/ndicom_server/apps/core/tests.py:
--------------------------------------------------------------------------------
1 | from django.test import TestCase
2 |
3 | # Create your tests here.
4 |
--------------------------------------------------------------------------------
/ndicom_server/apps/core/views.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/reactmed/neurdicom/1b3f659ebd4679a913d9ec95bf28e55c059696f1/ndicom_server/apps/core/views.py
--------------------------------------------------------------------------------
/ndicom_server/apps/dicom_ws/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/reactmed/neurdicom/1b3f659ebd4679a913d9ec95bf28e55c059696f1/ndicom_server/apps/dicom_ws/__init__.py
--------------------------------------------------------------------------------
/ndicom_server/apps/dicom_ws/admin.py:
--------------------------------------------------------------------------------
1 | from django.contrib import admin
2 |
3 | # Register your models here.
4 |
--------------------------------------------------------------------------------
/ndicom_server/apps/dicom_ws/apps.py:
--------------------------------------------------------------------------------
1 | from django.apps import AppConfig
2 |
3 |
4 | class DicomWsConfig(AppConfig):
5 | name = 'dicom_ws'
6 |
--------------------------------------------------------------------------------
/ndicom_server/apps/dicom_ws/migrations/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/reactmed/neurdicom/1b3f659ebd4679a913d9ec95bf28e55c059696f1/ndicom_server/apps/dicom_ws/migrations/__init__.py
--------------------------------------------------------------------------------
/ndicom_server/apps/dicom_ws/models.py:
--------------------------------------------------------------------------------
1 | from django.db import models
2 |
3 | # Create your models here.
4 |
--------------------------------------------------------------------------------
/ndicom_server/apps/dicom_ws/serializers.py:
--------------------------------------------------------------------------------
1 | from rest_framework.serializers import ModelSerializer, SerializerMethodField
2 |
3 | from apps.core.models import *
4 |
5 |
6 | class PatientSerializer(ModelSerializer):
7 | images_count = SerializerMethodField()
8 |
9 | class Meta:
10 | model = Patient
11 | fields = ('id', 'patient_id', 'patient_name', 'patient_sex', 'patient_age', 'patient_birthdate',
12 | 'images_count')
13 |
14 | def get_images_count(self, patient: Patient):
15 | return Instance.objects.filter(series__study__patient__id=patient.id).count()
16 |
17 |
18 | class SeriesSerializer(ModelSerializer):
19 | images_count = SerializerMethodField()
20 |
21 | class Meta:
22 | model = Series
23 | fields = (
24 | 'id', 'series_instance_uid', 'series_number', 'modality',
25 | 'patient_position', 'body_part_examined', 'protocol_name', 'images_count'
26 | )
27 |
28 | def get_images_count(self, series: Series):
29 | return Instance.objects.filter(series_id=series.id).count()
30 |
31 |
32 | class StudySerializer(ModelSerializer):
33 | patient = PatientSerializer()
34 | series = SeriesSerializer(many=True, required=False)
35 | modalities = SerializerMethodField()
36 | images_count = SerializerMethodField()
37 |
38 | class Meta:
39 | model = Study
40 | fields = (
41 | 'id', 'study_id', 'study_instance_uid', 'study_description', 'patient', 'modalities', 'images_count',
42 | 'series'
43 | )
44 |
45 | def get_modalities(self, study: Study):
46 | return list([series['modality'] for series in
47 | study.series.values('modality').distinct()])
48 |
49 | def get_images_count(self, study: Study):
50 | return Instance.objects.filter(series__study__id=study.id).count()
51 |
52 |
53 | class InstanceSerializer(ModelSerializer):
54 | class Meta:
55 | model = Instance
56 | fields = (
57 | 'id', 'sop_instance_uid', 'rows', 'columns', 'smallest_image_pixel_value', 'largest_image_pixel_value',
58 | 'color_space', 'pixel_aspect_ratio', 'pixel_spacing', 'photometric_interpretation', 'image',
59 | 'instance_number'
60 | )
61 |
62 |
63 | class InstanceDetailSerializer(ModelSerializer):
64 | parent = SerializerMethodField()
65 |
66 | class Meta:
67 | model = Instance
68 | fields = (
69 | 'id', 'sop_instance_uid', 'rows', 'columns', 'smallest_image_pixel_value', 'largest_image_pixel_value',
70 | 'color_space', 'pixel_aspect_ratio', 'pixel_spacing', 'photometric_interpretation', 'image',
71 | 'instance_number', 'parent'
72 | )
73 |
74 | def get_parent(self, instance: Instance):
75 | series = instance.series
76 | study = series.study
77 | patient = study.patient
78 | parent = {
79 | 'patient': {
80 | 'patient_name': patient.patient_name,
81 | 'patient_id': patient.patient_id
82 | },
83 | 'study': {
84 | 'study_date': str(study.study_date)
85 | },
86 | 'series': {
87 | 'id': series.id,
88 | 'modality': series.modality
89 | }
90 | }
91 | return parent
92 |
93 |
94 | class DicomNodeSerializer(ModelSerializer):
95 | class Meta:
96 | model = DicomNode
97 | fields = ('id', 'name', 'remote_url', 'instances_url', 'instance_file_url')
98 |
99 |
100 | class PluginSerializer(ModelSerializer):
101 | params = SerializerMethodField()
102 | result = SerializerMethodField()
103 | modalities = SerializerMethodField()
104 | tags = SerializerMethodField()
105 |
106 | def get_tags(self, plugin: Plugin):
107 | if plugin.tags:
108 | return list(plugin.tags)
109 | return []
110 |
111 | def get_params(self, plugin: Plugin):
112 | if plugin.params:
113 | return dict(plugin.params)
114 | return None
115 |
116 | def get_result(self, plugin: Plugin):
117 | if plugin.result:
118 | return dict(plugin.result)
119 | return None
120 |
121 | def get_modalities(self, plugin: Plugin):
122 | if plugin.result:
123 | return list(plugin.modalities)
124 | return None
125 |
126 | class Meta:
127 | model = Plugin
128 | fields = (
129 | 'id', 'author', 'name', 'display_name', 'version', 'tags',
130 | 'info', 'docs', 'params', 'result', 'type',
131 | 'modalities', 'is_installed'
132 | )
133 |
--------------------------------------------------------------------------------
/ndicom_server/apps/dicom_ws/tests.py:
--------------------------------------------------------------------------------
1 | from django.test import TestCase
2 |
3 | # Create your tests here.
4 |
--------------------------------------------------------------------------------
/ndicom_server/apps/dicom_ws/urls.py:
--------------------------------------------------------------------------------
1 | from django.contrib import admin
2 | from django.urls import path
3 | from .views import *
4 |
5 | urlpatterns = [
6 |
7 | # Patient studies
8 | path('patients//studies', PatientStudiesAPIView.as_view()),
9 | # Patients
10 | path('patients/', PatientDetailAPIView.as_view()),
11 | path('patients', PatientListAPIView.as_view()),
12 |
13 | # Study series
14 | path('studies//series', StudySeriesAPIView.as_view()),
15 | # Studies
16 | path('studies/', StudyDetailAPIView.as_view()),
17 | path('studies', StudyListAPIView.as_view()),
18 |
19 | # Series
20 | path('series//instances', SeriesInstanceListAPIView.as_view()),
21 | path('series/', SeriesDetailAPIView.as_view()),
22 | path('series', SeriesListAPIView.as_view()),
23 |
24 | # Instances
25 | path('instances//pixels/8bit', get_instance_8bit_pixels),
26 | path('instances//pixels', get_instance_pixels),
27 | path('instances//image', get_instance_image),
28 | path('instances//tags', get_instance_tags),
29 | path('instances/', InstanceDetailAPIView.as_view()),
30 | path('instances', InstanceListAPIView.as_view()),
31 |
32 | path('dicom_nodes/', DicomNodeDetailAPIView.as_view()),
33 | path('dicom_nodes', DicomNodeListAPIView.as_view())
34 | ]
35 |
--------------------------------------------------------------------------------
/ndicom_server/apps/dicom_ws/views.py:
--------------------------------------------------------------------------------
1 | import json
2 | import re
3 | from io import BytesIO
4 |
5 | import numpy as np
6 | import pydicom as dicom
7 | from PIL import Image
8 | from django.db.models import Q
9 | from django.http import HttpResponse
10 | from pydicom import Sequence
11 | from rest_framework import status
12 | from rest_framework.generics import *
13 | from rest_framework.response import Response
14 |
15 | from apps.core.utils import DicomSaver, DicomJsonEncoder
16 | from apps.dicom_ws.serializers import *
17 |
18 | # CACHE_TTL = getattr(settings, 'CACHE_TTL', DEFAULT_TIMEOUT)
19 | SEARCH_PARAM_RE = re.compile('(exact|startswith|endswith|contains)\s*=\s*((\w|\s|_|\^|-|\d)+)')
20 | DATE_SEARCH_PARAM_RE = re.compile('((from=)|(to=))')
21 | IMG_MIME_TYPE_RE = re.compile('image/(jpeg|jpg|gif|png|tiff)')
22 |
23 |
24 | class PatientDetailAPIView(RetrieveDestroyAPIView):
25 | serializer_class = PatientSerializer
26 | queryset = Patient.objects.all()
27 |
28 |
29 | class PatientListAPIView(ListAPIView):
30 | serializer_class = PatientSerializer
31 |
32 | def get_queryset(self):
33 | patient_name = self.request.query_params.get('patient_name', None)
34 | patient_id = self.request.query_params.get('patient_id', None)
35 | filters = []
36 | matcher = SEARCH_PARAM_RE.match(patient_name or '')
37 | if matcher:
38 | search_param = matcher.group(1)
39 | patient_name = matcher.group(2)
40 | q_params = {
41 | 'patient_name__' + search_param: patient_name
42 | }
43 | filters.append(Q(**q_params))
44 | matcher = SEARCH_PARAM_RE.match(patient_id or '')
45 | if matcher:
46 | search_param = matcher.group(1)
47 | patient_name = matcher.group(2)
48 | q_params = {
49 | 'patient_id__' + search_param: patient_name
50 | }
51 | filters.append(Q(**q_params))
52 | if len(filters) == 0:
53 | return Patient.objects.all()
54 | else:
55 | or_q = filters[0]
56 | for filter_q in filters:
57 | or_q |= filter_q
58 | return Patient.objects.filter(or_q)
59 |
60 |
61 | class PatientStudiesAPIView(ListAPIView):
62 | serializer_class = StudySerializer
63 |
64 | def get_queryset(self):
65 | patient_id = self.kwargs['pk']
66 | return Study.objects.filter(patient_id=patient_id)
67 |
68 |
69 | class StudySeriesAPIView(ListAPIView):
70 | serializer_class = SeriesSerializer
71 |
72 | def get_queryset(self):
73 | study_id = self.kwargs['pk']
74 | return Series.objects.filter(study_id=study_id)
75 |
76 |
77 | class StudyDetailAPIView(RetrieveDestroyAPIView):
78 | queryset = Study.objects.all()
79 | serializer_class = StudySerializer
80 |
81 |
82 | class StudyListAPIView(ListAPIView):
83 | queryset = Study.objects.all()
84 | serializer_class = StudySerializer
85 |
86 |
87 | class SeriesDetailAPIView(RetrieveDestroyAPIView):
88 | queryset = Series.objects.all()
89 | serializer_class = SeriesSerializer
90 |
91 |
92 | class SeriesListAPIView(ListAPIView):
93 | queryset = Series.objects.all()
94 | serializer_class = SeriesSerializer
95 |
96 |
97 | class InstanceDetailAPIView(RetrieveDestroyAPIView):
98 | queryset = Instance.objects.all()
99 | serializer_class = InstanceSerializer
100 |
101 |
102 | class InstanceListAPIView(ListCreateAPIView):
103 | queryset = Instance.objects.all()
104 | serializer_class = InstanceSerializer
105 |
106 | def create(self, request, *args, **kwargs):
107 | instances = []
108 | for file in request.FILES:
109 | dicom_file = request.FILES[file]
110 | instances.append(DicomSaver.save(dicom_file))
111 | serializer = InstanceSerializer(instances, many=True)
112 | return Response(serializer.data, status=status.HTTP_200_OK)
113 |
114 |
115 | class SeriesInstanceListAPIView(ListAPIView):
116 | serializer_class = InstanceSerializer
117 |
118 | def get_queryset(self):
119 | series_id = self.kwargs['pk']
120 | return Instance.objects.filter(series_id=series_id).order_by('instance_number')
121 |
122 |
123 | # @cache_page(CACHE_TTL)
124 | def get_instance_image(request, pk):
125 | instance = Instance.objects.get(pk=pk)
126 | ds = dicom.read_file(instance.image.path)
127 | accept_format = request.META.get('HTTP_ACCEPT', 'image/jpeg')
128 | if not accept_format or not IMG_MIME_TYPE_RE.match(accept_format):
129 | accept_format = 'image/jpeg'
130 | accept_format = accept_format.replace('image/', '')
131 | pixel_array = ds.pixel_array
132 | orig_shape = pixel_array.shape
133 | flatten_img = pixel_array.reshape((-1))
134 | img_min = min(flatten_img)
135 | img_max = max(flatten_img)
136 | np.floor_divide(flatten_img, (img_max - img_min + 1) / 256,
137 | out=flatten_img, casting='unsafe')
138 | img = flatten_img.astype(dtype=np.uint8).reshape(orig_shape)
139 | img = Image.fromarray(img)
140 | file = BytesIO()
141 | img.save(file, format=accept_format)
142 | file.seek(0)
143 | response = HttpResponse(file.read(), content_type='image/jpeg')
144 | response['Content-Disposition'] = 'attachment; filename=%d.jpeg' % instance.id
145 | return response
146 |
147 |
148 | def get_instance_tags(request, pk):
149 | instance = Instance.objects.get(pk=pk)
150 | ds = dicom.read_file(instance.image.path)
151 | tags = {}
152 | for tag_key in ds.dir():
153 | tag = ds.data_element(tag_key)
154 | if tag_key == 'PixelData':
155 | continue
156 | if not hasattr(tag, 'name') or not hasattr(tag, 'value'):
157 | continue
158 | tag_value = tag.value
159 | # Временная мера - по мере рещения удалить
160 | if isinstance(tag_value, Sequence):
161 | continue
162 | tags[tag.name] = tag_value
163 | dumped_json = json.dumps(tags, cls=DicomJsonEncoder)
164 | response = HttpResponse(dumped_json, content_type='application/json')
165 | return response
166 |
167 |
168 | def get_instance_pixels(request, pk):
169 | instance = Instance.objects.get(pk=pk)
170 | ds = dicom.read_file(instance.image.path)
171 | pixel_array = ds.pixel_array.tobytes()
172 | return HttpResponse(pixel_array, content_type='application/octet-stream')
173 |
174 |
175 | def get_instance_8bit_pixels(request, pk):
176 | instance = Instance.objects.get(pk=pk)
177 | ds = dicom.read_file(instance.image.path)
178 | pixel_array = ds.pixel_array
179 | orig_shape = pixel_array.shape
180 | flatten_img = pixel_array.reshape((-1))
181 | img_min = min(flatten_img)
182 | img_max = max(flatten_img)
183 | np.floor_divide(flatten_img, (img_max - img_min + 1) / 256,
184 | out=flatten_img, casting='unsafe')
185 | pixel_array = flatten_img.astype(dtype=np.uint8).reshape(orig_shape)
186 | pixel_array = pixel_array.tobytes()
187 | return HttpResponse(pixel_array, content_type='application/octet-stream')
188 |
189 |
190 | class DicomNodeListAPIView(ListCreateAPIView):
191 | queryset = DicomNode.objects.all()
192 | serializer_class = DicomNodeSerializer
193 |
194 |
195 | class DicomNodeDetailAPIView(RetrieveUpdateDestroyAPIView):
196 | queryset = DicomNode.objects.all()
197 | serializer_class = DicomNodeSerializer
198 |
--------------------------------------------------------------------------------
/ndicom_server/apps/users/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/reactmed/neurdicom/1b3f659ebd4679a913d9ec95bf28e55c059696f1/ndicom_server/apps/users/__init__.py
--------------------------------------------------------------------------------
/ndicom_server/apps/users/admin.py:
--------------------------------------------------------------------------------
1 | from django.contrib import admin
2 |
3 | # Register your models here.
4 |
--------------------------------------------------------------------------------
/ndicom_server/apps/users/apps.py:
--------------------------------------------------------------------------------
1 | from django.apps import AppConfig
2 |
3 |
4 | class UsersConfig(AppConfig):
5 | name = 'users'
6 |
--------------------------------------------------------------------------------
/ndicom_server/apps/users/handlers.py:
--------------------------------------------------------------------------------
1 | from apps.core.handlers import *
2 | from apps.core.models import User
3 | from apps.core.utils import required_auth, required_admin
4 | from apps.users.serializers import UserSerializer, CreateUserSerializer
5 |
6 |
7 | class UserAuthHandler(BaseJsonHandler):
8 | def post(self, *args, **kwargs):
9 | params = self.request.arguments
10 | email = params['email']
11 | password = params['password']
12 | try:
13 | user = User.objects.get(email=email)
14 | if user.check_password(password):
15 | # m = sha256()
16 | # m.update((str(user.id) + '|' + email).encode('utf-8'))
17 | # self.set_cookie('auth', m.hexdigest())
18 | self.set_secure_cookie('neurdicom.auth', str(user.id) + '|' + email)
19 | self.write({
20 | 'id': user.id,
21 | 'email': email,
22 | 'name': user.name,
23 | 'surname': user.surname,
24 | 'is_admin': user.is_staff
25 | })
26 | return
27 | except User.DoesNotExist:
28 | self.write_error(status_code=404)
29 | self.write({
30 | 'message': 'User with email %s does not exist' % email
31 | })
32 | return
33 |
34 |
35 | @required_auth(methods=['GET'])
36 | class UserListHandler(ListCreateHandler):
37 | serializer_class = UserSerializer
38 | queryset = User.objects.all()
39 |
40 | def post(self, *args, **kwargs):
41 | serializer = CreateUserSerializer(data=self.request.arguments)
42 | if serializer.is_valid():
43 | serializer.save()
44 | self.write(serializer.data)
45 | else:
46 | self.write(serializer.error_messages)
47 | self.send_error(500)
48 |
49 |
50 | @required_admin(methods=['DELETE'])
51 | class UserDetailHandler(RetrieveUpdateDestroyHandler):
52 | serializer_class = UserSerializer
53 | queryset = User.objects.all()
54 |
55 |
56 | class UserLogoutHandler(BaseJsonHandler):
57 | def post(self, *args, **kwargs):
58 | self.clear_all_cookies()
59 | self.write({
60 | 'message': 'Log out is successful'
61 | })
62 |
63 |
64 | @required_auth(methods=['GET'])
65 | class UserCheckHandler(BaseJsonHandler):
66 | def get(self, *args, **kwargs):
67 | self.set_status(200)
68 | self.write('')
69 |
--------------------------------------------------------------------------------
/ndicom_server/apps/users/migrations/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/reactmed/neurdicom/1b3f659ebd4679a913d9ec95bf28e55c059696f1/ndicom_server/apps/users/migrations/__init__.py
--------------------------------------------------------------------------------
/ndicom_server/apps/users/models.py:
--------------------------------------------------------------------------------
1 | from django.db import models
2 |
3 | # Create your models here.
4 |
--------------------------------------------------------------------------------
/ndicom_server/apps/users/serializers.py:
--------------------------------------------------------------------------------
1 | from rest_framework.exceptions import ValidationError
2 | from rest_framework.serializers import ModelSerializer
3 |
4 | from apps.core.models import User
5 |
6 |
7 | class CreateUserSerializer(ModelSerializer):
8 | class Meta:
9 | model = User
10 | fields = ['id', 'name', 'surname', 'email', 'password']
11 |
12 | def is_valid(self, raise_exception=False):
13 | email = self.data['email']
14 | if User.objects.filter(email=email).exists():
15 | self.error_messages.update({
16 | 'email': 'User with email "%s" already exists' % email
17 | })
18 | return False
19 | return True
20 |
21 |
22 | class UserSerializer(ModelSerializer):
23 | class Meta:
24 | model = User
25 | fields = ['id', 'name', 'surname', 'email']
26 |
--------------------------------------------------------------------------------
/ndicom_server/apps/users/tests.py:
--------------------------------------------------------------------------------
1 | from django.test import TestCase
2 |
3 | # Create your tests here.
4 |
--------------------------------------------------------------------------------
/ndicom_server/apps/users/views.py:
--------------------------------------------------------------------------------
1 | from django.shortcuts import render
2 |
3 | # Create your views here.
4 |
--------------------------------------------------------------------------------
/ndicom_server/examples/plugins/dicom_pixels/plugin.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/reactmed/neurdicom/1b3f659ebd4679a913d9ec95bf28e55c059696f1/ndicom_server/examples/plugins/dicom_pixels/plugin.py
--------------------------------------------------------------------------------
/ndicom_server/install_and_run.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | echo "Activate virtual env"
4 | source venv/bin/activate
5 |
6 | echo "Install requirements"
7 | pip install -r requirements.txt
8 | pip install git+git://github.com/pydicom/pydicom.git
9 | pip install git+git://github.com/pydicom/pynetdicom3.git
10 | echo "Start Docker container"
11 | docker-compose up --build &
12 |
13 | echo "Waiting PostgreSQL to launch on 8080..."
14 |
15 | while ! nc -z localhost 5432; do
16 | sleep 0.1
17 | done
18 |
19 | echo "PostgreSQL launched"
20 |
21 | sleep 10
22 | echo "Migrate database"
23 | python ./manage.py migrate
24 |
25 | echo "Store DICOM images"
26 | python ./manage.py clear_dicom
27 | python ./manage.py store_dicom ../images
28 | #python ./manage.py install_plugins --mode=PYPI ndicom_kmeans ndicom_cuda_kmeans ndicom_fcm ndicom_thresholding \
29 | # ndicom_region_growing ndicom_meanshift ndicom_gauss_kernel_kmeans ndicom_improved_kmeans ndicom_gaussian_mixture --clear --upgrade
30 | python app.py &
31 |
32 |
33 | while ! nc -z localhost 8080; do
34 | sleep 0.1
35 | done
36 |
37 | sleep 10
38 |
39 | cd ../ndicom_client
40 | rm -rf build
41 | npm run build
42 | npm install -g serve
43 | server -s build
44 |
--------------------------------------------------------------------------------
/ndicom_server/manage.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | import os
3 | import sys
4 |
5 | if __name__ == "__main__":
6 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "neurdicom.settings")
7 | try:
8 | from django.core.management import execute_from_command_line
9 | except ImportError as exc:
10 | raise ImportError(
11 | "Couldn't import Django. Are you sure it's installed and "
12 | "available on your PYTHONPATH environment variable? Did you "
13 | "forget to activate a virtual environment?"
14 | ) from exc
15 | execute_from_command_line(sys.argv)
16 |
--------------------------------------------------------------------------------
/ndicom_server/neurdicom/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/reactmed/neurdicom/1b3f659ebd4679a913d9ec95bf28e55c059696f1/ndicom_server/neurdicom/__init__.py
--------------------------------------------------------------------------------
/ndicom_server/neurdicom/settings.py:
--------------------------------------------------------------------------------
1 | """
2 | Django settings for neurdicom project.
3 |
4 | Generated by 'django-admin startproject' using Django 2.0.2.
5 |
6 | For more information on this file, see
7 | https://docs.djangoproject.com/en/2.0/topics/settings/
8 |
9 | For the full list of settings and their values, see
10 | https://docs.djangoproject.com/en/2.0/ref/settings/
11 | """
12 |
13 | import os
14 |
15 | # Build paths inside the project like this: os.path.join(BASE_DIR, ...)
16 | BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
17 |
18 | # Quick-start development settings - unsuitable for production
19 | # See https://docs.djangoproject.com/en/2.0/howto/deployment/checklist/
20 |
21 | # SECURITY WARNING: keep the secret key used in production secret!
22 | SECRET_KEY = 'z*l%m%=x3!*y8rp7pk)l&r@_!(q6=vn0gk)9+0q&3vno-@9ck&'
23 |
24 | # SECURITY WARNING: don't run with debug turned on in production!
25 | DEBUG = False
26 |
27 | ALLOWED_HOSTS = []
28 |
29 | # Application definition
30 |
31 | INSTALLED_APPS = [
32 | 'django.contrib.admin',
33 | 'django.contrib.auth',
34 | 'django.contrib.contenttypes',
35 | 'django.contrib.sessions',
36 | 'django.contrib.messages',
37 | 'django.contrib.staticfiles',
38 | 'rest_framework',
39 | 'apps.core',
40 | 'apps.dicom_ws'
41 | ]
42 |
43 | MIDDLEWARE = [
44 | 'django.middleware.security.SecurityMiddleware',
45 | 'django.contrib.sessions.middleware.SessionMiddleware',
46 | 'django.middleware.common.CommonMiddleware',
47 | 'django.middleware.csrf.CsrfViewMiddleware',
48 | 'django.contrib.auth.middleware.AuthenticationMiddleware',
49 | 'django.contrib.messages.middleware.MessageMiddleware',
50 | 'django.middleware.clickjacking.XFrameOptionsMiddleware',
51 | ]
52 |
53 | ROOT_URLCONF = 'neurdicom.urls'
54 |
55 | TEMPLATES = [
56 | {
57 | 'BACKEND': 'django.template.backends.django.DjangoTemplates',
58 | 'DIRS': [],
59 | 'APP_DIRS': True,
60 | 'OPTIONS': {
61 | 'context_processors': [
62 | 'django.template.context_processors.debug',
63 | 'django.template.context_processors.request',
64 | 'django.contrib.auth.context_processors.auth',
65 | 'django.contrib.messages.context_processors.messages',
66 | ],
67 | },
68 | },
69 | ]
70 |
71 | WSGI_APPLICATION = 'neurdicom.wsgi.application'
72 |
73 | DATABASES = {
74 | 'default': {
75 | 'ENGINE': 'django.db.backends.postgresql_psycopg2',
76 | 'NAME': os.environ.get('DB_NAME', 'neurdicom'),
77 | 'USER': os.environ.get('DB_USER', 'neurdicom'),
78 | 'PASSWORD': os.environ.get('DB_PASSWORD', 'neurdicom'),
79 | 'HOST': os.environ.get('DB_HOST', '127.0.0.1'),
80 | }
81 | }
82 |
83 | AUTH_PASSWORD_VALIDATORS = [
84 | {
85 | 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
86 | },
87 | {
88 | 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
89 | },
90 | {
91 | 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
92 | },
93 | {
94 | 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
95 | },
96 | ]
97 |
98 | # Internationalization
99 | # https://docs.djangoproject.com/en/2.0/topics/i18n/
100 |
101 | LANGUAGE_CODE = 'en-us'
102 |
103 | TIME_ZONE = 'UTC'
104 |
105 | USE_I18N = True
106 |
107 | USE_L10N = True
108 |
109 | USE_TZ = True
110 |
111 | STATIC_URL = '/static/'
112 | # CACHE_TTL = 60 * 1
113 |
114 | INTERNAL_IPS = [
115 | '127.0.0.1'
116 | ]
117 | REST_FRAMEWORK = {
118 | 'DEFAULT_RENDERER_CLASSES': (
119 | 'rest_framework.renderers.JSONRenderer',
120 | ),
121 | 'DEFAULT_PARSER_CLASSES': (
122 | 'rest_framework.parsers.JSONParser',
123 | )
124 | }
125 |
126 | MEDIA_URL = '/media/'
127 | MEDIA_ROOT = os.path.join(BASE_DIR, "media/")
128 |
129 | DICOMWEB_SERVER = {
130 | 'host': '127.0.0.1',
131 | 'port': 8080,
132 | 'num_processes': 0
133 | }
134 |
135 | DICOM_SERVER = {
136 | 'host': '127.0.0.1',
137 | 'aet': 'NEURDICOM',
138 | 'port': 8842
139 | }
140 |
141 | AUTH_USER_MODEL = 'core.User'
142 |
143 | REQUIRE_AUTH = False
144 | RUN_DICOM = False
145 |
146 | GITHUB_TOKEN = "afdcb822d7dca257c81b271db50d4d35edbed73d"
147 |
--------------------------------------------------------------------------------
/ndicom_server/neurdicom/urls.py:
--------------------------------------------------------------------------------
1 | """neurdicom URL Configuration
2 | The `urlpatterns` list routes URLs to views. For more information please see:
3 | https://docs.djangoproject.com/en/2.0/topics/http/urls/
4 | Examples:
5 | Function views
6 | 1. Add an import: from my_app import views
7 | 2. Add a URL to urlpatterns: path('', views.home, name='home')
8 | Class-based views
9 | 1. Add an import: from other_app.views import Home
10 | 2. Add a URL to urlpatterns: path('', Home.as_view(), name='home')
11 | Including another URLconf
12 | 1. Import the include() function: from django.urls import include, path
13 | 2. Add a URL to urlpatterns: path('blog/', include('blog.urls'))
14 | """
15 | from django.conf.urls.static import static
16 |
17 | from neurdicom import settings
18 |
19 | urlpatterns = static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
20 |
21 | USER_LIST_URL = r'/api/users'
22 | USER_DETAIL_URL = r'/api/users/(\d+)'
23 | USER_AUTH_URL = r'/api/users/auth'
24 | USER_LOGOUT_URL = r'/api/users/logout'
25 | USER_CHECK_URL = r'/api/users/check'
26 |
27 | PATIENT_LIST_URL = r'/api/patients'
28 | PATIENT_DETAIL_URL = r'/api/patients/(\d+)'
29 | PATIENT_STUDIES_URL = r'/api/patients/(\d+)/studies'
30 |
31 | STUDY_LIST_URL = r'/api/studies'
32 | STUDY_DETAIL_URL = r'/api/studies/(\d+)'
33 | STUDY_SERIES_URL = r'/api/studies/(\d+)/series'
34 |
35 | SERIES_LIST_URL = r'/api/series'
36 | SERIES_DETAIL_URL = r'/api/series/(\d+)'
37 | SERIES_INSTANCES_URL = r'/api/series/(\d+)/instances'
38 |
39 | INSTANCE_LIST_URL = r'/api/instances'
40 | INSTANCE_UPLOAD_URL = r'/api/instances/upload'
41 | INSTANCE_DETAIL_URL = r'/api/instances/(\d+)'
42 | INSTANCE_IMAGE_URL = r'/api/instances/(\d+)/image'
43 | INSTANCE_RAW_URL = r'/api/instances/(\d+)/raw'
44 | INSTANCE_TAGS_URL = r'/api/instances/(\d+)/tags'
45 | INSTANCE_PROCESS_URL = r'/api/instances/(\d+)/process/by_plugin/(\d+)'
46 |
47 | DICOM_NODE_LIST_URL = r'/api/dicom_nodes'
48 | DICOM_NODE_DETAIL_URL = r'/api/dicom_nodes/(\d+)'
49 | DICOM_NODE_ECHO_URL = r'/api/dicom_nodes/(\d+)/echo'
50 | DICOM_NODE_INSTANCES_URL = r'/api/dicom_nodes/(\d+)/instances'
51 |
52 | PLUGIN_LIST_URL = r'/api/plugins'
53 | PLUGIN_DETAIL_URL = r'/api/plugins/(\d+)'
54 | PLUGIN_INSTALL_URL = r'/api/plugins/([a-zA-Z]([a-zA-Z]|-|_|\d)*)/install'
55 |
56 | MEDIA_URL = r'/media/(.*)'
57 |
--------------------------------------------------------------------------------
/ndicom_server/neurdicom/wsgi.py:
--------------------------------------------------------------------------------
1 | """
2 | WSGI config for neurdicom project.
3 |
4 | It exposes the WSGI callable as a module-level variable named ``application``.
5 |
6 | For more information on this file, see
7 | https://docs.djangoproject.com/en/2.0/howto/deployment/wsgi/
8 | """
9 |
10 | import os
11 |
12 | from django.core.wsgi import get_wsgi_application
13 |
14 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "neurdicom.settings")
15 |
16 | application = get_wsgi_application()
17 |
--------------------------------------------------------------------------------
/ndicom_server/notebooks/brain.dcm:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/reactmed/neurdicom/1b3f659ebd4679a913d9ec95bf28e55c059696f1/ndicom_server/notebooks/brain.dcm
--------------------------------------------------------------------------------
/ndicom_server/requirements.txt:
--------------------------------------------------------------------------------
1 | Django==2.2.10
2 | djangorestframework==3.8.2
3 | jsonfield==2.0.2
4 | numpy==1.14.3
5 | opencv-python==3.4.1.15
6 | Pillow==6.2.0
7 | psycopg2-binary==2.7.4
8 | pytz==2018.4
9 | scikit-learn==0.19.1
10 | tornado==5.0.2
11 | jinja2
12 | pycurl
--------------------------------------------------------------------------------
/ndicom_server/run.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | docker-compose up
4 | source ./venv/bin/activate
5 | python ./app.py
--------------------------------------------------------------------------------
/neurdicom.conf:
--------------------------------------------------------------------------------
1 | upstream backend {
2 | server api:8080;
3 | }
4 |
5 | server {
6 |
7 | client_max_body_size 100m;
8 | listen 80;
9 | server_name neurdicom;
10 |
11 | location /static {
12 | root /mnt/front;
13 | }
14 |
15 | location /media {
16 | root /mnt/front;
17 | }
18 |
19 | location /api {
20 | proxy_set_header Host $http_host;
21 | proxy_redirect off;
22 | proxy_set_header X-Real-IP $remote_addr;
23 | proxy_set_header X-Scheme $scheme;
24 | proxy_pass http://backend/api;
25 | }
26 |
27 | location / {
28 | root /mnt/front;
29 | index index.html;
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/neurdicom.dev.conf:
--------------------------------------------------------------------------------
1 | upstream backend {
2 | ip_hash;
3 | server api:8080;
4 | server api:8081;
5 | server api:8082;
6 | server api:8083;
7 | }
8 |
9 |
10 | server {
11 |
12 | client_max_body_size 100m;
13 | listen 80;
14 | server_name neurdicom;
15 |
16 | location /static {
17 | root /mnt/front;
18 | }
19 |
20 | location /media {
21 | root /mnt/front;
22 | }
23 |
24 | location /api {
25 | proxy_set_header Host $http_host;
26 | proxy_redirect off;
27 | proxy_set_header X-Real-IP $remote_addr;
28 | proxy_set_header X-Scheme $scheme;
29 | proxy_pass http://backend/api;
30 | }
31 |
32 | location / {
33 | root /mnt/front;
34 | index index.html;
35 | }
36 | }
37 |
--------------------------------------------------------------------------------