def parse_result_and_map(
            self, frame: Frame) -> Tuple[Optional[str], Optional[str]]:
        result_im = self.REGIONS["result"].extract_one(frame.image)
        gray = np.max(result_im, axis=2)
        # mask out white/gray text (this is map and match time info)
        white_text = ((gray > 100) &
                      (np.ptp(result_im, axis=2) < 20)).astype(np.uint8) * 255
        white_text = cv2.erode(white_text, None)
        white_text = np.sum(white_text, axis=0) / 255
        right = np.argmax(white_text > 2)
        if right > 150:
            right -= 10
            logger.info(
                f"Trimming width of result image {gray.shape[1]} -> {right} to cut white text"
            )
            gray = gray[:, :right]
        else:
            right = gray.shape[1]
        result_text = imageops.tesser_ocr(gray,
                                          whitelist="".join(
                                              set("".join(self.RESULTS))),
                                          invert=True)
        result = textops.matches(result_text, self.RESULTS)
        if np.min(result) > 2:
            logger.warning(
                f"Could not identify result from {result_text!r} (match={np.min(result)})"
            )
            return None, None

        result = self.RESULTS[arrayops.argmin(result)]
        logger.debug(f"Got result {result} from {result_text!r}")
        # TODO: test this with "draw" result
        map_image = self.REGIONS["map_name"].extract_one(frame.image)[:,
                                                                      right:]
        gray = np.min(map_image, axis=2)
        map_text = textops.strip_string(
            imageops.tesser_ocr(gray,
                                whitelist=string.ascii_uppercase + " :'",
                                invert=True,
                                scale=2),
            string.ascii_uppercase + " ",
        )
        logger.debug(f"Parsed map as {map_text}")

        return result, map_text
 def _parse_killed_name(self, frame, row, killed_agent_x) -> Optional[str]:
     killed_name_gray = self._get_region(
         frame.image_yuv,
         row.center[1] - 10,
         row.center[1] + 10,
         row.center[0] + 10,
         killed_agent_x - 10,
         0,
         debug_name="killed_name",
         debug_image=frame.debug_image,
     )
     if killed_name_gray.shape[1] == 0:
         return None
     killed_name_norm = 255 - imageops.normalise(killed_name_gray, min=170)
     return textops.strip_string(
         imageops.tesser_ocr(killed_name_norm,
                             engine=imageops.tesseract_lstm).upper(),
         alphabet=string.ascii_uppercase + string.digits + "# ",
     )
    def process(self, frame: Frame) -> bool:
        result_y = self.REGIONS["result"].extract_one(frame.image_yuv[:, :, 0])
        _, result_thresh = cv2.threshold(result_y, 220, 255, cv2.THRESH_BINARY)
        match, result = imageops.match_templates(
            result_thresh,
            self.RESULTS,
            cv2.TM_CCORR_NORMED,
            required_match=self.RESULT_TEMPLATE_REQUIRED_MATCH,
            previous_match_context=(self.__class__.__name__, "result"),
        )

        if match > self.RESULT_TEMPLATE_REQUIRED_MATCH:
            logger.debug(f"Round result is {result} with match={match}")

            score_ims = self.REGIONS["scores"].extract(frame.image)
            score_gray = [
                imageops.normalise(np.max(im, axis=2)) for im in score_ims
            ]
            scores = imageops.tesser_ocr_all(
                score_gray,
                expected_type=int,
                engine=din_next_regular_digits,
                invert=True,
            )
            logger.debug(f"Round score is {scores}")

            frame.valorant.postgame = Postgame(
                victory=result == "victory",
                score=(scores[0], scores[1]),
                map=imageops.ocr_region(frame, self.REGIONS, "map"),
                game_mode=imageops.ocr_region(frame, self.REGIONS,
                                              "game_mode"),
                image=lazy_upload("postgame",
                                  self.REGIONS.blank_out(frame.image),
                                  frame.timestamp),
            )
            draw_postgame(frame.debug_image, frame.valorant.postgame)

            sort_mode_gray = np.min(
                self.SCOREBOARD_REGIONS["scoreboard_sort_mode"].extract_one(
                    frame.image),
                axis=2)
            sort_mode_filt = 255 - imageops.normalise(sort_mode_gray,
                                                      bottom=75)
            # cv2.imshow('sort_mode_gray', sort_mode_gray)
            sort_mode = imageops.tesser_ocr(sort_mode_filt,
                                            engine=imageops.tesseract_lstm)

            sort_mode_match = max([
                levenshtein.ratio(
                    textops.strip_string(sort_mode).upper(), expected)
                for expected in self.SCOREBOARD_SORT_MODES
            ])
            logger.debug(
                f"Got scoreboard sort mode: {sort_mode!r} match={sort_mode_match:.2f}"
            )

            if sort_mode_match > 0.75:
                frame.valorant.scoreboard = self._parse_scoreboard(frame)
                draw_scoreboard(frame.debug_image, frame.valorant.scoreboard)

            return True

        return False
