Skip to content

Commit 2a60fed

Browse files
authored
Merge pull request #448 from DigitalSlideArchive/selection-algorithm
Add an example slide selection algorithm.
2 parents 4f1deca + 12abef5 commit 2a60fed

File tree

16 files changed

+456
-6
lines changed

16 files changed

+456
-6
lines changed

.pre-commit-config.yaml

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ repos:
4343
files: README.rst
4444
name: rst-linter of README.rst
4545
- repo: https://github.com/codespell-project/codespell
46-
rev: v2.3.0
46+
rev: v2.4.1
4747
hooks:
4848
- id: codespell
4949
args:
@@ -63,13 +63,13 @@ repos:
6363
# - './histomicsui/web_client/package.json'
6464
- repo: https://github.com/astral-sh/ruff-pre-commit
6565
# Ruff version.
66-
rev: v0.8.3
66+
rev: v0.12.0
6767
hooks:
6868
- id: ruff
6969
args: [--fix, --exit-non-zero-on-fix]
7070
types_or: [python, pyi, jupyter]
7171
- repo: https://github.com/asottile/pyupgrade
72-
rev: v3.19.0
72+
rev: v3.20.0
7373
hooks:
7474
- id: pyupgrade
7575
args:
@@ -80,6 +80,6 @@ repos:
8080
hooks:
8181
- id: autopep8
8282
- repo: https://github.com/PyCQA/flake8
83-
rev: 7.1.1
83+
rev: 7.2.0
8484
hooks:
8585
- id: flake8

README.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,5 +29,6 @@ Navigating the Documentation
2929
* `docs/GLOSSARY.rst <docs/GLOSSARY.rst>`_ contains a glossary of technical terms.
3030
* `docs/ERROR-TABLES.rst <docs/ERROR-TABLES.rst>`_ contains tables of explanations for error messages that users may encounter.
3131
* `docs/CUSTOMIZING.rst <docs/CUSTOMIZING.rst>`_ contains details on customizing the workflow for different use cases.
32+
* `docs/TASKS.rst <docs/TASKS.rst>`_ contains an alternative deployment to allow running external tasks, such as a slide selection algorithm.
3233
* `docs/APIUSAGE.rst <docs/APIUSAGE.rst>`_ describes how to make API called for perform common workflow actions.
3334
* `docs/rationale.md <docs/rationale.md>`_ contains a rationale of the SEER WSI DeID pilot project.
Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
---
2+
services:
3+
girder:
4+
image: dsarchive/wsi_deid:latest
5+
restart: unless-stopped
6+
# To have full capabilities with S3 assetstores, we use a user file system
7+
# (fuse). This requires some privileges. This is not needed if only
8+
# filesystem assetstores are used. Instead of privileged mode, fuse can
9+
# use specific devices, security_opt, and cap_add:
10+
# devices:
11+
# - /dev/fuse:/dev/fuse
12+
# security_opt:
13+
# - apparmor:unconfined
14+
# cap_add:
15+
# - SYS_ADMIN
16+
# but these may be somewhat host specific, so we default to privileged. If
17+
# the docker daemon is being run with --no-new-privileges, fuse may not
18+
# work.
19+
# See also https://github.com/docker/for-linux/issues/321 for possible
20+
# methods to avoid both privileged mode and cap_add SYS_ADMIN.
21+
privileged: true
22+
# Set DSA_PORT to expose the interface on another port (default 8080).
23+
ports:
24+
- "${DSA_PORT:-8080}:8080"
25+
# Set DSA_USER to a user id that is part of the docker group (e.g.,
26+
# `DSA_USER=$(id -u):$(id -g)`). This makes files in assetstores and logs
27+
# owned by that user and provides permissions to manage docker
28+
environment:
29+
DSA_USER: ${DSA_USER:-}
30+
GIRDER_CONFIG: /conf/girder.local.conf
31+
PROVISION: tasks
32+
volumes:
33+
# Needed to use slicer_cli_web to run docker containers
34+
- /var/run/docker.sock:/var/run/docker.sock
35+
# Default assetstore
36+
- fsdata:/assetstore
37+
- logs:/logs
38+
# Change for local files:
39+
# - ./assetstore:/assetstore
40+
# - ./logs:/logs
41+
# Location of girder.local.conf and provision.py; add to use local
42+
# versions
43+
# - .:/conf
44+
# Add for import and export location. This can also be done with a
45+
# docker-compose.local.yml file. See the example.
46+
# - <some local path>:/import
47+
# - <some local path>:/export
48+
- ./worker.local.cfg:/venv/lib/python3.11/site-packages/girder_worker/worker.local.cfg
49+
depends_on:
50+
- mongodb
51+
- memcached
52+
- rabbitmq
53+
mongodb:
54+
image: "mongo:latest"
55+
restart: unless-stopped
56+
volumes:
57+
# Location to store database files
58+
# Change for local files:
59+
# - ./db:/data/db
60+
# - ./logs:/var/log/mongodb
61+
- dbdata:/data/db
62+
memcached:
63+
image: memcached
64+
command: -m 4096
65+
restart: unless-stopped
66+
# rabbitmq is used to communicate to the worker to run tasks
67+
rabbitmq:
68+
image: "rabbitmq:latest"
69+
restart: unless-stopped
70+
environment:
71+
RABBITMQ_DEFAULT_USER: ${RABBITMQ_DEFAULT_USER:-}
72+
RABBITMQ_DEFAULT_PASS: ${RABBITMQ_DEFAULT_PASS:-}
73+
volumes:
74+
- ./rabbitmq.advanced.config:/etc/rabbitmq/advanced.config:ro
75+
worker:
76+
image: dsarchive/wsi_deid:latest
77+
# Set DSA_USER to a user id that is part of the docker group (e.g.,
78+
# `DSA_USER=$(id -u):$(id -g)`). This provides permissions to manage
79+
# docker
80+
environment:
81+
DSA_USER: ${DSA_USER:-}
82+
PROVISION: worker
83+
C_FORCE_ROOT: true
84+
restart: unless-stopped
85+
volumes:
86+
# Needed to use slicer_cli_web to run docker containers
87+
- /var/run/docker.sock:/var/run/docker.sock
88+
# Needed to allow transferring data to slicer_cli_web docker containers
89+
- ${TMPDIR:-/tmp}:${TMPDIR:-/tmp}
90+
- ./worker.local.cfg:/venv/lib/python3.11/site-packages/girder_worker/worker.local.cfg
91+
depends_on:
92+
- rabbitmq
93+
command: bash -c "C_FORCE_ROOT=true PROVISION=worker python /conf/provision.py && python -m girder_worker --concurrency=1 -Ofair --prefetch-multiplier=1"
94+
95+
volumes:
96+
dbdata:
97+
fsdata:
98+
logs:

devops/wsi_deid/provision.py

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import os
2+
import sys
23

34
import girder.utility.config
45
from girder.models import getDbConnection
@@ -91,6 +92,22 @@ def provision(): # noqa
9192
folder['name'] = folderName
9293
folder['public'] = public
9394
Folder().save(folder)
95+
if os.environ.get('PROVISION') == 'tasks':
96+
taskCollName = 'Tasks'
97+
if Collection().findOne({'lowerName': taskCollName.lower()}) is None:
98+
Collection().createCollection(taskCollName, adminUser)
99+
taskCollection = Collection().findOne({'lowerName': taskCollName.lower()})
100+
taskFolder = Folder().createFolder(
101+
taskCollection, 'Slicer CLI Web Tasks', parentType='collection',
102+
public=True, creator=adminUser, reuseExisting=True)
103+
Setting().set('slicer_cli_web.task_folder', str(taskFolder['_id']))
104+
Setting().set('worker.broker', 'amqp://guest:guest@rabbitmq')
105+
Setting().set('worker.backend', 'rpc://guest:guest@rabbitmq')
106+
Setting().set('worker.api_url', 'http://girder:8080/api/v1')
107+
try:
108+
os.chmod('/var/run/docker.sock', 0o777)
109+
except Exception:
110+
pass
94111
# Set default import/export paths
95112
if not Setting().get(PluginSettings.WSI_DEID_IMPORT_PATH):
96113
Setting().set(PluginSettings.WSI_DEID_IMPORT_PATH, '/import')
@@ -127,6 +144,12 @@ def provision(): # noqa
127144

