Skip to content
Snippets Groups Projects
get_perfusion.py 7.7 KiB
Newer Older
"""
Utility GUI to extract the perfusion scores for polar map segments.

Note that this script depends on pre-defined definitions of locations
to find the perfusion scores in the images.

The script makes use of [Tesseract](https://github.com/tesseract-ocr/tesseract)
(character recognition) to help with labeling. The GUI always displays where to find
the current number in the whole image, the number as well as the number detected by
Tesseract. This can be accepted or modified.
"""
from dataclasses import dataclass
from typing import Tuple

import cv2 as cv
import numpy as np
import pytesseract


"""
Dataclass for polar map segments.
Each segment has an id, a name, a location and a
rectangle where it can be found in the RGB image.
"""


@dataclass
class PolarMapSegment:
    id: int
    rect: Tuple[int, int, int, int]
    name: str
    location: str


# definition of polar map segments
# see https://www.ahajournals.org/doi/full/10.1161/hc0402.102975
SEGMENTS = [
    PolarMapSegment(
        id=1, location="basal", name="anterior", rect=(27, 281, 27 + 22, 281 + 29)
    ),
    PolarMapSegment(
        id=2, location="basal", name="anteroseptal", rect=(156, 59, 156 + 22, 59 + 30)
    ),
    PolarMapSegment(
        id=3, location="basal", name="inferoseptal", rect=(411, 58, 411 + 22, 58 + 29)
    ),
    PolarMapSegment(
        id=4, location="basal", name="inferior", rect=(540, 280, 540 + 22, 280 + 29)
    ),
    PolarMapSegment(
        id=5,
        location="basal",
        name="inferolateral",
        rect=(411, 503, 411 + 22, 503 + 29),
    ),
    PolarMapSegment(
        id=6,
        location="basal",
        name="anterolateral",
        rect=(156, 503, 156 + 22, 503 + 29),
    ),
    PolarMapSegment(
        id=7, location="mid", name="anterior", rect=(100, 281, 100 + 22, 281 + 29)
    ),
    PolarMapSegment(
        id=8, location="mid", name="anteroseptal", rect=(192, 122, 192 + 22, 122 + 29)
    ),
    PolarMapSegment(
        id=9, location="mid", name="inferoseptal", rect=(375, 122, 372 + 22, 122 + 29)
    ),
    PolarMapSegment(
        id=10, location="mid", name="inferior", rect=(467, 280, 467 + 22, 280 + 29)
    ),
    PolarMapSegment(
        id=11, location="mid", name="inferolateral", rect=(375, 439, 375 + 22, 439 + 29)
    ),
    PolarMapSegment(
        id=12, location="mid", name="anterolateral", rect=(192, 439, 192 + 22, 439 + 29)
    ),
    PolarMapSegment(
        id=13, location="apical", name="anterior", rect=(174, 281, 174 + 22, 281 + 29)
    ),
    PolarMapSegment(
        id=14, location="apical", name="septal", rect=(284, 171, 284 + 22, 171 + 29)
    ),
    PolarMapSegment(
        id=15, location="apical", name="inferior", rect=(394, 280, 394 + 22, 280 + 30)
    ),
    PolarMapSegment(
        id=16, location="apical", name="lateral", rect=(284, 391, 284 + 22, 391 + 29)
    ),
    PolarMapSegment(
        id=17, location="apex", name="apex", rect=(284, 281, 284 + 22, 281 + 29)
    ),
"""
The Cedars-Sinai Cardiac Suite shows perfusion scores in a green
font. Theses limits are used for segmentation of the font.
"""
hsv_green_lower = np.array([40, 100, 100])
hsv_green_upper = np.array([80, 255, 255])


if __name__ == "__main__":
    import argparse
    import os

    import pandas as pd

    from mu_map.polar_map.prepare import headers

    parser = argparse.ArgumentParser(
        formatter_class=argparse.ArgumentDefaultsHelpFormatter,
    )
    parser.add_argument(
        "--polar_map_dir",
        type=str,
        required=True,
        help="directory where files are output to",
    )
    parser.add_argument(
        "--images_dir",
        type=str,
        default="images",
        help="directory under <out_dir> where images of polar maps are stored",
    )
    parser.add_argument(
        "--csv",
        type=str,
        default="polar_maps.csv",
        help="file unter <out_dir> where meta information is stored",
    )
    parser.add_argument(
        "--perfusion_csv",
        type=str,
        default="perfusion.csv",
        help="csv file output by this script under polar_map_dir",
    )
    parser.add_argument(
        "--number_res",
        type=int,
        default=128,
        help="numbers cutouts are rescaled to this resolution for better number recognition",
    )
    args = parser.parse_args()

    args.images_dir = os.path.join(args.polar_map_dir, args.images_dir)
    args.csv = os.path.join(args.polar_map_dir, args.csv)
    args.perfusion_csv = os.path.join(args.polar_map_dir, args.perfusion_csv)

    data = pd.read_csv(args.csv)
    data = data[data[headers.segments]]

    if os.path.isfile(args.perfusion_csv):
        perfusion = pd.read_csv(args.perfusion_csv)
    else:
        perfusion = pd.DataFrame()

    for i in range(len(data)):
        row = data.iloc[i]
        _id = row[headers.id]
        _file = row[headers.file]

        if _file in perfusion["file"].values:
            print(
                f"SKIP file {_file} as perfusion scores are already in {args.perfusion_csv}"
            )
        polar_map = cv.imread(os.path.join(args.images_dir, _file))
        for segment in SEGMENTS:
            # extract number segment
            top, left, bottom, right = segment.rect
            img_number = polar_map[top:bottom, left:right]

            # process segment for improved automatic number detection
            scale = args.number_res / img_number.shape[1]
            img_number = cv.resize(img_number, None, fx=scale, fy=scale)
            img_number = cv.cvtColor(img_number, cv.COLOR_BGR2HSV)
            img_number = cv.inRange(img_number, hsv_green_lower, hsv_green_upper)
            img_number = cv.morphologyEx(
                img_number, cv.MORPH_CLOSE, np.ones((4, 4), np.uint8)
            )
            img_number = cv.morphologyEx(
                img_number, cv.MORPH_OPEN, np.ones((2, 2), np.uint8)
            )
            _, img_number = cv.threshold(img_number, 0, 255, cv.THRESH_BINARY_INV)

            # try to recognize number
            str_number = pytesseract.image_to_string(
                img_number,
                config="-c tessedit_char_whitelist=[1,2,3,4,5,6,7,8,9,0] --psm 7",
            )
            str_number = str_number.strip()

            # prepare image for visualization
            _polar_map = polar_map.copy()
            _polar_map = cv.rectangle(
                _polar_map, (left, top), (right, bottom), (255, 255, 255), 1
            )
            _polar_map = cv.resize(_polar_map, (512, 512))
            img_number = img_number.repeat(3).reshape((*img_number.shape, 3))
            scale = 512 / img_number.shape[0]
            img_number = cv.resize(img_number, None, fx=scale, fy=scale)
            space_h = np.full((512, 10, 3), 239, np.uint8)

            cv.imshow(
                "Polar Map - Segment", np.hstack((_polar_map, space_h, img_number))
            )
            cv.waitKey(50)

            while True:
                _input = input(f"Number is {str_number} (y/other): ")
                try:
                    if _input == "y":
                        number = int(str_number)
                        break
                    elif _input == "q":
                        exit(0)
                    else:
                        number = int(_input)
                        break
                except ValueError:
                    print(
                        f"Cannot parse {_input} as a number. Please enter a valid number."
                    )

            _value = pd.Series({f"segment_{segment.id}": number})
            row = pd.concat([row, _value])
        perfusion = perfusion.append(row, ignore_index=True)
        perfusion = perfusion.sort_values(by=["id", "file"])
        perfusion.to_csv(args.perfusion_csv, index=False)