class PostgameProcessor(Processor):

    REGIONS = ExtractionRegionsCollection(
        os.path.join(os.path.dirname(__file__), "data", "regions", "16_9.zip"))
    SCOREBOARD_REGIONS = ExtractionRegionsCollection(
        os.path.join(os.path.dirname(__file__), "data", "regions",
                     "scoreboard", "16_9.zip"))
    RESULTS = {
        "victory":
        imageops.imread(
            os.path.join(os.path.dirname(__file__), "data", "victory.png"), 0),
        "defeat":
        imageops.imread(
            os.path.join(os.path.dirname(__file__), "data", "defeat.png"), 0),
    }
    RESULT_TEMPLATE_REQUIRED_MATCH = 0.8

    # TAB_SELECTED_TEMPLATE = np.array([0] * 50 + [3] * 73 + [5] * 24 + [3] * 73 + [0] * 50)
    # TABS = [
    #     ('summary', 240),
    #     ('scoreboard', 335)
    # ]
    SCOREBOARD_SORT_MODES = [
        textops.strip_string("Individually Sorted").upper(),
        textops.strip_string("Grouped By Team").upper(),
    ]

    AGENT_TEMPLATES = {
        name: load_agent_template(
            os.path.join(os.path.dirname(__file__), "data", "agents",
                         name.lower() + ".png"))
        for name in agents
    }
    AGENT_TEMPLATE_REQUIRED_MATCH = 50

    def process(self, frame: Frame) -> bool:
        result_y = self.REGIONS["result"].extract_one(frame.image_yuv[:, :, 0])
        _, result_thresh = cv2.threshold(result_y, 220, 255, cv2.THRESH_BINARY)
        match, result = imageops.match_templates(
            result_thresh,
            self.RESULTS,
            cv2.TM_CCORR_NORMED,
            required_match=self.RESULT_TEMPLATE_REQUIRED_MATCH,
            previous_match_context=(self.__class__.__name__, "result"),
        )

        if match > self.RESULT_TEMPLATE_REQUIRED_MATCH:
            logger.debug(f"Round result is {result} with match={match}")

            score_ims = self.REGIONS["scores"].extract(frame.image)
            score_gray = [
                imageops.normalise(np.max(im, axis=2)) for im in score_ims
            ]
            scores = imageops.tesser_ocr_all(
                score_gray,
                expected_type=int,
                engine=din_next_regular_digits,
                invert=True,
            )
            logger.debug(f"Round score is {scores}")

            frame.valorant.postgame = Postgame(
                victory=result == "victory",
                score=(scores[0], scores[1]),
                map=imageops.ocr_region(frame, self.REGIONS, "map"),
                game_mode=imageops.ocr_region(frame, self.REGIONS,
                                              "game_mode"),
                image=lazy_upload("postgame",
                                  self.REGIONS.blank_out(frame.image),
                                  frame.timestamp),
            )
            draw_postgame(frame.debug_image, frame.valorant.postgame)

            sort_mode_gray = np.min(
                self.SCOREBOARD_REGIONS["scoreboard_sort_mode"].extract_one(
                    frame.image),
                axis=2)
            sort_mode_filt = 255 - imageops.normalise(sort_mode_gray,
                                                      bottom=75)
            # cv2.imshow('sort_mode_gray', sort_mode_gray)
            sort_mode = imageops.tesser_ocr(sort_mode_filt,
                                            engine=imageops.tesseract_lstm)

            sort_mode_match = max([
                levenshtein.ratio(
                    textops.strip_string(sort_mode).upper(), expected)
                for expected in self.SCOREBOARD_SORT_MODES
            ])
            logger.debug(
                f"Got scoreboard sort mode: {sort_mode!r} match={sort_mode_match:.2f}"
            )

            if sort_mode_match > 0.75:
                frame.valorant.scoreboard = self._parse_scoreboard(frame)
                draw_scoreboard(frame.debug_image, frame.valorant.scoreboard)

            return True

        return False

    def _parse_scoreboard(self, frame: Frame) -> Scoreboard:
        agent_images = self.SCOREBOARD_REGIONS["agents"].extract(frame.image)

        name_images = self.SCOREBOARD_REGIONS["names"].extract(frame.image)

        stat_images = self.SCOREBOARD_REGIONS["stats"].extract(frame.image)
        stat_images_filt = [
            self._filter_statrow_image(im) for im in stat_images
        ]
        stat_image_rows = [
            stat_images_filt[r * 8:(r + 1) * 8] for r in range(10)
        ]

        # cv2.imshow(
        #     'stats',
        #     np.vstack([
        #         np.hstack([self._filter_statrow_image(n)] + r)
        #         for n, r in zip(name_images, stat_image_rows)
        #     ])
        # )

        stats = []
        for i, (agent_im, name_im, stat_row) in enumerate(
                zip(agent_images, name_images, stat_image_rows)):
            agent_match, agent = imageops.match_templates(
                agent_im,
                self.AGENT_TEMPLATES,
                method=cv2.TM_SQDIFF,
                required_match=self.AGENT_TEMPLATE_REQUIRED_MATCH,
                use_masks=True,
                previous_match_context=(self.__class__.__name__, "scoreboard",
                                        "agent", i),
            )
            if agent_match > self.AGENT_TEMPLATE_REQUIRED_MATCH:
                agent = None

            row_bg = name_im[np.max(name_im, axis=2) < 200]
            row_color = np.median(row_bg, axis=0).astype(np.int)

            # cv2.imshow('name', self._filter_statrow_image(name_im))
            # cv2.waitKey(0)
            stat = PlayerStats(
                agent,
                imageops.tesser_ocr(
                    self._filter_statrow_image(name_im),
                    engine=imageops.tesseract_lstm,
                ),
                row_color[0] > row_color[2],
                *imageops.tesser_ocr_all(
                    stat_row,
                    expected_type=int,
                    engine=din_next_regular_digits,
                ),
            )
            stats.append(stat)
            logger.debug(
                f"Got player stats: {stat} - agent match={agent_match:.2f}, row colour={tuple(row_color)}"
            )

        return Scoreboard(
            stats,
            image=lazy_upload("scoreboard",
                              self.SCOREBOARD_REGIONS.blank_out(frame.image),
                              frame.timestamp),
        )

    def _filter_statrow_image(self, im):
        im_gray = np.min(im, axis=2).astype(np.float)
        bgcol = np.percentile(im_gray, 90)
        im_norm = im_gray - bgcol
        im_norm = im_norm / np.max(im_norm)
        im = 255 - np.clip(im_norm * 255, 0, 255).astype(np.uint8)
        return im
    def process(self, frame: Frame) -> bool:
        agent_name_yuv = self.REGIONS["agent_name"].extract_one(
            frame.image_yuv)
        agent_name_thresh = cv2.inRange(agent_name_yuv, (200, 85, 120),
                                        (255, 115, 150))
        # if hasattr(frame, 'source_image'):
        # 	cv2.imshow('agent_name_yuv', agent_name_yuv)
        # 	cv2.imshow('agent_name_thresh', agent_name_thresh)
        # 	cv2.imwrite(
        # 		os.path.join(os.path.dirname(__file__), 'data', 'agent_names', os.path.basename(frame.source_image)),
        # 		agent_name_thresh
        # 	)

        match, best_match = imageops.match_templates(
            agent_name_thresh,
            self.AGENT_NAME_TEMPLATES,
            method=cv2.TM_CCORR_NORMED,
            required_match=0.95,
            # verbose=True,
        )
        # self.REGIONS.draw(frame.debug_image)

        if match > self.AGENT_TEMPLATE_REQUIRED_MATCH:
            selected_agent_ims = self.REGIONS["selected_agents"].extract(
                frame.image)
            selected_agent_ims_gray = [
                255 - imageops.normalise(np.max(im, axis=2), bottom=50)
                for im in selected_agent_ims
            ]
            selected_agent_texts = imageops.tesser_ocr_all(
                selected_agent_ims_gray,
                engine=imageops.tesseract_lstm,
            )
            logger.info(f"Got selected_agent_texts={selected_agent_texts}")

            picking = True
            for i, text in enumerate(selected_agent_texts):
                for word in textops.strip_string(text, string.ascii_letters +
                                                 " .").split(" "):
                    match = levenshtein.ratio(word, best_match)
                    logger.debug(
                        f"Player {i}: Got match {match:.2f} for {word!r} = {best_match!r}"
                    )
                    if match > 0.7:
                        logger.info(
                            f"Found matching locked in agent {text!r} for selecting agent {best_match!r} - selection locked"
                        )
                        picking = False

            game_mode = imageops.ocr_region(frame, self.REGIONS, "game_mode")

            ranks = []
            for i, im in enumerate(self.REGIONS["player_ranks"].extract(
                    frame.image)):
                match, matched_rank = imageops.match_templates(
                    im,
                    self.RANK_TEMPLATES,
                    method=cv2.TM_SQDIFF,
                    use_masks=True,
                    required_match=15,
                    previous_match_context=("player_ranks", i),
                )
                ranks.append((matched_rank, round(match, 3)))

            player_name_ims = self.REGIONS["player_names"].extract(frame.image)
            player_name_gray = [
                255 - imageops.normalise(np.max(im, axis=2), bottom=50)
                for im in player_name_ims
            ]
            player_names = imageops.tesser_ocr_all(
                player_name_gray, engine=imageops.tesseract_lstm)

            frame.valorant.agent_select = AgentSelect(
                best_match,
                locked_in=not picking,
                map=imageops.ocr_region(frame, self.REGIONS, "map"),
                game_mode=game_mode,
                player_names=player_names,
                agents=selected_agent_texts,
                ranks=ranks,
                image=lazy_upload("agent_select",
                                  self.REGIONS.blank_out(frame.image),
                                  frame.timestamp,
                                  selection="last"),
            )
            draw_agent_select(frame.debug_image, frame.valorant.agent_select)
            return True

        return False
