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

remove dependency on opencv-python #126

Merged

Conversation

zacharyburnett
Copy link
Collaborator

@zacharyburnett zacharyburnett commented Nov 21, 2022

Resolves AL-691

This PR removes unconditional dependency on opencv-python added in a previous PR; this was causing installation issues on some users' platforms.

I am also exploring replacing usage of opencv-python with scikit-image

Checklist

  • added entry in CHANGES.rst (either in Bug Fixes or Changes to API)
  • updated relevant tests
  • updated relevant documentation
  • updated relevant milestone(s)
  • added relevant label(s)

@codecov
Copy link

codecov bot commented Nov 21, 2022

Codecov Report

Base: 71.17% // Head: 71.17% // No change to project coverage 👍

Coverage data is based on head (8fd6162) compared to base (8fd6162).
Patch has no changes to coverable lines.

❗ Current head 8fd6162 differs from pull request most recent head 593e8b9. Consider uploading reports for the commit 593e8b9 to get more accurate results

Additional details and impacted files
@@           Coverage Diff           @@
##             main     #126   +/-   ##
=======================================
  Coverage   71.17%   71.17%           
=======================================
  Files          15       15           
  Lines        2626     2626           
=======================================
  Hits         1869     1869           
  Misses        757      757           

Help us with your feedback. Take ten seconds to tell us how you rate us. Have a feature suggestion? Share it here.

☔ View full report at Codecov.
📢 Do you have feedback about the report comment? Let us know in this issue.

@zacharyburnett
Copy link
Collaborator Author

zacharyburnett commented Nov 21, 2022

several functions and constants from opencv-python are currently used:

  • cv2.circle( img, center, radius, color[, thickness[, lineType[, shift]]] ) -> img
  • cv2.ellipse( img, center, axes, angle, startAngle, endAngle, color[, thickness[, lineType[, shift]]] ) -> img
  • cv2.findContours( image, mode, method[, contours[, hierarchy[, offset]]] ) -> contours, hierarchy
  • cv2.RETR_EXTERNAL
  • cv2.CHAIN_APPROX_SIMPLE
  • cv2.contourArea( contour[, oriented] ) -> retval
  • cv2.minEnclosingCircle( points ) -> center, radius
  • cv2.minAreaRect( points ) -> retval
    def extend_snowballs(plane, snowballs, sat_flag, jump_flag, expansion=1.5):
    # For a given DQ plane it will use the list of snowballs to create expanded circles of pixels with
    # the jump flag set.
    image = np.zeros(shape=(plane.shape[0], plane.shape[1], 3), dtype=np.uint8)
    num_circles = len(snowballs)
    sat_pix = np.bitwise_and(plane, 2)
    for snowball in snowballs:
    jump_radius = snowball[1]
    jump_center = snowball[0]
    cenx = jump_center[1]
    ceny = jump_center[0]
    extend_radius = round(jump_radius * expansion)
    image = cv.circle(image, (round(ceny), round(cenx)), extend_radius, (0, 0, 4), -1)
    jump_circle = image[:, :, 2]
    saty, satx = np.where(sat_pix == 2)
    jump_circle[saty, satx] = 0
    plane = np.bitwise_or(plane, jump_circle)
    return plane, num_circles

    def extend_ellipses(plane, ellipses, sat_flag, jump_flag, expansion=1.1):
    # For a given DQ plane it will use the list of ellipses to create expanded ellipses of pixels with
    # the jump flag set.
    image = np.zeros(shape=(plane.shape[0], plane.shape[1], 3), dtype=np.uint8)
    num_ellipses = len(ellipses)
    sat_pix = np.bitwise_and(plane, 2)
    for ellipse in ellipses:
    ceny = ellipse[0][0]
    cenx = ellipse[0][1]
    # Expand the ellipse by the expansion factor. The number of pixels added to both axes is
    # the number of pixels added to the minor axis. This prevents very large flagged ellipses
    # with high axis ratio ellipses. The major and minor axis are not always the same index.
    # Therefore, we have to test to find which is actually the minor axis.
    if ellipse[1][1] < ellipse[1][0]:
    axis1 = ellipse[1][0] + (expansion - 1.0) * ellipse[1][1]
    axis2 = ellipse[1][1] * expansion
    else:
    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)
    jump_ellipse = image[:, :, 2]
    saty, satx = np.where(sat_pix == 2)
    jump_ellipse[saty, satx] = 0
    plane = np.bitwise_or(plane, jump_ellipse)
    return plane, num_ellipses

    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]
    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]
    return ellipses

@zacharyburnett
Copy link
Collaborator Author

looking into replacing shape construction with scikit-image, it appears there is a crucial difference in the drawing method:

import cv2
import numpy
from matplotlib import pyplot
from skimage import draw

image_0 = numpy.full((10, 10), fill_value=0, dtype=numpy.uint8)

center = (4, 4)
radius = 4

disk = draw.disk(center, radius)
image_1 = image_0.copy()
image_1[disk] = 1

image_2 = cv2.circle(image_0.copy(), center, radius, 1, thickness=-1)

figure, axes = pyplot.subplots(nrows=1, ncols=2)

axes[0].imshow(image_1)
axes[0].set_title('scikit-image')

axes[1].imshow(image_2)
axes[1].set_title('opencv')

pyplot.show()

Figure_1

@zacharyburnett zacharyburnett marked this pull request as ready for review November 23, 2022 17:26
@zacharyburnett
Copy link
Collaborator Author

@hbushouse for the goal of removing dependence on opencv-python, do you think I should explore replacing with scikit-image, or is a conditional import for the affected functions (the current state of this PR) sufficient for this use case?

@hbushouse
Copy link
Collaborator

@hbushouse for the goal of removing dependence on opencv-python, do you think I should explore replacing with scikit-image, or is a conditional import for the affected functions (the current state of this PR) sufficient for this use case?

For now let's just stick with the conditional import contained in this PR. It appears the replacement with scikit-image will take more work to ensure the same results.

Copy link
Collaborator

@hbushouse hbushouse left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks OK to me, but there's a CI failure (code style) that needs fixing.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants