|
1 | 1 | """Tests to verify the service functionality of the application module.""" |
2 | 2 |
|
| 3 | +from collections.abc import Callable |
3 | 4 | from datetime import UTC, datetime, timedelta |
| 5 | +from pathlib import Path |
4 | 6 | from unittest.mock import MagicMock, patch |
5 | 7 |
|
| 8 | +import pydicom |
6 | 9 | import pytest |
7 | 10 | from typer.testing import CliRunner |
8 | 11 |
|
@@ -439,3 +442,112 @@ def test_application_run_update_item_custom_metadata_not_found(mock_get_client: |
439 | 442 |
|
440 | 443 | with pytest.raises(NotFoundException, match="not found"): |
441 | 444 | service.application_run_update_item_custom_metadata("run-123", "invalid-item-id", {"key": "value"}) |
| 445 | + |
| 446 | + |
| 447 | +@pytest.fixture |
| 448 | +def create_dicom() -> Callable[..., pydicom.Dataset]: |
| 449 | + """Fixture that returns a function to create minimal but valid DICOM datasets.""" |
| 450 | + |
| 451 | + def _create_dicom(series_uid: str, rows: int, cols: int) -> pydicom.Dataset: |
| 452 | + """Create a minimal but valid DICOM dataset. |
| 453 | +
|
| 454 | + Args: |
| 455 | + series_uid: The series instance UID |
| 456 | + rows: Number of rows (height) |
| 457 | + cols: Number of columns (width) |
| 458 | +
|
| 459 | + Returns: |
| 460 | + A valid pydicom Dataset |
| 461 | + """ |
| 462 | + ds = pydicom.Dataset() |
| 463 | + |
| 464 | + # File Meta Information |
| 465 | + ds.file_meta = pydicom.Dataset() |
| 466 | + ds.file_meta.TransferSyntaxUID = pydicom.uid.ImplicitVRLittleEndian |
| 467 | + ds.file_meta.MediaStorageSOPClassUID = "1.2.840.10008.5.1.4.1.1.77.1.6" # VL Whole Slide Microscopy |
| 468 | + ds.file_meta.MediaStorageSOPInstanceUID = pydicom.uid.generate_uid() |
| 469 | + |
| 470 | + # Required DICOM attributes |
| 471 | + ds.SeriesInstanceUID = series_uid |
| 472 | + ds.SOPInstanceUID = pydicom.uid.generate_uid() |
| 473 | + ds.SOPClassUID = ds.file_meta.MediaStorageSOPClassUID |
| 474 | + ds.StudyInstanceUID = pydicom.uid.generate_uid() |
| 475 | + ds.Modality = "SM" |
| 476 | + ds.Rows = rows |
| 477 | + ds.Columns = cols |
| 478 | + |
| 479 | + return ds |
| 480 | + |
| 481 | + return _create_dicom |
| 482 | + |
| 483 | + |
| 484 | +@pytest.mark.unit |
| 485 | +def test_filter_dicom_series_files_single_file( |
| 486 | + tmp_path: Path, |
| 487 | + create_dicom: Callable[..., pydicom.Dataset], |
| 488 | +) -> None: |
| 489 | + """Test that single DICOM files are not filtered.""" |
| 490 | + ds = create_dicom("1.2.3.4.5", 1024, 1024) |
| 491 | + dcm_file = tmp_path / "test.dcm" |
| 492 | + ds.save_as(dcm_file, write_like_original=False) |
| 493 | + |
| 494 | + excluded = ApplicationService._filter_dicom_series_files(tmp_path) |
| 495 | + assert len(excluded) == 0 |
| 496 | + |
| 497 | + |
| 498 | +@pytest.mark.unit |
| 499 | +def test_filter_dicom_series_files_pyramid(tmp_path: Path, create_dicom: Callable[..., pydicom.Dataset]) -> None: |
| 500 | + """Test that for multi-file DICOM series, only the highest resolution file is kept.""" |
| 501 | + series_uid = "1.2.3.4.5" |
| 502 | + |
| 503 | + # Create low resolution DICOM file |
| 504 | + ds_low = create_dicom(series_uid, 512, 512) |
| 505 | + dcm_file_low = tmp_path / "test_low.dcm" |
| 506 | + ds_low.save_as(dcm_file_low, write_like_original=False) |
| 507 | + |
| 508 | + # Create medium resolution DICOM file |
| 509 | + ds_med = create_dicom(series_uid, 1024, 1024) |
| 510 | + dcm_file_med = tmp_path / "test_med.dcm" |
| 511 | + ds_med.save_as(dcm_file_med, write_like_original=False) |
| 512 | + |
| 513 | + # Create high resolution DICOM file |
| 514 | + ds_high = create_dicom(series_uid, 2048, 2048) |
| 515 | + dcm_file_high = tmp_path / "test_high.dcm" |
| 516 | + ds_high.save_as(dcm_file_high, write_like_original=False) |
| 517 | + |
| 518 | + # Filter the series |
| 519 | + excluded = ApplicationService._filter_dicom_series_files(tmp_path) |
| 520 | + |
| 521 | + # Should exclude 2 files (low and medium), keeping only the highest resolution |
| 522 | + assert len(excluded) == 2 |
| 523 | + assert dcm_file_low in excluded |
| 524 | + assert dcm_file_med in excluded |
| 525 | + assert dcm_file_high not in excluded |
| 526 | + |
| 527 | + |
| 528 | +@pytest.mark.unit |
| 529 | +def test_filter_dicom_series_files_multiple_series( |
| 530 | + tmp_path: Path, create_dicom: Callable[..., pydicom.Dataset] |
| 531 | +) -> None: |
| 532 | + """Test that files from different series are not filtered against each other.""" |
| 533 | + # Series 1 - two files |
| 534 | + ds1_low = create_dicom("1.2.3.4.5", 512, 512) |
| 535 | + dcm_file1_low = tmp_path / "series1_low.dcm" |
| 536 | + ds1_low.save_as(dcm_file1_low, write_like_original=False) |
| 537 | + |
| 538 | + ds1_high = create_dicom("1.2.3.4.5", 1024, 1024) |
| 539 | + dcm_file1_high = tmp_path / "series1_high.dcm" |
| 540 | + ds1_high.save_as(dcm_file1_high, write_like_original=False) |
| 541 | + |
| 542 | + # Series 2 - single file (should not be filtered) |
| 543 | + ds2 = create_dicom("6.7.8.9.0", 512, 512) |
| 544 | + dcm_file2 = tmp_path / "series2.dcm" |
| 545 | + ds2.save_as(dcm_file2, write_like_original=False) |
| 546 | + |
| 547 | + excluded = ApplicationService._filter_dicom_series_files(tmp_path) |
| 548 | + |
| 549 | + # Should exclude only the low-res file from series 1 |
| 550 | + assert len(excluded) == 1 |
| 551 | + assert dcm_file1_low in excluded |
| 552 | + assert dcm_file1_high not in excluded |
| 553 | + assert dcm_file2 not in excluded |
0 commit comments