Exemple #6
0
    def _parse_score_report(self, y: np.ndarray) -> ScoreReport:
        rp_report_image = self.REGIONS["rp_fields"].extract_one(y)

        lines = []
        for line in range(3):
            line_im = rp_report_image[line * 40 + 5:(line + 1) * 40 - 7, 5:]
            lines.append(
                imageops.tesser_ocr(line_im,
                                    engine=imageops.tesseract_lstm,
                                    invert=True,
                                    scale=2))

        score_report = ScoreReport()
        for line in lines:
            valid = False
            if ":" in line:
                stat_name, stat_value = line.lower().replace(" ",
                                                             "").split(":", 1)
                if stat_name == "entrycost":
                    score_report.entry_rank = stat_value.lower()
                    valid = True
                elif stat_name == "kills":
                    try:
                        score_report.kills = int(stat_value.replace("o", "0"))
                    except ValueError:
                        logger.warning(
                            f'Could not parse Score Report > kills: {stat_value!r}" as int'
                        )
                    else:
                        valid = True
                elif stat_name == "matchplacement":
                    stat_value = stat_value.replace("#", "")
                    try:
                        score_report.placement = int(
                            stat_value.replace("o", "0").split("/", 1)[0])
                    except ValueError:
                        logger.warning(
                            f'Could not parse Score Report > placement: {stat_value!r}" as placement'
                        )
                    else:
                        valid = True
            if not valid:
                logger.warning(f"Unknown line in score report: {line!r}")

        score_adjustment_image = self.REGIONS["score_adjustment"].extract_one(
            y)
        score_adjustment_text = imageops.tesser_ocr(
            score_adjustment_image,
            engine=imageops.tesseract_lstm,
            invert=True,
            scale=1)
        score_adjustment_text_strip = (textops.strip_string(
            score_adjustment_text, alphabet=string.digits + "RP+-").replace(
                "RP", "").replace("+", "").replace("-", ""))
        try:
            score_report.rp_adjustment = int(score_adjustment_text_strip)
        except ValueError:
            logger.warning(
                f'Could not parse Score Report > score adjustment: {score_adjustment_text!r}" as valid adjustment'
            )

        current_rp_image = self.REGIONS["current_rp"].extract_one(y)
        current_rp_text = imageops.tesser_ocr(current_rp_image,
                                              engine=imageops.tesseract_lstm,
                                              invert=True,
                                              scale=1)
        current_rp_text_strip = textops.strip_string(current_rp_text,
                                                     alphabet=string.digits +
                                                     "RP").replace("RP", "")
        try:
            score_report.current_rp = int(current_rp_text_strip)
        except ValueError:
            logger.warning(
                f'Could not parse Score Report > current RP: {current_rp_text!r}" as valid RP'
            )

        return score_report
