def findBestMatch(self, needle, similarity):
        """ Finds the best match using a search pyramid to improve efficiency

        Pyramid implementation unashamedly stolen from https://github.com/stb-tester/stb-tester

        *Developer's Note - Despite the name, this method actually returns the **first** result
        with enough similarity, not the **best** result.*
        """
        needle = cv2.cvtColor(needle, cv2.COLOR_BGR2GRAY) # Convert to grayscale
        haystack = self.haystack
        # Check if haystack or needle are a solid color - if so, switch to SQDIFF_NORMED

        if self._is_solid_color(needle):
            print("Solid color, using SQDIFF")
            method = cv2.TM_SQDIFF_NORMED
            if self._is_solid_black(needle):
                print("Inverting images")
                # Invert needle & haystack before matching
                needle = numpy.invert(needle)
                haystack = numpy.invert(haystack)
        else:
            #print("Not Solid color, using CCOEFF")
            method = cv2.TM_CCOEFF_NORMED

        levels = 3
        needle_pyramid = self._build_pyramid(needle, levels)
        # Needle will be smaller than haystack, so may not be able to create
        # ``levels`` smaller versions of itself. If not, create only as many
        # levels for ``haystack`` as we could for ``needle``.
        haystack_pyramid = self._build_pyramid(haystack, min(levels, len(needle_pyramid)))
        roi_mask = None

        # Run through each level in the pyramid, refining found ROIs
        for level in range(len(haystack_pyramid)):
            # Populate the heatmap with ones or zeroes depending on the appropriate method
            lvl_haystack = haystack_pyramid[level]
            lvl_needle = needle_pyramid[level]
            if (lvl_needle.shape[0] > lvl_haystack.shape[0]) or (lvl_needle.shape[1] > lvl_haystack.shape[1]):
                raise ValueError("Image to find is larger than search area")
            matches_heatmap = (
                (numpy.ones if method == cv2.TM_SQDIFF_NORMED else numpy.zeros)(
                    (lvl_haystack.shape[0] - lvl_needle.shape[0] + 1, lvl_haystack.shape[1] - lvl_needle.shape[1] + 1),
                    dtype=numpy.float32))

            # Scale up region of interest for the next level in the pyramid
            # (if it's been set and is a valid size)
            if roi_mask is not None:
                if any(x < 3 for x in roi_mask.shape):
                    roi_mask = None
                else:
                    roi_mask = cv2.pyrUp(roi_mask)

            # If roi_mask is set, only search the best candidates in haystack
            # for the needle:

            if roi_mask is None:
                # Initialize mask to the whole image
                rois = [(0, 0, matches_heatmap.shape[1], matches_heatmap.shape[0])]
            else:
                # Depending on version of OpenCV, findContours returns either a three-tuple
                # or a two-tuple. Unsure why the install is different (possibly linked to
                # OS version).
                try:
                    _, contours, _ = cv2.findContours(
                        roi_mask,
                        cv2.RETR_EXTERNAL,
                        cv2.CHAIN_APPROX_NONE)
                except ValueError:
                    contours, _ = cv2.findContours(
                        roi_mask,
                        cv2.RETR_EXTERNAL,
                        cv2.CHAIN_APPROX_NONE)
                # Expand contour rect by 1px on all sides with some tuple magic
                rois = [tuple(sum(y) for y in zip(cv2.boundingRect(x), (-1, -1, 2, 2))) for x in contours]


            for roi in rois:
                # Trim ROI bounds to zero (if negative)
                x, y, w, h = roi
                x = max(x, 0)
                y = max(y, 0)
                # Add needle dimensions to roi
                roi = (x, y, w+lvl_needle.shape[1]-1, h+lvl_needle.shape[0]-1)
                # numpy 2D slice
                roi_slice = (slice(roi[1], roi[1]+roi[3]), slice(roi[0], roi[0]+roi[2]))
                # numpy 2D slice
                r_slice = (slice(y, y+h), slice(x, x+w))

                # Search the region of interest for needle (and update heatmap)
                matches_heatmap[r_slice] = cv2.matchTemplate(
                    lvl_haystack[roi_slice],
                    lvl_needle,
                    method)

            min_val, max_val, min_loc, max_loc = cv2.minMaxLoc(matches_heatmap)
            # Reduce similarity to allow for scaling distortion
            # (unless we are on the original image)
            pyr_similarity = max(0, similarity - (0.2 if level < len(haystack_pyramid)-1 else 0))
            position = None
            confidence = None
            # Check for a match
            if method == cv2.TM_SQDIFF_NORMED:
                confidence = min_val
                if min_val <= 1-pyr_similarity:
                    # Confidence checks out
                    position = min_loc
            else:
                confidence = max_val
                if max_val >= pyr_similarity:
                    # Confidence checks out
                    position = max_loc

            if not position:
                break

            # Find the best regions of interest
            _, roi_mask = cv2.threshold(
                # Source image
                matches_heatmap,
                # Confidence threshold
                ((1-pyr_similarity) if method == cv2.TM_SQDIFF_NORMED else pyr_similarity),
                # Max value
                255,
                # Thresholding style
                (cv2.THRESH_BINARY_INV if method == cv2.TM_SQDIFF_NORMED else cv2.THRESH_BINARY))
            roi_mask = roi_mask.astype(numpy.uint8)

        # Whew! Let's see if there's a match after all that.

        if not position:
            Debug.log(3, "Best match: {} at {}".format(max_val, max_loc))
            return None

        # There was a match!
        if method == cv2.TM_SQDIFF_NORMED:
            confidence = 1 - confidence # Invert confidence if we used the SQDIFF method
        return (position, confidence)
Finds the best match using a search pyramid to improve efficiency

        Pyramid implementation unashamedly stolen from https://github.com/stb-tester/stb-tester

        *Developer's Note - Despite the name, this method actually returns the **first** result
        with enough similarity, not the **best** result.*