def detect(self, frame, mask=None):
        if self.row_divs == 1 and self.col_divs == 1:
            return self.detector.detect(frame, mask)
        else:
            if kVerbose:
                print('BlockAdaptor ', self.row_divs, 'x', self.col_divs)
            block_generator = img_mask_blocks(frame, mask, self.row_divs,
                                              self.col_divs)
            kps_all = []  # list are thread-safe

            def detect_block(b_m_i_j):
                b, m, i, j = b_m_i_j
                if kVerbose and False:
                    print('BlockAdaptor  in block (', i, ',', j, ')')
                kps = self.detector.detect(b, mask=m)
                #print('adaptor: detected #features: ', len(kps), ' in block (',i,',',j,')')
                for kp in kps:
                    #print('kp.pt before: ', kp.pt)
                    kp.pt = (kp.pt[0] + j, kp.pt[1] + i)
                    #print('kp.pt after: ', kp.pt)
                kps_all.extend(kps)

            if not self.do_parallel:
                # process the blocks sequentially
                for b, m, i, j in block_generator:
                    detect_block((b, m, i, j))
            else:
                with ThreadPoolExecutor(max_workers=4) as executor:
                    executor.map(
                        detect_block, block_generator
                    )  # automatic join() at the end of the `width` block
            return np.array(kps_all)
    def detectAndCompute(self, frame, mask=None):
        if self.row_divs == 1 and self.col_divs == 1:
            return self.detector.detectAndCompute(frame, mask)
        else:
            if kVerbose:
                print('BlockAdaptor ', self.row_divs, 'x', self.col_divs)
            block_generator = img_mask_blocks(frame, mask, self.row_divs,
                                              self.col_divs)
            kps_all = []
            des_all = []
            kps_des_map = {}  # (i,j) -> (kps,des)

            def detect_and_compute_block(b_m_i_j):
                b, m, i, j = b_m_i_j
                if kVerbose and False:
                    print('BlockAdaptor  in block (', i, ',', j, ')')
                if self.is_detector_equal_to_descriptor:
                    kps, des = self.detector.detectAndCompute(b, mask=m)
                else:
                    kps = self.detector.detect(b, mask=m)
                    kps, des = self.descriptor.compute(b, kps)
                    #print('adaptor: detected #features: ', len(kps), ' in block (',i,',',j,')')
                # transform the points
                for kp in kps:
                    #print('kp.pt before: ', kp.pt)
                    kp.pt = (kp.pt[0] + j, kp.pt[1] + i)
                    #print('kp.pt after: ', kp.pt)
                kps_des_map[(i, j)] = (kps, des)

            if not self.do_parallel:
                # process the blocks sequentially
                for b, m, i, j in block_generator:
                    detect_and_compute_block((b, m, i, j))
            else:
                with ThreadPoolExecutor(
                        max_workers=kBlockAdaptorMaxNumWorkers) as executor:
                    executor.map(
                        detect_and_compute_block, block_generator
                    )  # automatic join() at the end of the `width` block

            # now merge the computed results
            for ij, (kps, des) in kps_des_map.items():
                kps_all.extend(kps)
                if des is not None and len(des) > 0:
                    if len(des_all) > 0:
                        des_all = np.vstack([des_all, des])
                    else:
                        des_all = des
            return np.array(kps_all), np.array(des_all)