Sign inSign up

osimis/odelia-viewer

By osimis

•Updated almost 3 years ago

Image
0

1.6K

osimis/odelia-viewer repository overview

⁠Odelia Webviewer

This docker image provides OHIF webviewer enhanced with labeling and AI integration functionality for Odelia⁠ project

⁠Usage

While this image could be used standalone, we recommend using docker-compose with provided configuration files for best experience.

⁠Running
docker-compose up -d
⁠Typical directory structure

We propose the following work directory structure to start odelia webviewer from.

odelia-viewer
├── config    # Configuration files folder
│   ├── nginx.conf
│   ├── orthanc.json
│   ├── odelia-config.js
├── logs 	   # Folder for nginx logs
├── volumes    # Mounted volumes directory to store studies
├── docker-compose.yaml    #docker compose configuration file
⁠Example configuration files

Click to expand relevant sections

Replace

https://odelia.lua.tatar

with IP address of your machine or

http://127.0.0.1:8080

for local deployment

nginx.conf
worker_processes 2;

error_log /var/logs/nginx/mydomain.error.log;

pid /var/run/nginx.pid;

include /usr/share/nginx/modules/*.conf; # See /usr/share/doc/nginx/README.dynamic.



events {

worker_connections 1024; ## Default: 1024

use epoll; # http://nginx.org/en/docs/events.html

multi_accept on; # http://nginx.org/en/docs/ngx_core_module.html#multi_accept

}



# Core Modules Docs:

# http://nginx.org/en/docs/http/ngx_http_core_module.html

http {

include '/usr/local/openresty/nginx/conf/mime.types';

default_type application/octet-stream;



keepalive_timeout 65;

keepalive_requests 100000;

tcp_nopush on;

tcp_nodelay on;



# lua_ settings

#

lua_package_path '/usr/local/openresty/lualib/?.lua;;';

lua_shared_dict discovery 1m; # cache for discovery metadata documents

lua_shared_dict jwks 1m; # cache for JWKs

# lua_ssl_trusted_certificate /etc/ssl/certs/ca-certificates.crt;



variables_hash_max_size 2048;

server_names_hash_bucket_size 128;

server_tokens off;
resolver 8.8.8.8 valid=30s ipv6=off;
resolver_timeout 11s;
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';
# Nginx `listener` block
server {
listen [::]:80 default_server;
listen 80;
access_log /var/logs/nginx/mydomain.access.log;
# Domain to protect
server_name 127.0.0.1 localhost; # mydomain.com;
proxy_intercept_errors off;
gzip on;
gzip_types text/css application/javascript application/json image/svg+xml;
gzip_comp_level 9;
etag on;
# https://github.com/bungle/lua-resty-session/issues/15
set $session_check_ssi off;
lua_code_cache off;
set $session_secret Eeko7aeb6iu5Wohch9Loo1aitha0ahd1;
set $session_storage cookie;
server_tokens off; # Hides server version num
# Reverse Proxy for `orthanc` admin
#
location /pacs-admin/ {
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
expires 0;
add_header Cache-Control private;
proxy_pass http://orthanc:8042/;

}



# Reverse Proxy for `orthanc` APIs (including DICOMWeb)

#

location /pacs/ {



proxy_http_version 1.1;



proxy_set_header Host $host;

proxy_set_header X-Real-IP $remote_addr;

proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;

proxy_set_header X-Forwarded-Proto $scheme;



expires 0;

add_header Cache-Control private;



proxy_pass http://orthanc:8042/;



# By default, this endpoint is protected by CORS (cross-origin-resource-sharing)

# You can add headers to allow other domains to request this resource.

# See the "Updating CORS Settings" example below

}



# Do not cache sw.js, required for offline-first updates.

location /sw.js {

add_header Cache-Control "no-cache";

proxy_cache_bypass $http_pragma;

proxy_cache_revalidate on;

expires off;

access_log off;

}



# Single Page App

# Try files, fallback to index.html

#

location / {

alias /var/www/html/;

index index.html;

try_files $uri $uri/ /index.html;

add_header Cache-Control "no-store, no-cache, must-revalidate";

add_header 'Cross-Origin-Embedder-Policy'  'require-corp';

add_header 'Cross-Origin-Opener-Policy'  'same-origin';

}



# EXAMPLE: Redirect server error pages to the static page /40x.html

#

# error_page 404 /404.html;

# location = /40x.html {

# }



# EXAMPLE: Redirect server error pages to the static page /50x.html

#

# error_page 500 502 503 504 /50x.html;

# location = /50x.html {

# }

}

}
odelia-config.js
window.config = {
  routerBasename: '/',
  // whiteLabeling: {},
  extensions: [],
  modes: [],
  customizationService: {
    // Shows a custom route -access via http://localhost:3000/custom
    // helloPage: '@ohif/extension-default.customizationModule.helloPage',
  },
  showStudyList: true,
  // some windows systems have issues with more than 3 web workers
  maxNumberOfWebWorkers: 3,
  // below flag is for performance reasons, but it might not work for all servers
  omitQuotationForMultipartRequest: true,
  showWarningMessageForCrossOrigin: true,
  showCPUFallbackMessage: true,
  showLoadingIndicator: true,
  strictZSpacingForVolumeViewport: true,
  maxNumRequests: {
    interaction: 100,
    thumbnail: 75,
    // Prefetch number is dependent on the http protocol. For http 2 or
    // above, the number of requests can be go a lot higher.
    prefetch: 25,
  },
  // filterQueryParam: false,
  dataSources: [
     {
      friendlyName: 'Orthanc Server',
      namespace: '@ohif/extension-default.dataSourcesModule.dicomweb',
      sourceName: 'dicomweb',
      configuration: {
        name: 'Orthanc',
        wadoUriRoot: 'https://odelia.lua.tatar/pacs/wado',
        qidoRoot: 'https://odelia.lua.tatar/pacs/dicom-web',
        wadoRoot: 'https://odelia.lua.tatar/pacs/dicom-web',
        qidoSupportsIncludeField: false,
        imageRendering: 'wadors',
        thumbnailRendering: 'wadors',
      },
    },
    {
      friendlyName: 'dicom json',
      namespace: '@ohif/extension-default.dataSourcesModule.dicomjson',
      sourceName: 'dicomjson',
      configuration: {
        name: 'json',
      },
    },
    {
      friendlyName: 'dicom local',
      namespace: '@ohif/extension-default.dataSourcesModule.dicomlocal',
      sourceName: 'dicomlocal',
      configuration: {},
    },

    {
      friendlyName: 'dicomweb delegating proxy',
      namespace: '@ohif/extension-default.dataSourcesModule.dicomwebproxy',
      sourceName: 'dicomwebproxy',
      configuration: {
        name: 'dicomwebproxy',
      },
    },
    {
      friendlyName: 'dicom json',
      namespace: '@ohif/extension-default.dataSourcesModule.dicomjson',
      sourceName: 'dicomjson',
      configuration: {
        name: 'json',
      },
    },
    {
      friendlyName: 'dicom local',
      namespace: '@ohif/extension-default.dataSourcesModule.dicomlocal',
      sourceName: 'dicomlocal',
      configuration: {},
    },
  ],
  httpErrorHandler: error => {
    // This is 429 when rejected from the public idc sandbox too often.
    console.warn(error.status);

    // Could use services manager here to bring up a dialog/modal if needed.
    console.warn('test, navigate to https://ohif.org/');
  },
 
  defaultDataSourceName: 'dicomweb',
  hotkeys: [
    {
      commandName: 'incrementActiveViewport',
      label: 'Next Viewport',
      keys: ['right'],
    },
    {
      commandName: 'decrementActiveViewport',
      label: 'Previous Viewport',
      keys: ['left'],
    },
    { commandName: 'rotateViewportCW', label: 'Rotate Right', keys: ['r'] },
    { commandName: 'rotateViewportCCW', label: 'Rotate Left', keys: ['l'] },
    { commandName: 'invertViewport', label: 'Invert', keys: ['i'] },
    {
      commandName: 'flipViewportHorizontal',
      label: 'Flip Horizontally',
      keys: ['h'],
    },
    {
      commandName: 'flipViewportVertical',
      label: 'Flip Vertically',
      keys: ['v'],
    },
    { commandName: 'scaleUpViewport', label: 'Zoom In', keys: ['+'] },
    { commandName: 'scaleDownViewport', label: 'Zoom Out', keys: ['-'] },
    { commandName: 'fitViewportToWindow', label: 'Zoom to Fit', keys: ['='] },
    { commandName: 'resetViewport', label: 'Reset', keys: ['space'] },
    { commandName: 'nextImage', label: 'Next Image', keys: ['down'] },
    { commandName: 'previousImage', label: 'Previous Image', keys: ['up'] },
    
    {
      commandName: 'setToolActive',
      commandOptions: { toolName: 'Zoom' },
      label: 'Zoom',
      keys: ['z'],
    },
    // ~ Window level presets
    {
      commandName: 'windowLevelPreset1',
      label: 'W/L Preset 1',
      keys: ['1'],
    },
    {
      commandName: 'windowLevelPreset2',
      label: 'W/L Preset 2',
      keys: ['2'],
    },
    {
      commandName: 'windowLevelPreset3',
      label: 'W/L Preset 3',
      keys: ['3'],
    },
    {
      commandName: 'windowLevelPreset4',
      label: 'W/L Preset 4',
      keys: ['4'],
    },
    {
      commandName: 'windowLevelPreset5',
      label: 'W/L Preset 5',
      keys: ['5'],
    },
    {
      commandName: 'windowLevelPreset6',
      label: 'W/L Preset 6',
      keys: ['6'],
    },
    {
      commandName: 'windowLevelPreset7',
      label: 'W/L Preset 7',
      keys: ['7'],
    },
    {
      commandName: 'windowLevelPreset8',
      label: 'W/L Preset 8',
      keys: ['8'],
    },
    {
      commandName: 'windowLevelPreset9',
      label: 'W/L Preset 9',
      keys: ['9'],
    },
  ],
};
docker-compose.yaml
# Reference:
# - https://docs.docker.com/compose/compose-file
# - https://eclipsesource.com/blogs/2018/01/11/authenticating-reverse-proxy-with-keycloak/

version: '3.5'

services:
  # Exposed server that's handling incoming web requests
  # Underlying image: openresty/openresty:alpine-fat
  ohif_viewer:
    image: osimis/odelia-viewer:latest
    container_name: ohif
    volumes:
      # Nginx config
      - ./config/nginx.conf:/usr/local/openresty/nginx/conf/nginx.conf:ro
      # Logs
      - ./logs/nginx:/var/logs/nginx
      - ./config/odelia-config.js:/var/www/html/app-config.js
      # Let's Encrypt
      # - letsencrypt_certificates:/etc/letsencrypt
      # - letsencrypt_challenges:/var/www/letsencrypt
    ports:
      - '443:443' # SSL
      - '8080:80' # Web
    depends_on:
      - orthanc
    restart: on-failure

  # LINK: https://hub.docker.com/r/jodogne/orthanc-plugins/
  # https://github.com/mrts/docker-postgresql-multiple-databases
  orthanc:
    image: jodogne/orthanc-plugins:1.12.2
    hostname: orthanc
    container_name: orthanc
    volumes:
      # Config
      - ./config/orthanc.json:/etc/orthanc/orthanc.json:ro
      # Persist data
      - ./volumes/orthanc-db/:/var/lib/orthanc/db/
    restart: unless-stopped
orthanc.json
{
  "Name": "Orthanc inside Docker",
  "StorageDirectory": "/var/lib/orthanc/db",
  "IndexDirectory": "/var/lib/orthanc/db",
  "StorageCompression": false,
  "MaximumStorageSize": 0,
  "MaximumPatientCount": 0,
  "LuaScripts": [],
  "Plugins": ["/usr/share/orthanc/plugins", "/usr/local/share/orthanc/plugins"],
  "ConcurrentJobs": 2,
  "HttpServerEnabled": true,
  "HttpPort": 8042,
  "HttpDescribeErrors": true,
  "HttpCompressionEnabled": true,
  "DicomServerEnabled": true,
  "DicomAet": "ORTHANC",
  "DicomCheckCalledAet": false,
  "DicomPort": 4242,
  "DefaultEncoding": "Latin1",
  "DeflatedTransferSyntaxAccepted": true,
  "JpegTransferSyntaxAccepted": true,
  "Jpeg2000TransferSyntaxAccepted": true,
  "JpegLosslessTransferSyntaxAccepted": true,
  "JpipTransferSyntaxAccepted": true,
  "Mpeg2TransferSyntaxAccepted": true,
  "RleTransferSyntaxAccepted": true,
  "UnknownSopClassAccepted": false,
  "DicomScpTimeout": 30,

  "RemoteAccessAllowed": true,
  "SslEnabled": false,
  "SslCertificate": "certificate.pem",
  "AuthenticationEnabled": false,
  "RegisteredUsers": {
    "test": "test"
  },
  "DicomModalities": {},
  "DicomModalitiesInDatabase": false,
  "DicomAlwaysAllowEcho": true,
  "DicomAlwaysAllowStore": true,
  "DicomCheckModalityHost": false,
  "DicomScuTimeout": 10,
  "OrthancPeers": {},
  "OrthancPeersInDatabase": false,
  "HttpProxy": "",

  "HttpVerbose": true,

  "HttpTimeout": 10,
  "HttpsVerifyPeers": true,
  "HttpsCACertificates": "",
  "UserMetadata": {},
  "UserContentType": {},
  "StableAge": 60,
  "StrictAetComparison": false,
  "StoreMD5ForAttachments": true,
  "LimitFindResults": 0,
  "LimitFindInstances": 0,
  "LimitJobs": 10,
  "LogExportedResources": false,
  "KeepAlive": true,
  "TcpNoDelay": true,
  "HttpThreadsCount": 50,
  "StoreDicom": true,
  "DicomAssociationCloseDelay": 5,
  "QueryRetrieveSize": 10,
  "CaseSensitivePN": false,
  "LoadPrivateDictionary": true,
  "Dictionary": {},
  "SynchronousCMove": true,
  "JobsHistorySize": 10,
  "SaveJobs": true,
  "OverwriteInstances": false,
  "MediaArchiveSize": 1,
  "StorageAccessOnFind": "Always",
  "MetricsEnabled": true,

  "DicomWeb": {
    "Enable": true,
    "Root": "/dicom-web/",
    "EnableWado": true,
    "WadoRoot": "/wado",
    "Host": "127.0.0.1",
    "Ssl": false,
    "StowMaxInstances": 10,
    "StowMaxSize": 10,
    "QidoCaseSensitive": false
  }
}

⁠Acknowledgement

Tag summary

Content type

Image

Digest

sha256:11905711c…

Size

106.1 MB

Last updated

almost 3 years ago

docker pull osimis/odelia-viewer