Exemple #7
0
    def _process_player_stats(self,
                              y: np.ndarray,
                              duos: bool = False,
                              shunt: int = 0) -> Tuple[PlayerStats, ...]:
        name_images = self.REGIONS["names"].shunt(x=shunt).extract(y)
        names = []
        for im in name_images:
            # self._mask_components_touching_edges(im)
            im = 255 - cv2.bitwise_and(
                im,
                cv2.dilate(
                    cv2.threshold(im, 0, 255,
                                  cv2.THRESH_BINARY | cv2.THRESH_OTSU)[1],
                    None,
                ),
            )
            im = cv2.resize(im, (0, 0), fx=2, fy=2)
            im = cv2.GaussianBlur(im, (0, 0), 1)

            name = imageops.tesser_ocr(
                im,
                engine=imageops.tesseract_lstm,
            ).replace(" ", "")
            match = np.mean(imageops.tesseract_lstm.AllWordConfidences())
            logger.info(f"Got name {name!r} ~ {match:1.2f}")
            if match < 0.75:
                name = imageops.tesser_ocr(
                    im,
                    engine=imageops.tesseract_only,
                )
                logger.info(f"Using {name!r} instead")
            names.append(name)

        stat_images = self.REGIONS["stats"].shunt(x=shunt).extract(y)

        # for im in stat_images:
        #     self._mask_components_touching_edges(im)

        stats = imageops.tesser_ocr_all(
            stat_images,
            engine=ocr.tesseract_ttlakes_digits_specials,
        )

        for i in range(len(stats)):
            value = stats[i]
            logger.debug(f"Got stat {i}: {value!r}")
            if value:
                value = value.lower().replace(" ", "")
                for c1, c2 in "l1", "i1", "o0", (":", ""):
                    value = value.replace(c1, c2)
                value = textops.strip_string(value, string.digits + "/")
            if i < 3:
                try:
                    stats[i] = tuple([int(v) for v in value.split("/")])
                except ValueError as e:
                    logger.warning(f'Could not parse {value!r} as 3 ints" {e}')
                    stats[i] = None
            elif 6 <= i <= 8:
                # survival time
                if stats[i] is not None:
                    try:
                        seconds = int(value)
                    except ValueError as e:
                        logger.warning(
                            f'Could not parse "{stats[i]}" as int: {e}')
                        seconds = None
                    else:
                        seconds = mmss_to_seconds(seconds)
                        logger.info(f"MM:SS {stats[i]} -> {seconds}")
                    stats[i] = seconds
            else:
                try:
                    stats[i] = int(value)
                except ValueError as e:
                    logger.warning(f'Could not parse {value!r} as int" {e}')
                    stats[i] = None

        # typing: ignore
        # noinspection PyTypeChecker
        count = 3 if not duos else 2
        r = tuple([PlayerStats(names[i], *stats[i::3]) for i in range(count)])

        for s in r:
            if not s.kills:
                pass
            elif len(s.kills) == 3:
                s.assists = s.kills[1]
                s.knocks = s.kills[2]
                s.kills = s.kills[0]
            else:
                s.kills = s.kills[0]

        logger.info(f"Got {pprint.pformat(r)}")
        return r