Skip to content

Unnecessary system dependency #135

@cbarkr

Description

@cbarkr

Hello!

I noticed that the libGL system dependency is not strictly necessary as OpenCV's GUI functionalities (e.g. cv2.imshow) are not utilized. opencv-python-headless is a drop-in replacement for opencv-python which excludes the GUI library dependencies, and therefore may be used to remove rt-utils' dependency on libGL.

To ensure that switching to opencv-python-headless doesn't break existing behaviour, I drafted basic sanity tests for some relevant functions in image_helper.py, which I ran in a Docker container explicitly built without libGL. Please see the end of this message for reference. While these tests pass, I welcome any suggestions for further testing of this module (specifically get_slice_mask_from_slice_contour_data).

Will make a PR with these changes soon in the hopes that they may help someone else.

Thanks!

📁 requirements.txt

# requirements.txt
- opencv-python>=4.0.0
+ opencv-python-headless>=4.0.0

📁 Dockerfile

# Dockerfile
FROM ubuntu

WORKDIR /app

# Explicitly *not* installing LibGL (libgl1 && libglib2.0-0)
RUN apt-get update && \
    apt-get install -y python3 python3-pip python3-venv && \
    rm -rf /var/lib/apt/lists/*

COPY . .

ENV PATH="/opt/venv/bin:$PATH"
RUN python3 -m venv /opt/venv && \
    pip3 install pytest && \
    pip3 install --no-cache-dir -r requirements.txt

CMD ["pytest"]

📁 tests/test_image_helper.py

# tests/test_image_helper.py
import pytest
import numpy as np

from rt_utils.image_helper import (
    load_sorted_image_series,
    create_empty_series_mask,
    find_mask_contours,
    draw_line_upwards_from_point,
)


def create_full_series_mask(series_data):
    ref_dicom_image = series_data[0]
    mask_dims = (
        int(ref_dicom_image.Columns),
        int(ref_dicom_image.Rows),
        len(series_data),
    )
    mask = np.ones(mask_dims).astype(bool)
    return mask


def test_find_mask_contours_with_empty_mask(series_path):
    series_data = load_sorted_image_series(series_path)
    empty_mask = create_empty_series_mask(series_data)[:, :, 0]

    # `hierarchy` is `None` in this case, thus `hierarchy = hierarchy[0]` fails
    with pytest.raises(TypeError):
        actual_contours, actual_hierarchy = find_mask_contours(empty_mask, True)


def test_find_mask_contours_with_full_mask(series_path):
    series_data = load_sorted_image_series(series_path)
    full_mask = create_full_series_mask(series_data)[:, :, 0]

    # Entire mask is a single contour
    expected_contours = [
        [
            [np.int32(0), np.int32(0)],
            [np.int32(0), np.int32(511)],
            [np.int32(511), np.int32(511)],
            [np.int32(511), np.int32(0)],
        ]
    ]
    expected_hierarchy = [[np.int32(-1), np.int32(-1), np.int32(-1), np.int32(-1)]]

    actual_contours, actual_hierarchy = find_mask_contours(full_mask, True)

    assert np.array_equal(expected_contours, actual_contours) == True
    assert np.array_equal(expected_hierarchy, actual_hierarchy) == True


def test_draw_line_upwards_from_point_with_empty_mask(series_path):
    series_data = load_sorted_image_series(series_path)
    empty_mask = create_empty_series_mask(series_data)[:, :, 0]
    start = (0, 0)

    # Should fill (0,0) and one point in each direction
    expected_binary_mask = empty_mask.copy()
    expected_binary_mask[0][0] = True
    expected_binary_mask[0][1] = True
    expected_binary_mask[1][0] = True

    actual_binary_mask = draw_line_upwards_from_point(empty_mask, start, 1)

    assert np.array_equal(expected_binary_mask, actual_binary_mask) == True


def test_draw_line_upwards_from_point_with_full_mask(series_path):
    series_data = load_sorted_image_series(series_path)
    full_mask = create_full_series_mask(series_data)[:, :, 0]
    start = (0, 0)

    # Should remain unchanged
    expected_binary_mask = full_mask.copy()

    actual_binary_mask = draw_line_upwards_from_point(full_mask, start, 1)

    assert np.array_equal(expected_binary_mask, actual_binary_mask) == True

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions