├── .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 |
Application Content
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 | 32 | 34 | 36 | 38 | 40 | 41 | 44 | 45 | Русский 47 | English 49 | 50 | 51 | 52 | 53 | 55 | 56 | 57 | 61 | 62 | 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 |
49 | 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 | 110 | 111 | 114 | 115 | 116 | 118 | 119 | 120 | 122 | 123 | 124 | 125 | 127 | 128 | 129 | 132 | 133 | 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 |
83 | { 84 | Object.keys(params).map(paramName => { 85 | const param = params[paramName]; 86 | const displayName = param['display_name']; 87 | const isRequired = param['is_required'] || false; 88 | const range = param['range']; 89 | const step = param['step']; 90 | const type = param['type']; 91 | const values = param['values']; 92 | const _default = param['default']; 93 | if (isRequired) { 94 | return ( 95 | 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 | 144 | 145 | 148 | 149 | 150 | 153 | 154 | 155 | 158 | 159 | 160 | 163 | 164 | 165 | 167 | 168 | 169 | 170 | 171 | 172 | 174 | 175 | 176 | 179 | 180 | 181 | 184 | 185 | 186 | 189 | 190 | 191 | 194 | 195 | 196 | this.pluginSelect} placeholder='Плагин' fluid search selection 197 | options={pluginOptions} onChange={this.onApplyPluginCallback} 198 | /> 199 | 200 | 201 | 204 | 205 | 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 |
8 |
9 |
10 | Вход 11 |
12 |
13 | 19 | 26 | Войти 27 | Забыли пароль? Вы можете его восстановить 28 | 29 |
30 |
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 |
50 | 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 |
66 | 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 |
109 | 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 |
53 | 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 |
77 | 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 | --------------------------------------------------------------------------------