Skip to content

Commit

Permalink
Basic OHIF Plugin support for monailabel (Project-MONAI#252)
Browse files Browse the repository at this point in the history
* by default allow all for cors

Signed-off-by: SACHIDANAND ALLE <[email protected]>

* ohif plugin+integration for monailabel

Signed-off-by: Sachidanand Alle <[email protected]>

* add eslint config

Signed-off-by: Sachidanand Alle <[email protected]>

* Fix Interface + others

Signed-off-by: Sachidanand Alle <[email protected]>

* Fix build

Signed-off-by: Sachidanand Alle <[email protected]>

* fix build and buttons

Signed-off-by: Sachidanand Alle <[email protected]>

* enable download option to save labelmap as .nrrd

Signed-off-by: Sachidanand Alle <[email protected]>

* Support ohif build during package

Signed-off-by: Sachidanand Alle <[email protected]>

* Auto code fix

Signed-off-by: Sachidanand Alle <[email protected]>

* Fix OHIF build

Signed-off-by: Sachidanand Alle <[email protected]>

* Skip OHIF build for Pull Requests

Signed-off-by: Sachidanand Alle <[email protected]>

* Fix build

Signed-off-by: Sachidanand Alle <[email protected]>

* Add ohif readme

Signed-off-by: Sachidanand Alle <[email protected]>

* implement next sample action

Signed-off-by: Sachidanand Alle <[email protected]>

* enable download option from get endpoint for slicer

Signed-off-by: Sachidanand Alle <[email protected]>

* fix save label

Signed-off-by: Sachidanand Alle <[email protected]>

* mock to create dicom seg and upload to orthanc

Signed-off-by: Sachidanand Alle <[email protected]>

* fix after testing

Signed-off-by: Sachidanand Alle <[email protected]>

* rename func param

Signed-off-by: Sachidanand Alle <[email protected]>

* fix segment idx for multi-label SegmentAttribute

Signed-off-by: Sachidanand Alle <[email protected]>

* Fix rgb type while saving the label

Signed-off-by: Sachidanand Alle <[email protected]>

* Enable OHIF through DICOMWeb interface (infer/save/train works)

Signed-off-by: Sachidanand Alle <[email protected]>

* fix unit tests

Signed-off-by: Sachidanand Alle <[email protected]>

* fix training etc...

Signed-off-by: Sachidanand Alle <[email protected]>

* Fix code check

Signed-off-by: Sachidanand Alle <[email protected]>

* support start/stop training action

Signed-off-by: Sachidanand Alle <[email protected]>

* show dicom meta info when next sample is fetched

Signed-off-by: Sachidanand Alle <[email protected]>

* support auth to connect to remote dicomweb via proxy + readme

Signed-off-by: Sachidanand Alle <[email protected]>

* Fix readme

Signed-off-by: Sachidanand Alle <[email protected]>

* update readme

Signed-off-by: Sachidanand Alle <[email protected]>

* minor updates

Signed-off-by: Alvin Ihsani <[email protected]>

* adding prefix

Signed-off-by: Alvin Ihsani <[email protected]>

* autofix

Signed-off-by: Sachidanand Alle <[email protected]>

Co-authored-by: Sachidanand Alle <[email protected]>
Co-authored-by: Alvin Ihsani <[email protected]>
  • Loading branch information
3 people authored Sep 1, 2021
1 parent f548ad9 commit 5060051
Show file tree
Hide file tree
Showing 57 changed files with 4,349 additions and 20 deletions.
1 change: 1 addition & 0 deletions .github/workflows/pythonapp.yml
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@ jobs:
echo "$root_dir"
# build tar.gz and wheel
export BUILD_OHIF=false
python setup.py check -m -s
python setup.py sdist bdist_wheel
python -m twine check dist/*
Expand Down
3 changes: 3 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
[submodule "plugins/ohif/Viewers"]
path = plugins/ohif/Viewers
url = https://github.com/OHIF/Viewers.git
42 changes: 31 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,21 +13,23 @@ principles with [MONAI](https://github.com/Project-MONAI).

[Brief Demo](https://youtu.be/gzAR-Ix31Gs)

<img src="https://raw.githubusercontent.com/Project-MONAI/MONAILabel/main/docs/images/demo.png" width="800"/>
![DEMO](docs/images/demo.png)

## Features

> _The codebase is currently under active development._
- framework for developing and deploying MONAI Label Apps to train and infer AI models
- compositional & portable APIs for ease of integration in existing workflows
- customizable design for varying user expertise
- 3DSlicer support


## Installation

MONAI Label supports following OS with **GPU/CUDA** enabled.
- Ubuntu
- [Windows](https://docs.monai.io/projects/label/en/latest/installation.html#windows)

- Ubuntu
- [Windows](https://docs.monai.io/projects/label/en/latest/installation.html#windows)

To install the current release, you can simply run:

Expand All @@ -43,28 +45,46 @@ To install the current release, you can simply run:

```

For **_prerequisites_**, other installation methods (using the default GitHub branch, using Docker, etc.), please refer to the [installation guide](https://docs.monai.io/projects/label/en/latest/installation.html).
For **_prerequisites_**, other installation methods (using the default GitHub branch, using Docker, etc.), please refer
to the [installation guide](https://docs.monai.io/projects/label/en/latest/installation.html).

> Once you start the MONAI Label Server, by default it will be up and serving at http://127.0.0.1:8000/. Open the serving
URL in browser. It will provide you the list of Rest APIs available.
> Once you start the MONAI Label Server, by default it will be up and serving at http://127.0.0.1:8000/. Open the serving URL in browser. It will provide you the list of Rest APIs available.
### 3D Slicer

Download **Preview Release** from https://download.slicer.org/ and install MONAI Label plugin from Slicer Extension Manager.
Download **Preview Release** from https://download.slicer.org/ and install MONAI Label plugin from Slicer Extension
Manager.

Refer [3D Slicer plugin](plugins/slicer) for other options to install and run MONAI Label plugin in 3D Slicer.
> To avoid accidentally using an older Slicer version, you may want to _uninstall_ any previously installed 3D Slicer package.
> To avoid accidentally using an older Slicer version, you may want to _uninstall_ any previously installed 3D Slicer package.
### OHIF

MONAI Label comes with pre-built plugin for [OHIF Viewer](https://github.com/OHIF/Viewers).
> Please install [Orthanc](https://www.orthanc-server.com/download.php) before using OHIF Viewer.
> For Ubuntu 20.x, Orthanc can be installed as `apt-get install orthanc orthanc-dicomweb`. However, you have to **upgrade to latest version** by following steps mentioned [here](https://book.orthanc-server.com/users/debian-packages.html#replacing-the-package-from-the-service-by-the-lsb-binaries)
>
> You can use [PlastiMatch](https://plastimatch.org/plastimatch.html#plastimatch-convert) to convert NIFTI to DICOM
> OHIF Viewer will be accessible at http://127.0.0.1:8000/ohif/
> For development OHIF can be built by running
![OHIF](docs/images/ohif.png)

## Contributing

For guidance on making a contribution to MONAI Label, see the [contributing guidelines](CONTRIBUTING.md).

## Community
Join the conversation on Twitter [@ProjectMONAI](https://twitter.com/ProjectMONAI) or join our [Slack channel](https://forms.gle/QTxJq3hFictp31UM9).

Ask and answer questions over on [MONAI Label's GitHub Discussions tab](https://github.com/Project-MONAI/MONAILabel/discussions).
Join the conversation on Twitter [@ProjectMONAI](https://twitter.com/ProjectMONAI) or join
our [Slack channel](https://forms.gle/QTxJq3hFictp31UM9).

Ask and answer questions over
on [MONAI Label's GitHub Discussions tab](https://github.com/Project-MONAI/MONAILabel/discussions).

## Links

- Website: https://monai.io/
- API documentation: https://docs.monai.io/projects/label
- Code: https://github.com/Project-MONAI/MONAILabel
Expand Down
Binary file added docs/images/ohif.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 0 additions & 2 deletions monailabel/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,6 @@ class Settings(BaseSettings):
DATASTORE_AUTO_RELOAD: bool = True
DATASTORE_FILE_EXT: List[str] = ["*.nii.gz", "*.nii"]

DICOM_WEB: str = "http://localhost:8042"

CORS_ORIGINS: List[AnyHttpUrl] = []

class Config:
Expand Down
4 changes: 3 additions & 1 deletion monailabel/endpoints/activelearning.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,9 @@ async def sample(strategy: str, params: Optional[dict] = None):
image_id = result["id"]
image_info = instance.datastore().get_image_info(image_id)

return {
result = {
"id": image_id,
**image_info,
}
logger.info(f"Next sample: {result}")
return result
29 changes: 29 additions & 0 deletions monailabel/endpoints/ohif.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import logging
import os

from fastapi import APIRouter, HTTPException
from fastapi.responses import FileResponse

from monailabel.utils.others.generic import get_mime_type

logger = logging.getLogger(__name__)

router = APIRouter(
prefix="/ohif",
tags=["Others"],
responses={404: {"description": "Not found"}},
)


@router.get("/{path:path}", include_in_schema=False)
async def get_ohif(path: str):
ohif_dir = os.path.join(os.path.dirname(os.path.realpath(__file__)), "..", "static", "ohif")
file = os.path.join(ohif_dir, "index.html")
if path:
path = os.path.join(ohif_dir, path.replace("/", os.pathsep))
file = path if os.path.exists(path) else file

if not os.path.exists(file):
logger.info(file)
raise HTTPException(status_code=404, detail="Resource NOT Found")
return FileResponse(file, media_type=get_mime_type(file))
32 changes: 32 additions & 0 deletions monailabel/endpoints/proxy.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import logging

import httpx
from fastapi import APIRouter
from fastapi.responses import Response

from monailabel.config import settings

logger = logging.getLogger(__name__)

router = APIRouter(
prefix="/proxy",
tags=["Others"],
responses={404: {"description": "Not found"}},
)


@router.get("/dicom/{path:path}", include_in_schema=False)
async def proxy(path: str, response: Response):
auth = (
(settings.DICOMWEB_USERNAME, settings.DICOMWEB_PASSWORD)
if settings.DICOMWEB_USERNAME and settings.DICOMWEB_PASSWORD
else None
)

async with httpx.AsyncClient(auth=auth) as client:
proxy_path = f"{settings.STUDIES.lstrip('/')}/{path}"
logger.debug(f"Proxy conneting to {proxy_path}")
proxy = await client.get(proxy_path)
response.body = proxy.content
response.status_code = proxy.status_code
return response
4 changes: 3 additions & 1 deletion monailabel/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
from starlette.middleware.cors import CORSMiddleware

from monailabel.config import settings
from monailabel.endpoints import activelearning, batch_infer, datastore, infer, info, logs, scoring, train
from monailabel.endpoints import activelearning, batch_infer, datastore, infer, info, logs, ohif, proxy, scoring, train
from monailabel.utils.others.app_utils import app_instance
from monailabel.utils.others.generic import init_log_config

Expand Down Expand Up @@ -61,6 +61,8 @@
app.include_router(scoring.router)
app.include_router(datastore.router)
app.include_router(logs.router)
app.include_router(ohif.router)
app.include_router(proxy.router)


@app.get("/", include_in_schema=False)
Expand Down
11 changes: 6 additions & 5 deletions monailabel/utils/datastore/dicom/cache.py
Original file line number Diff line number Diff line change
Expand Up @@ -83,11 +83,12 @@ def get_label_uri(self, label_id: str, label_tag: str, image_id: str = "") -> st

def _dicom_info(self, series_id):
meta = load_json_dataset(self._client.search_for_series(search_filters={"SeriesInstanceUID": series_id})[0])
return {
"SeriesInstanceUID": series_id,
"StudyInstanceUID": str(meta["StudyInstanceUID"].value),
"PatientID": str(meta["PatientID"].value),
}
fields = ["StudyDate", "StudyTime", "Modality", "RetrieveURL", "PatientID", "StudyInstanceUID"]

info = {"SeriesInstanceUID": series_id}
for f in fields:
info[f] = str(meta[f].value)
return info

def list_images(self) -> List[str]:
datasets = self._client.search_for_series(search_filters={"Modality": self._modality})
Expand Down
1 change: 1 addition & 0 deletions plugins/ohif/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
node_modules/
96 changes: 96 additions & 0 deletions plugins/ohif/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
## MONAILabel Plugin for OHIF Viewer

![](Screenshots/1.png)

## Development setup

- Build the OHIF plugin for development:

`(cd plugins/ohif && ./build.sh)`

- Run App with Orthanc (DICOMWeb):

`./monailabel/monailabel start_server -a samples/segmentation_spleen -s http://127.0.0.1:8042/dicom-web`

- Access OHIF: http://127.0.0.1:8000/ohif/

```shell
# If you want to avoid building OHIF package for every code changes,
# you can run OHIF Viewer directly in checked-out git submodule
cd plugins/ohif/Viewers

yarn run dev:orthanc

# in this case ohif will run at http://127.0.0.1:3000/
xdg-open http://127.0.0.1:3000/
```

## Installing Orthanc (DICOMWeb)

### Ubuntu 20.x

```shell
# Install orthanc and dicomweb plugin
sudo apt-get install orthanc orthanc-dicomweb -y

# Install plastimatch
sudo apt-get install plastimatch -y
```

However, you have to **upgrade to latest version** by following steps
mentioned [here](https://book.orthanc-server.com/users/debian-packages.html#replacing-the-package-from-the-service-by-the-lsb-binaries)

```shell
sudo service orthanc stop
sudo wget https://lsb.orthanc-server.com/orthanc/1.9.7/Orthanc --output-document /usr/sbin/Orthanc
sudo rm -f /usr/share/orthanc/plugins/*.so

sudo wget https://lsb.orthanc-server.com/orthanc/1.9.7/libServeFolders.so --output-document /usr/share/orthanc/plugins/libServeFolders.so
sudo wget https://lsb.orthanc-server.com/orthanc/1.9.7/libModalityWorklists.so --output-document /usr/share/orthanc/plugins/libModalityWorklists.so
sudo wget https://lsb.orthanc-server.com/plugin-dicom-web/1.6/libOrthancDicomWeb.so --output-document /usr/share/orthanc/plugins/libOrthancDicomWeb.so

sudo service orthanc restart
```

### Windows/Others _(latest version)_

- Download and Install Orthanc from https://www.orthanc-server.com/download.php

## Converting NIFTI to DICOM

```shell
plastimatch convert --patient-id patient1 --input image.nii.gz --output-dicom test
```

## Uploading DICOM to Orthanc

### Use Orthanc Browser

Use orthanc browser located at http://127.0.0.1:8042/app/explorer.html#upload to upload the files.

### Using STORE SCP/SCU

#### Enable AET

`sudo vim /etc/orthanc/orthanc.json`

```json5
// The list of the known DICOM modalities
"DicomModalities" : {
/**
* Uncommenting the following line would enable Orthanc to
* connect to an instance of the "storescp" open-source DICOM
* store (shipped in the DCMTK distribution) started by the
* command line "storescp 2000".
**/
"sample": ["MONAILABEL", "127.0.0.1", 104]
```
`sudo service orthanc restart`
#### Upload Files
```shell
# If AET 'MONAILABEL' is enabled in Orthanc
python -m pynetdicom storescu 127.0.0.1 4242 test -aet MONAILABEL -r
```
Binary file added plugins/ohif/Screenshots/1.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions plugins/ohif/Viewers
Submodule Viewers added at 4cf171
55 changes: 55 additions & 0 deletions plugins/ohif/build.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
#!/bin/bash

curr_dir="$(pwd)"
my_dir="$(dirname "$(readlink -f "$0")")"

echo "Installing requirements..."
sh $my_dir/requirements.sh

install_dir=${1:-$my_dir/../../monailabel/static/ohif}

echo "Current Dir: ${curr_dir}"
echo "My Dir: ${my_dir}"
echo "Installing OHIF at: ${install_dir}"

cd ${my_dir}
git submodule update --init

cd Viewers

# Viewers/platform/viewer/public/config/default.js
git checkout -- ./platform/viewer/public/config/default.js
sed -i "s|routerBasename: '/'|routerBasename: '/ohif/'|g" ./platform/viewer/public/config/default.js
sed -i "s|name: 'DCM4CHEE'|name: 'Orthanc'|g" ./platform/viewer/public/config/default.js
sed -i "s|https://server.dcmjs.org/dcm4chee-arc/aets/DCM4CHEE/wado|/proxy/dicom/wado|g" ./platform/viewer/public/config/default.js
sed -i "s|https://server.dcmjs.org/dcm4chee-arc/aets/DCM4CHEE/rs|/proxy/dicom|g" ./platform/viewer/public/config/default.js

# Viewers/platform/viewer/.env
git checkout -- ./platform/viewer/.env
sed -i "s|PUBLIC_URL=/|PUBLIC_URL=/ohif/|g" ./platform/viewer/.env

# monailabel plugin
cd extensions
rm monai-label
ln -s ../../monai-label monai-label
cd ..

git checkout -- ./platform/viewer/src/index.js
sed -i "s|let config = {};|import OHIFMONAILabelExtension from '@ohif/extension-monai-label';\nlet config = {};|g" ./platform/viewer/src/index.js
sed -i "s|defaultExtensions: \[|defaultExtensions: \[OHIFMONAILabelExtension,|g" ./platform/viewer/src/index.js

yarn config set workspaces-experimental true
yarn install
rm -rf ./Viewers/platform/viewer/dist
QUICK_BUILD=true yarn run build

# Reset if you want to run directly from yarn run dev:orthanc (without monailabel server)
git checkout -- platform/viewer/.env
git checkout -- platform/viewer/public/config/default.js
git checkout -- yarn.lock
cd ..

rm -rf ${install_dir}
mv ./Viewers/platform/viewer/dist ${install_dir}
echo "Copied OHIF to ${install_dir}"
cd ${curr_dir}
8 changes: 8 additions & 0 deletions plugins/ohif/monai-label/.webpack/webpack.dev.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
const path = require('path');
const webpackCommon = require('./../../../.webpack/webpack.commonjs.js');
const SRC_DIR = path.join(__dirname, '../src');
const DIST_DIR = path.join(__dirname, '../dist');

module.exports = (env, argv) => {
return webpackCommon(env, argv, { SRC_DIR, DIST_DIR });
};
Loading

0 comments on commit 5060051

Please sign in to comment.