Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix the DICOMweb uploader and its tests #381

Merged
merged 58 commits into from
May 1, 2024
Merged
Show file tree
Hide file tree
Changes from 15 commits
Commits
Show all changes
58 commits
Select commit Hold shift + click to select a range
4d01044
Rework dicomweb
stefpiatek Apr 26, 2024
ce6d3cf
Update dicomweb tests after rework
milanmlft Apr 26, 2024
d224ea5
Docker compose `version` is obsolete
milanmlft Apr 29, 2024
19ec2c1
Fix dicomweb-server healthcheck; use correct credentials
milanmlft Apr 29, 2024
bdbb1df
Fix the DicomWebUploader tests
milanmlft Apr 29, 2024
2a91054
Simplify services setup for core tests
milanmlft Apr 29, 2024
70182af
Fix volume path
milanmlft Apr 29, 2024
2f6ff49
Clean up orphan containers at startup
milanmlft Apr 29, 2024
262ffee
Fix typo
milanmlft Apr 29, 2024
be15576
Add negative checks in case of wrong DICOMweb credentials
milanmlft Apr 29, 2024
0e1aa3a
Remove obsolete function
milanmlft Apr 29, 2024
a0c7f50
Fix orthanc credentials
milanmlft Apr 29, 2024
baf54e9
Use different credentials for DICOMweb test server and fix volume paths
milanmlft Apr 29, 2024
53fb47f
Fix system test: actually check for presence of studies on DICOMweb
milanmlft Apr 29, 2024
9514521
Better log message
milanmlft Apr 29, 2024
ebfbe1b
Fix module docstring
milanmlft Apr 29, 2024
3215d3a
Add clarifying comment
milanmlft Apr 29, 2024
0a05664
Use lowercase for local variable
milanmlft Apr 29, 2024
59e36b8
Fix outdated comment
milanmlft Apr 30, 2024
b840885
Docker compose 'version is obsolete'
milanmlft Apr 30, 2024
131ba35
Formatting: 4-space indents
milanmlft Apr 30, 2024
53c5686
Use content type json for stow request
milanmlft Apr 30, 2024
deaedd7
Update comment about the `orthanc_dicomweb_url`
milanmlft Apr 30, 2024
ce2182a
Fix typos
milanmlft Apr 30, 2024
f161d16
Add `raise_for_status()` calls in tests
milanmlft Apr 30, 2024
bde47de
Another `raise_for_status()`
milanmlft Apr 30, 2024
36005d6
Query for study ID directly
milanmlft Apr 30, 2024
0aae10a
Set http timeout as class field for `DicomWebUploader`
milanmlft Apr 30, 2024
d820549
Hardcode DICOMweb config values in tests instead of using envvars
milanmlft Apr 30, 2024
365173d
Set `http_timeout` for mock Dicomweb uploader
milanmlft Apr 30, 2024
94d34d4
Validate DICOMweb server before attempting upload
milanmlft Apr 30, 2024
918ba87
Update comment
milanmlft Apr 30, 2024
76c6a17
Add another `response.raise_status()`
milanmlft Apr 30, 2024
bbcc133
Refactor: move checks for already exported images to base uploader
milanmlft Apr 30, 2024
afbb78f
Add test for DICOMweb uploader to check failure if image already expo…
milanmlft Apr 30, 2024
db9233d
Implement checking for image already uploaded in dicomweb uploader
milanmlft Apr 30, 2024
82596f5
Merge branch 'main' into milanmlft/fix-dicomweb-uploader
milanmlft Apr 30, 2024
f886059
Move Orthanc helpers to `core.uploader`
milanmlft Apr 30, 2024
9ba5775
Make `Uploader.upload_dicom_image` methods work with Orthanc study ID…
milanmlft Apr 30, 2024
5ede1a6
Move FTPS-specific fixtures to its own test file
milanmlft Apr 30, 2024
abf25ff
Fix orthanc-anon related envvar references
milanmlft Apr 30, 2024
c177902
`ORTHANC_URL` -> `ORTHANC_ANON_URL`, which is more accurate
milanmlft Apr 30, 2024
d469acf
Fix `TEST_DIR` path
milanmlft Apr 30, 2024
1ece0ee
Fix return type for fixture
milanmlft Apr 30, 2024
6a401d8
Refactor `FTPSUploader.upload_dicom_image()`; extract FTPS specific f…
milanmlft Apr 30, 2024
34cc6ef
Fix return types for fixtures
milanmlft Apr 30, 2024
45c056a
Rename fixture so it doesn't get detected as a test
milanmlft Apr 30, 2024
0b27ae6
Move 'already exported' check to `send_via_ftps()`
milanmlft Apr 30, 2024
16f57bc
Refactor: let `get_uploader()` work with just the project slug
milanmlft Apr 30, 2024
b500442
Add missing envvars for tests
milanmlft Apr 30, 2024
a10fa3d
Fix import
milanmlft Apr 30, 2024
f438a1a
Fix docker compose, messed up during merge
milanmlft Apr 30, 2024
00ed2da
Implement `DicomWebUploader.upload_dicom_image()`
milanmlft Apr 30, 2024
4d6eeda
Set `ORTHANC_ANON_*` envvars in the `orthanc-anon` container
milanmlft Apr 30, 2024
5cf8d39
Avoid loading envvars when they're not needed
milanmlft Apr 30, 2024
a8d8d17
And another missing envvar
milanmlft Apr 30, 2024
5ba4f59
Why did I even do this
milanmlft Apr 30, 2024
2641ae2
Update pixl_core/src/core/uploader/_dicomweb.py
stefpiatek May 1, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 19 additions & 16 deletions pixl_core/src/core/uploader/_dicomweb.py
milanmlft marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
Expand Up @@ -36,13 +36,15 @@ def __init__(self, project_slug: str, keyvault_alias: Optional[str]) -> None:
def _set_config(self) -> None:
# Use the Azure KV alias as prefix if it exists, otherwise use the project name
az_prefix = self.keyvault_alias
az_prefix = az_prefix if az_prefix else self.project_slug
self.az_prefix = az_prefix if az_prefix else self.project_slug

