Skip to content

Commit

Permalink
add specific exception when ellipse construction attempted without `o…
Browse files Browse the repository at this point in the history
…pencv-python` installed (#136)

* add specific exception when ellipse construction attempted without `opencv-python` installed

* add installation instructions

* add change log entry

* formatting

* simplify naming

* xfail opencv tests

* add toxenv description

* fix installation instructions

* formatting
  • Loading branch information
zacharyburnett authored Dec 22, 2022
1 parent 9d95972 commit 7a9258d
Show file tree
Hide file tree
Showing 5 changed files with 46 additions and 25 deletions.
3 changes: 3 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,9 @@ jobs:
- toxenv: test-numpy120
os: ubuntu-latest
python-version: '3.8'
- toxenv: test-opencv-xdist
os: ubuntu-latest
python-version: '3.x'
- toxenv: test-jwst-xdist-cov
os: ubuntu-latest
python-version: '3.x'
Expand Down
2 changes: 1 addition & 1 deletion CHANGES.rst
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ General
Bug Fixes
---------

-
- improve exception handling when attempting to use ellipses without ``opencv-python`` installed [#136]

Changes to API
--------------
Expand Down
59 changes: 37 additions & 22 deletions src/stcal/jump/jump.py
Original file line number Diff line number Diff line change
@@ -1,21 +1,20 @@
import time
import logging
import warnings
import multiprocessing
import time

import numpy as np
from . import twopoint_difference as twopt
from . import constants

import multiprocessing
from . import constants
from . import twopoint_difference as twopt

ELLIPSE_PACKAGE = None
try:
import cv2 as cv

OPENCV_INSTALLED = True
except ImportError:
OPENCV_INSTALLED = False
warnings.warn('Could not import `opencv-python`; '
'certain snowball detection and usage of ellipses will be inoperable')
ELLIPSE_PACKAGE = 'opencv-python'
except (ImportError, ModuleNotFoundError):
ELLIPSE_PACKAGE_WARNING = '`opencv-python` must be installed (`pip install stcal[opencv]`) ' \
'in order to use ellipses'

log = logging.getLogger(__name__)
log.setLevel(logging.DEBUG)
Expand Down Expand Up @@ -237,7 +236,7 @@ def detect_jumps(frames_per_group, data, gdq, pdq, err,
# modified unless copied beforehand
gdq = gdq.copy()
data = data.copy()
copy_arrs = False # we dont need to copy arrays again in find_crs
copy_arrs = False # we dont need to copy arrays again in find_crs

for i in range(n_slices - 1):
slices.insert(i, (data[:, :, i * yinc:(i + 1) * yinc, :],
Expand Down Expand Up @@ -398,8 +397,13 @@ def extend_snowballs(plane, snowballs, sat_flag, jump_flag, expansion=1.5):
jump_center = snowball[0]
cenx = jump_center[1]
ceny = jump_center[0]
center = (round(ceny), round(cenx))
extend_radius = round(jump_radius * expansion)
image = cv.circle(image, (round(ceny), round(cenx)), extend_radius, (0, 0, 4), -1)
color = (0, 0, 4)
if ELLIPSE_PACKAGE == 'opencv-python':
image = cv.circle(image, center, extend_radius, color, -1)
else:
raise ModuleNotFoundError(ELLIPSE_PACKAGE_WARNING)
jump_circle = image[:, :, 2]
saty, satx = np.where(sat_pix == 2)
jump_circle[saty, satx] = 0
Expand Down Expand Up @@ -428,8 +432,13 @@ def extend_ellipses(plane, ellipses, sat_flag, jump_flag, expansion=1.1):
axis1 = ellipse[1][0] * expansion
axis2 = ellipse[1][1] + (expansion - 1.0) * ellipse[1][0]
alpha = ellipse[2]
image = cv.ellipse(image, (round(ceny), round(cenx)), (round(axis1 / 2),
round(axis2 / 2)), alpha, 0, 360, (0, 0, 4), -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)
else:
raise ModuleNotFoundError(ELLIPSE_PACKAGE_WARNING)
jump_ellipse = image[:, :, 2]
saty, satx = np.where(sat_pix == 2)
jump_ellipse[saty, satx] = 0
Expand All @@ -441,21 +450,27 @@ 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)
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]
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]
else:
raise ModuleNotFoundError(ELLIPSE_PACKAGE_WARNING)
return circles


def find_ellipses(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 ellipse parameters.
pixels = np.bitwise_and(dqplane, bitmask)
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]
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]
else:
raise ModuleNotFoundError(ELLIPSE_PACKAGE_WARNING)
return ellipses


Expand Down
4 changes: 2 additions & 2 deletions tests/test_jump.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ def _cube(ngroups, readnoise=10):
return _cube


@pytest.mark.skipif(not OPENCV_INSTALLED, reason="`opencv-python` not installed")
@pytest.mark.xfail(not OPENCV_INSTALLED, reason="`opencv-python` not installed")
def test_find_simple_circle():
plane = np.zeros(shape=(5, 5), dtype=np.uint8)
plane[2, 2] = DQFLAGS['JUMP_DET']
Expand All @@ -43,7 +43,7 @@ def test_find_simple_circle():
assert circle[0][1] == pytest.approx(1.0, 1e-3)


@pytest.mark.skipif(not OPENCV_INSTALLED, reason="`opencv-python` not installed")
@pytest.mark.xfail(not OPENCV_INSTALLED, reason="`opencv-python` not installed")
def test_find_simple_ellipse():
plane = np.zeros(shape=(5, 5), dtype=np.uint8)
plane[2, 2] = DQFLAGS['JUMP_DET']
Expand Down
3 changes: 3 additions & 0 deletions tox.ini
Original file line number Diff line number Diff line change
Expand Up @@ -46,11 +46,13 @@ description =
run tests
jwst: of JWST pipeline
romancal: of Romancal pipeline
opencv: requiring opencv-python
warnings: treating warnings as errors
cov: with coverage
xdist: using parallel processing
extras =
test
opencv: opencv
deps =
xdist: pytest-xdist
jwst: jwst[test] @ git+https://github.com/spacetelescope/jwst.git
Expand All @@ -73,6 +75,7 @@ commands =
jwst: --pyargs jwst --ignore-glob=timeconversion --ignore-glob=associations \
romancal: --pyargs romancal \
cov: --cov=. --cov-config=pyproject.toml --cov-report=term-missing --cov-report=xml \
opencv: -- tests/test_jump.py \
{posargs}

[testenv:build-docs]
Expand Down

0 comments on commit 7a9258d

Please sign in to comment.