From 9b2cba5a80fa2a7ba930d570c7d2acd800f9b050 Mon Sep 17 00:00:00 2001 From: zacharyburnett Date: Wed, 1 May 2024 10:29:35 -0400 Subject: [PATCH] remove usage of `opencv-python` entirely --- .github/workflows/ci.yml | 6 --- pyproject.toml | 9 +--- src/stcal/jump/jump.py | 96 ++++++++++++---------------------------- tox.ini | 5 --- 4 files changed, 30 insertions(+), 86 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 5a61e5245..d79d01908 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -73,12 +73,6 @@ jobs: - toxenv: test-numpy120 os: ubuntu-latest python-version: '3.8' - - toxenv: test-opencv-xdist - os: ubuntu-latest - python-version: '3.10' - - toxenv: test-scikitimage-xdist - os: ubuntu-latest - python-version: '3.10' - toxenv: test-jwst-xdist-cov os: ubuntu-latest python-version: '3.x' diff --git a/pyproject.toml b/pyproject.toml index 27ffc289d..67b1066b2 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -13,7 +13,9 @@ classifiers = [ ] dependencies = [ 'astropy >=5.0.4', + 'scikit-image', 'scipy >=1.6.0', + 'shapely', 'numpy >=1.17', ] dynamic = ['version'] @@ -35,13 +37,6 @@ test = [ 'pytest-doctestplus', 'pytest-openfiles >=0.5.0', ] -ellipse = [ - 'scikit-image', - 'shapely', -] -opencv = [ - 'opencv-python>=4.6.0.66', -] [project.urls] 'repository' = 'https://github.com/spacetelescope/stcal' diff --git a/src/stcal/jump/jump.py b/src/stcal/jump/jump.py index 3dad7b51f..0eae6e542 100644 --- a/src/stcal/jump/jump.py +++ b/src/stcal/jump/jump.py @@ -9,25 +9,11 @@ from . import constants from . import twopoint_difference as twopt -ELLIPSE_PACKAGE = None -try: - import cv2 as cv - - ELLIPSE_PACKAGE = 'opencv-python' -except (ImportError, ModuleNotFoundError): - try: - import shapely.geometry - import skimage.draw - import skimage.measure - - from .circle import Circle - - ELLIPSE_PACKAGE = 'scikit-image' - ELLIPSE_PACKAGE_WARNING = '`opencv-python` not installed; ' \ - 'using `scikit-image` + `shapely` for ellipse construction' - except (ImportError, ModuleNotFoundError): - ELLIPSE_PACKAGE_WARNING = 'an image processing package (either `opencv-python` or `scikit-image` + `shapely`)' \ - 'must be installed in order to use ellipses' +import shapely.geometry +import skimage.draw +import skimage.measure + +from .circle import Circle log = logging.getLogger(__name__) log.setLevel(logging.DEBUG) @@ -413,14 +399,9 @@ def extend_snowballs(plane, snowballs, sat_flag, jump_flag, expansion=1.5): center = (round(ceny), round(cenx)) extend_radius = round(jump_radius * expansion) color = (0, 0, 4) - if ELLIPSE_PACKAGE == 'opencv-python': - image = cv.circle(image, center, extend_radius, color, -1) - elif ELLIPSE_PACKAGE == 'scikit-image': - warnings.warn(ELLIPSE_PACKAGE_WARNING) - disk = skimage.draw.disk(center, extend_radius) - image[disk] = color - else: - raise ModuleNotFoundError(ELLIPSE_PACKAGE_WARNING) + warnings.warn(ELLIPSE_PACKAGE_WARNING) + disk = skimage.draw.disk(center, extend_radius) + image[disk] = color jump_circle = image[:, :, 2] saty, satx = np.where(sat_pix == 2) jump_circle[saty, satx] = 0 @@ -452,14 +433,9 @@ def extend_ellipses(plane, ellipses, sat_flag, jump_flag, expansion=1.1): center = (round(ceny), round(cenx)) axes = (round(axis1 / 2), round(axis2 / 2)) color = (0, 0, 4) - if ELLIPSE_PACKAGE == 'opencv-python': - image = cv.ellipse(image, center, axes, alpha, 0, 360, color, -1) - elif ELLIPSE_PACKAGE == 'scikit-image': - warnings.warn(ELLIPSE_PACKAGE_WARNING) - ellipse = skimage.draw.ellipse(*center, *axes, rotation=alpha) - image[ellipse] = color - else: - raise ModuleNotFoundError(ELLIPSE_PACKAGE_WARNING) + warnings.warn(ELLIPSE_PACKAGE_WARNING) + ellipse = skimage.draw.ellipse(*center, *axes, rotation=alpha) + image[ellipse] = color jump_ellipse = image[:, :, 2] saty, satx = np.where(sat_pix == 2) jump_ellipse[saty, satx] = 0 @@ -471,16 +447,9 @@ def find_circles(dqplane, bitmask, min_area): # Using an input DQ plane this routine will find the groups of pixels with at least the minimum # area and return a list of the minimum enclosing circle parameters. pixels = np.bitwise_and(dqplane, bitmask) - if ELLIPSE_PACKAGE == 'opencv-python': - contours, hierarchy = cv.findContours(pixels, cv.RETR_EXTERNAL, cv.CHAIN_APPROX_SIMPLE) - bigcontours = [con for con in contours if cv.contourArea(con) >= min_area] - circles = [cv.minEnclosingCircle(con) for con in bigcontours] - elif ELLIPSE_PACKAGE == 'scikit-image': - contours = [shapely.geometry.Polygon(con) for con in skimage.measure.find_contours(pixels)] - bigcontours = [con for con in contours if con.area > min_area] - circles = [Circle.from_points(numpy.stack(con.exterior.xy, axis=1)) for con in bigcontours] - else: - raise ModuleNotFoundError(ELLIPSE_PACKAGE_WARNING) + contours = [shapely.geometry.Polygon(con) for con in skimage.measure.find_contours(pixels)] + bigcontours = [con for con in contours if con.area > min_area] + circles = [Circle.from_points(numpy.stack(con.exterior.xy, axis=1)) for con in bigcontours] return circles @@ -489,29 +458,20 @@ def find_ellipses(dqplane, bitmask, min_area): # area and return a list of the minimum enclosing ellipse parameters. pixels = np.bitwise_and(dqplane, bitmask) - if ELLIPSE_PACKAGE == 'opencv-python': - contours, hierarchy = cv.findContours(pixels, cv.RETR_EXTERNAL, cv.CHAIN_APPROX_SIMPLE) - bigcontours = [con for con in contours if cv.contourArea(con) > min_area] - # minAreaRect is used becuase fitEllipse requires 5 points and it is possible to have a contour - # with just 4 points. - ellipses = [cv.minAreaRect(con) for con in bigcontours] - elif ELLIPSE_PACKAGE == 'scikit-image': - contours = [shapely.geometry.Polygon(con) for con in skimage.measure.find_contours(pixels)] - bigcontours = [con for con in contours if con.area > min_area] - rectangles = [ - numpy.flip(numpy.stack(con.minimum_rotated_rectangle.exterior.xy, axis=1), axis=1) - for con in bigcontours - ] - ellipses = [ - ( - numpy.mean(rectangle[[0, 2], :], axis=0), - numpy.hypot(*numpy.diff(rectangle[[0, 1, 2], :], axis=0)), - numpy.degrees(numpy.arctan2(*numpy.flip(numpy.diff(rectangle[[3, 0], :], axis=0)[0]))) - ) - for rectangle in rectangles - ] - else: - raise ModuleNotFoundError(ELLIPSE_PACKAGE_WARNING) + contours = [shapely.geometry.Polygon(con) for con in skimage.measure.find_contours(pixels)] + bigcontours = [con for con in contours if con.area > min_area] + rectangles = [ + numpy.flip(numpy.stack(con.minimum_rotated_rectangle.exterior.xy, axis=1), axis=1) + for con in bigcontours + ] + ellipses = [ + ( + numpy.mean(rectangle[[0, 2], :], axis=0), + numpy.hypot(*numpy.diff(rectangle[[0, 1, 2], :], axis=0)), + numpy.degrees(numpy.arctan2(*numpy.flip(numpy.diff(rectangle[[3, 0], :], axis=0)[0]))) + ) + for rectangle in rectangles + ] return ellipses diff --git a/tox.ini b/tox.ini index 18828b0d3..6b770ccf0 100644 --- a/tox.ini +++ b/tox.ini @@ -46,15 +46,11 @@ description = run tests jwst: of JWST pipeline romancal: of Romancal pipeline - opencv: requiring opencv-python - scikitimage: requiring scikit-image warnings: treating warnings as errors cov: with coverage xdist: using parallel processing extras = test - scikitimage: ellipse - opencv: opencv deps = xdist: pytest-xdist jwst: jwst[test] @ git+https://github.com/spacetelescope/jwst.git @@ -79,7 +75,6 @@ commands = jwst: --pyargs jwst --ignore-glob=timeconversion --ignore-glob=associations --ignore-glob=scripts \ romancal: --pyargs romancal \ cov: --cov=. --cov-config=pyproject.toml --cov-report=term-missing --cov-report=xml \ - opencv,scikitimage: -- tests/test_jump.py \ {posargs} [testenv:build-docs]