self.user = self.keyvault.fetch_secret(f"{az_prefix}--dicomweb--username")
self.password = self.keyvault.fetch_secret(f"{az_prefix}--dicomweb--password")
self.orthanc_user = config("ORTHANC_USERNAME")
self.orthanc_password = config("ORTHANC_PASSWORD")
self.orthanc_url = config("ORTHANC_URL")
milanmlft marked this conversation as resolved.
Show resolved Hide resolved
self.endpoint_name = self.keyvault.fetch_secret(f"{az_prefix}--dicomweb--url")
self.url = self.orthanc_url + "/dicom-web/servers/" + self.endpoint_name
self.endpoint_user = self.keyvault.fetch_secret(f"{self.az_prefix}--dicomweb--username")
self.endpoint_password = self.keyvault.fetch_secret(f"{self.az_prefix}--dicomweb--password")
self.endpoint_url = self.keyvault.fetch_secret(f"{self.az_prefix}--dicomweb--url")
self.orthanc_dicomweb_url = self.orthanc_url + "/dicom-web/servers/" + self.az_prefix
milanmlft marked this conversation as resolved.
Show resolved Hide resolved

def upload_dicom_image(self) -> None:
msg = "Currently not implemented. Use `send_via_stow()` instead."
Expand All @@ -59,8 +61,8 @@ def send_via_stow(self, resource_id: str) -> requests.Response:

try:
response = requests.post(
self.url + "/stow",
auth=(self.user, self.password),
self.orthanc_dicomweb_url + "/stow",
auth=(self.orthanc_user, self.orthanc_password),
headers=headers,
data=json.dumps(payload),
milanmlft marked this conversation as resolved.
Show resolved Hide resolved
timeout=30,
Expand All @@ -75,7 +77,9 @@ def send_via_stow(self, resource_id: str) -> requests.Response:

def _check_dicomweb_server(self) -> bool:
"""Checks if the dicomweb server exists."""
response = requests.get(self.url, auth=(self.user, self.password), timeout=30)
response = requests.get(
self.orthanc_dicomweb_url, auth=(self.orthanc_user, self.orthanc_password), timeout=30
milanmlft marked this conversation as resolved.
Show resolved Hide resolved
)
success_code = 200
if response.status_code != success_code:
return False
Expand All @@ -87,31 +91,30 @@ def _setup_dicomweb_credentials(self) -> None:
This dyniamically creates a new endpoint in Orthanc with the necessary credentials, so we
milanmlft marked this conversation as resolved.
Show resolved Hide resolved
can avoid hardcoding the credentials in the Orthanc configuration at build time.
"""
DICOM_ENDPOINT_URL = config("DICOM_ENDPOINT_URL")
HTTP_TIMEOUT = int(config("HTTP_TIMEOUT", default=30))
milanmlft marked this conversation as resolved.
Show resolved Hide resolved

dicomweb_config = {
"Url": DICOM_ENDPOINT_URL,
"Username": self.user,
"Password": self.password,
"Url": self.endpoint_url,
"Username": self.endpoint_user,
"Password": self.endpoint_password,
"HasDelete": True,
"Timeout": HTTP_TIMEOUT,
milanmlft marked this conversation as resolved.
Show resolved Hide resolved
}

headers = {"content-type": "application/json"}
try:
requests.put(
milanmlft marked this conversation as resolved.
Show resolved Hide resolved
self.url,
auth=(self.user, self.password),
self.orthanc_dicomweb_url,
auth=(self.orthanc_user, self.orthanc_password),
headers=headers,
data=json.dumps(dicomweb_config),
timeout=10,
)
milanmlft marked this conversation as resolved.
Show resolved Hide resolved
except requests.exceptions.RequestException:
logger.error("Failed to update DICOMweb token")
logger.error("Failed to update DICOMweb config for {}", self.orthanc_dicomweb_url)
raise
else:
logger.info("DICOMweb token updated")
logger.info("Set up DICOMweb config for {}", self.orthanc_dicomweb_url)

def upload_parquet_files(self) -> None:
msg = "DICOMWeb uploader does not support parquet files"
Expand Down
32 changes: 5 additions & 27 deletions pixl_core/tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,9 +53,10 @@
os.environ["ORTHANC_URL"] = "http://localhost:8043"
os.environ["ORTHANC_USERNAME"] = "orthanc"
os.environ["ORTHANC_PASSWORD"] = "orthanc" # noqa: S105, hardcoded password
os.environ["DICOM_ENDPOINT_NAME"] = "test"
# Endpoint for DICOMWeb server as seen from within Orthanc
os.environ["DICOM_ENDPOINT_URL"] = "http://localhost:8042/dicom-web/"
os.environ["DICOMWEB_URL"] = "http://dicomweb-server:8042/dicom-web"
milanmlft marked this conversation as resolved.
Show resolved Hide resolved
os.environ["DICOMWEB_USERNAME"] = "orthanc_dicomweb"
os.environ["DICOMWEB_PASSWORD"] = "orthanc_dicomweb" # noqa: S105, hardcoded password


@pytest.fixture(scope="package")
Expand All @@ -67,7 +68,7 @@ def run_containers() -> Generator[subprocess.CompletedProcess[bytes], None, None
timeout=60,
)
yield run_subprocess(
shlex.split("docker compose up --build --wait"),
shlex.split("docker compose up --build --wait --remove-orphans"),
TEST_DIR,
timeout=60,
)
Expand All @@ -79,30 +80,7 @@ def run_containers() -> Generator[subprocess.CompletedProcess[bytes], None, None


@pytest.fixture(scope="package")
def run_dicomweb_containers() -> Generator[subprocess.CompletedProcess[bytes], None, None]:
"""
Spins up 2 Orthanc containers, one that acts as the base storage, mimicking our orthanc-anon
or orthanc-raw servers, and the other one as a DICOMweb server to upload DICOM files to.
"""
run_subprocess(
shlex.split("docker compose down --volumes"),
TEST_DIR,
timeout=60,
)
yield run_subprocess(
shlex.split("docker compose -f docker-compose.dicomweb.yml up --build --wait"),
TEST_DIR,
timeout=60,
)
run_subprocess(
shlex.split("docker compose down --volumes"),
TEST_DIR,
timeout=60,
)


@pytest.fixture(scope="package")
def study_id(run_dicomweb_containers) -> str:
def study_id(run_containers) -> str:
"""Uploads a DICOM file to the Orthanc server and returns the study ID."""
DCM_FILE = Path(__file__).parents[2] / "test" / "resources" / "Dicom1.dcm"
ORTHANC_URL = os.environ["ORTHANC_URL"]
Expand Down
69 changes: 0 additions & 69 deletions pixl_core/tests/docker-compose.dicomweb.yml

This file was deleted.

85 changes: 69 additions & 16 deletions pixl_core/tests/docker-compose.yml
milanmlft marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
Expand Up @@ -11,22 +11,75 @@
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
version: "3.8"

networks:
pixl-test:

services:
queue:
container_name: pixl-test-queue
image: rabbitmq:3.12.9-management
environment:
RABBITMQ_DEFAULT_USER: guest
RABBITMQ_DEFAULT_PASS: guest
ports:
- "25672:5672"
- "35672:15672"
healthcheck:
test: rabbitmq-diagnostics -q check_running
interval: 10s
timeout: 5s
retries: 5

orthanc:
image: orthancteam/orthanc:24.4.0
platform: linux/amd64
environment:
ORTHANC_NAME: "orthanc"
ORTHANC_USERNAME: "orthanc"
ORTHANC_PASSWORD: "orthanc"
ORTHANC_AE_TITLE: "orthanc"
RAW_AE_TITLE: ORHTANCRAW
milanmlft marked this conversation as resolved.
Show resolved Hide resolved
RAW_DICOM_PORT: "4242"
RAW_IP_ADDR: "orthanc-raw" # aka. hostname
DICOM_WEB_PLUGIN_ENABLED: true
ports:
- "4243:4242"
- "8043:8042"
networks:
- pixl-test
healthcheck:
test: ["CMD-SHELL", "/probes/test-aliveness.py --user=orthanc --pwd=orthanc"]
start_period: 10s
retries: 2
interval: 3s
timeout: 2s

queue:
container_name: pixl-test-queue
image: rabbitmq:3.12.9-management
environment:
RABBITMQ_DEFAULT_USER: guest
RABBITMQ_DEFAULT_PASS: guest
ports:
- "25672:5672"
- "35672:15672"
healthcheck:
test: rabbitmq-diagnostics -q check_running
interval: 10s
timeout: 5s
retries: 5
dicomweb-server:
image: orthancteam/orthanc:24.4.0
platform: linux/amd64
environment:
ORTHANC_NAME: "dicomweb"
ORTHANC_USERNAME: "orthanc_dicomweb"
ORTHANC_PASSWORD: "orthanc_dicomweb"
ORTHANC_AE_TITLE: "DICOMWEB"
RAW_AE_TITLE: ORHTANCRAW
RAW_DICOM_PORT: "4242"
RAW_IP_ADDR: "dicom-web" # aka. hostname
DICOM_WEB_PLUGIN_ENABLED: true
ports:
- "4244:4242"
- "8044:8042"
volumes:
- ../../test/dicomweb_config/:/run/secrets:ro
networks:
- pixl-test
healthcheck:
test:
[
"CMD-SHELL",
"/probes/test-aliveness.py --user=orthanc_dicomweb --pwd=orthanc_dicomweb",
]
start_period: 10s
retries: 2
interval: 3s
timeout: 2s
Loading
Loading