128145

129146
if __name__ == '__main__':
147+
if os.environ.get('PROVISION') == 'worker':
148+
try:
149+
os.chmod('/var/run/docker.sock', 0o777)
150+
except Exception:
151+
pass
152+
sys.exit(0)
130153
# This loads plugins, allowing setting validation
131154
configureServer()
132155
provision()
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
[ {rabbit, [ {consumer_timeout, undefined} ]} ].

devops/wsi_deid/worker.local.cfg

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
[celery]
2+
app_main=girder_worker
3+
broker=amqp://guest:guest@rabbitmq/
4+
backend=rpc://guest:guest@rabbitmq/
5+
6+
[girder_worker]
7+
8+
[logging]
9+
level = debug
10+
format = [%%(asctime)s] %%(levelname)s: %%(message)s

docs/TASKS.rst

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
Installation for Running Tasks
2+
==============================
3+
4+
See `INSTALL.rst <./INSTALL.rst>`_ for basic installation. This has the same
5+
prerequisites, but adds running custom tasks, like a third-party slide
6+
selection algorithm.
7+
8+
.. contents:: Table of Contents
9+
:depth: 1
10+
:local:
11+
:backlinks: none
12+
13+
Differences in Start
14+
--------------------
15+
16+
Compared to starting without task support, we need to use a different docker-compose.yml file, and, for non-Windows systems, we need to specify the user that is running the process which grants permission to run other docker containers.
17+
18+
Pulling images, stopping, and updating are the same.
19+
20+
The start command changes to::
21+
22+
docker-compose -f docker-compose.with-tasks.yml -f docker-compose.local.yml up -d
23+
24+
As before, the system will be available from a web browser on http://localhost:8080.
25+
26+
Adding a Task
27+
-------------
28+
29+
Tasks can be added by going to ``Collections`` -> ``Tasks`` -> ``Slicer CLI Web Tasks`` and clicking the upload CLI button.
30+
31+
.. image:: screenshots/upload_cli_button.png
32+
:alt: The upload CLI button
33+
34+
We need an example task to demonstrate. This can be built from the command line by going to ``utilities/sample-algorithm`` directory, and running ``docker build --force-rm -t dsarchive/example_ssa .``. At the end of this process, there will be a docker image named ``dsarchive/example_ssa:latest``.
35+
36+
Type ``dsarchive/example_ssa:latest`` in the Upload CLI Docker Images dialog and click ``Import Image``. After a short amount of time, the new task will be available.
37+
38+
Running a Task
39+
--------------
40+
41+
Navigate to the task, e.g., ``Collections`` -> ``Tasks`` -> ``Slicer CLI Web Tasks`` -> ``dsarchive/example_ssa`` -> ``latest`` -> ``Slide Selection``. Select the appropriate parameters as you desire and click ``Run Task``.
42+
43+
.. image:: screenshots/sample_task.png
44+
:alt: A sample task
45+
46+
The ``Jobs`` link will show the progress of the task.
47+
48+
The sample slide selection algorithm will create a collection called ``Selected Slides`` that defaults to having a folder with the date that the task was run containing the selected slides.

docs/screenshots/sample_task.png

39 KB
Loading
7.69 KB
Loading

setup.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,8 +44,9 @@ def prerelease_local_scheme(version):
4444
'easyocr',
4545
'girder>=3.2.8',
4646
'girder-homepage',
47+
'girder-worker[girder]',
4748
'histomicsui',
48-
'large-image[tiff,ometiff,openslide,memcached,converter]>=1.32.7',
49+
'large-image[tiff,ometiff,openslide,memcached,converter]>=1.32.10',
4950
'large-image-source-tiff[all]',
5051
'lxml',
5152
'openpyxl',

0 commit comments

Comments
 (0)