Skip to content

Commit

Permalink
Add image notification and background processing example code (sectra…
Browse files Browse the repository at this point in the history
…-medical#7)

* Add ia_wholeslide example

Fixes sectra-medical#1 sectra-medical#2 and sectra-medical#3
  • Loading branch information
sectra-masve authored Nov 21, 2024
1 parent b7b7782 commit d8a4ec7
Show file tree
Hide file tree
Showing 21 changed files with 2,584 additions and 43 deletions.
29 changes: 13 additions & 16 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,31 +1,28 @@
# Sectra Digital Pathology (DPAT) ImageAnalysis (IA) API - Software Development Kit (SDK)

## About
This is a work in progress repository providing examples on how to integrate with the Sectra IA-API.
This repository provides examples on integrating with the Sectra IA-API using python code.

Currently only a basic application exist that returns (random) results given user input.
The `examples` folder provides two examples that are suitable to get started.
Coding style is intentionally minimalistic, aiming for quick and easy reading, as we expect downstream users to apply their own style and preferences.

See the `examples` folder for more info. A roadmap is available under [milestones](https://github.com/sectra-medical/dpat_imageanalysisapi_sdk/milestones)
## Contributing

## Plan for Sectra-led contributions
We are open to accept contributions. This could be:

### 2024, January
- typed clients (e.g. implementing the API specification as dataclasses)
- further examples (likely to be placed below a `contrib/` folder)

- Invite collaborators to vet/discuss the basic example
## Planned extensions

### 2024, June
We plan on adding example code for integrating with IDS7 worklists by sending HL7v2 messages.

- Finalize image notification python examples (M1 milestone)

### 2024, August

- Make project fully public (remove invite-only)

### 2024, December
## Changelog

- Finalize extended "best practices" example (M2 milestone)
### 2024-11-20

## Changelog
- Add background image processing example ( `examples/python/ia_wholeslide` )
- Make the repository public

### 2024-06-13

Expand Down
55 changes: 55 additions & 0 deletions examples/python/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
# Sectra Digital Pathology ImageAnalysis (IA) API

## Getting started in python

This document outlines how we suggest you try the examples to get started using python.

### Prerequisites

- We assume you have a copy of the document *"Interface Specification: Pathology Image Analysis Application Interface, Sectra Digital Pathology Module"*. At the time of writing, the most recent version was *"Verion 4.1, August 2024"*.
- This document describes all the REST endpoints and corresponding json schemas.
- Contact Sectra support for a copy, see https://my.sectra.com/en-US/support-contact/

- You will need to have access to a Sectra PACS with the Digital Pathology module
- We suggest you start testing in your testing Sectra PACS instance, if such an installatino is available to you.
- You will need the appropriate permissions to add and configure IA Applications in that Sectra PACS, or be in contact with someone who has
- For third party AI vendors, there is an online Sectra PACS available for a fee. Contact Sectra Support and request "discussing access to a test PACS for digital pathology AI vendors".

- You will need ensure TCP/IP connectivity between the Sectra PACS server and the machine running your IA-APP.
- Both parties will initiate communications, so both servers need to be accessible to each other under appropiate DNS names.

- The python examples uses the `uv` package manager, see https://docs.astral.sh/uv/getting-started/installation/ for installation.

## 1. Starting the most basic app

See the README in `./ia_app_basic` for more detailed instructions, TLDR:

```
cd examples/python/ia_app_basic
./flask_run.sh
```

This starts a webserver listening on 0.0.0.0:5005 (all ips, port 5005) on the machine you started it on.

Next, you will need to register this with the Sectra PACS DPAT Server. For more detailed instructions, refer to Chapter 7 in the *System Administrator Manual*.

- Go to https://<pathologyserver>/sectrapathologyimport/config
- log in, click Image Analysis Applications
- Under Server Side Applications, click 'register new'
- Enter the URL where your started web server (this repository) is running, as reachable from the pathology server.
- Example: `http://my-ia-app-server:5001/iademo`
- NOTE! Ensuring connectivity can take some work in most hospital environments.
- Press 'Retrieve registration Info'
- The fields should be populated, press *Save*
- Per default, the app is disabled. Click the 'disabled' button to toggle it to enabled.

If succesful, you should now be able to right-click in any Pathology Image and select your new IA-APP (you might need to refresh any running sessions).

TODO: screenshot here of succesful operation
TODO: screenshot of configuration UI

## 2. A more realistic example

TODO: Writeme


34 changes: 24 additions & 10 deletions examples/python/ia_app_basic/README.md
Original file line number Diff line number Diff line change
@@ -1,29 +1,42 @@
pcaddemo
========
# Sectra DPAT IA-API Example: Basic App

## About
This is a demo repository for a simple Sectra Image Analysis API (IA-API) implementation.
A very simple Sectra Image Analysis API (IA-API) implementation for a Server Side App.

Author: Martin Lindvall, [email protected]
The example is extremely barebone, with the goal to get you started and see that you manage to get the Sectra DPAT - App network connections functioning.

## Application function
For a more realistic implementation where WSI processing is done in longer-running background jobs, see `../ia_wholeslide`

### Demo Application function

- Input: User draws a polygon around a region of interest ( taggedPolygon, see `webserver.py:app_return_registerinfo` )

- Output: a graphical primitive, or a "patch gallery" (see `webserver.py:DEMO_TYPE` and `webserver.py:app_on_userinput` )

## Development notes
### Development notes
Uses a simple flask server for responding to IA-API requests and a requests-based client for outgoing communications with the IA-API..

For simplicity, uses no async libraries. For load-scaling, simply run with number of workers processes corresponding to expected max number of simultaneous users. Tools such as gunicorn or a similar pre-forking worker server is recommended to spawn the workers.
For simplicity, uses no async libraries.

For load-scaling, simply run with number of workers processes corresponding to expected max number of simultaneous users. Tools such as gunicorn or a similar pre-forking worker server is recommended to spawn the workers.

Should have no trouble scaling as long as number of max simultaneous users are below 1000 or so.

### Prerequisites
## Usage

In order to try this out you'll need to

1. Get the server within this folder running (see Install and Run)
2. Configure Sectra DPAT adding the URL of the server to the list of configured IA-Apps (see Configuring the Sectra DPAT Server)

### Install and run

#### Prerequisites

You currently need libgeos to build shapely. On ubuntu `sudo apt install libgeos-dev`.
The shapely dependency will eventually be removed.

### Install and run
#### Starting

You can run this example in a virtualenv using poetry, or directly in the current python environment.

Expand All @@ -40,7 +53,7 @@ pip install -e .
python pcaddemo/__main__.py
```

## Sectra Server configuration
### Configuring the Sectra DPAT Server

Running this demo starts a web server on 0.0.0.0 (all local ips) listening on port 5001.

Expand All @@ -62,3 +75,4 @@ If succesful, you should now be able to right-click in any Pathology Image and s
## Tested with

- DPAT 3.4 on 2024-01-05
- DPAT 4.1 on 2024-11-21
38 changes: 21 additions & 17 deletions examples/python/ia_app_basic/pcaddemo/webserver.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,6 @@
APP_NAME = "ImageAnalysisDemo"
APP_MANUFACTURER = "Sectra (demo)"
BIND_PORT = 5005
DEMO_TYPE = "gallery" # use 'polygon' or 'gallery'


def json_resp(data):
"""serialize data to JSON and add appropriate IA-api headers"""
Expand Down Expand Up @@ -71,7 +69,7 @@ def app_return_registerinfo():
"url": f"http://{hostname}/iademo",
"manufacturer": APP_MANUFACTURER,
"inputTemplate": {"type": "taggedPolygon", "content": {"tags": []}},
"context": {},
"context": {"gallery": False},
}
return json_resp(data)

Expand All @@ -90,16 +88,17 @@ def app_on_userinput():
response = {}
data = request.get_json()

# save_filename = f"./debug/{data['action']}_tmp.json"
# with open(save_filename, 'w') as file:
# json.dump(data, file)
save_filename = f"./debug/userinput_{data['action']}_tmp.json"
with open(save_filename, 'w') as file:
json.dump(data, file)

# data['action'] :: create, modify, delete, cancel
if data["action"] == "create":
if DEMO_TYPE == "polygon":
return app_create_primitiveArea(data)
else:
use_patch_gallery = "gallery" in data["context"] and data["context"]["gallery"]
if use_patch_gallery:
return app_create_patchCollection(data)
else:
return app_create_primitiveArea(data)
elif data["action"] == "modify":
for button in data["input"]["data"]["result"]["content"]["actions"]:
if button["state"] == 1:
Expand All @@ -121,22 +120,26 @@ def app_create_primitiveArea(data):
this demo implementation simply returns the input geometry with a label attached
"""
input_polygon = data["input"]["content"]["polygon"]
plg = sectra_polygon_to_shapely(input_polygon)
# determine suitable placement of label text
min_y_pt = reduce(
lambda pt1, pt2: pt2 if pt2[1] < pt1[1] else pt1, plg.exterior.coords
)
# this demo app supports being configured as either type: multiArea or type: taggedPolygon
input_polygons = []
if data["input"]["type"] == "taggedPolygon":
input_polygons = [data["input"]["content"]["polygon"]]
elif data["input"]["type"] == "multiArea":
input_polygons = data["input"]["content"]["polygons"]

# determine suitable label placement (choose uppermost point in polygon)
all_points = [pt for polyg in input_polygons for pt in polyg["points"]]
min_y_pt = reduce(lambda pt1, pt2: pt2 if pt2["y"] < pt1["y"] else pt1, all_points)

text = "0 demo-positive cells found."

response_result = {
"type": "primitive",
"content": {
"style": {"fillStyle": None, "size": None, "strokeStyle": "#FFA500"},
"polygons": [input_polygon], # same area as user input,
"polygons": input_polygons, # same area as user input,
"labels": [
{"location": {"x": min_y_pt[0], "y": min_y_pt[1]}, "label": text}
{"location": min_y_pt, "label": text}
],
},
}
Expand Down Expand Up @@ -233,6 +236,7 @@ def app_modify_button(button_id, data):
def app_delete(data):
"""result has been deleted by the user, and our app is informed"""
# do not intervene (will get the result deleted)
# TODO: server does not like empty response any more?
return json_resp({})


Expand Down
35 changes: 35 additions & 0 deletions examples/python/ia_wholeslide/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# Temporary Python files
*.pyc
*.egg-info
__pycache__
.ipynb_checkpoints

# Temporary OS files
Icon*

# Temporary virtual environment files
/.cache/
/.venv/

# Temporary server files
.env
*.pid

# Build and release directories
/build/
/dist/
/debug/
*.spec

# Sublime Text
*.sublime-workspace

# Eclipse
.settings

# Don't include
scripts
.python-version

data/queue
data/requests
Loading

0 comments on commit d8a4ec7

Please sign in to comment.