import os
import sys
import cv2
import json
import uuid
import base64
import pydicom
import numpy as np
from io import BytesIO
from pydicom.dataset import Dataset
from PIL import ImageFont, Image, ImageDraw
import medical_image
from medical_image.utils import *


def init_json():
    return {
            "Height": None,
            "Width": None,
            "GraphicAnnotationSequence": [
                # {
                #     "GraphicLayer": None,
                #     "TextObjectSequence": [],
                #     "GraphicObjectSequence": [],
                #     "Probability": None,
                #     "Heatmap": None
                # }
            ],
            "SCArray": None,
            "ReportArray": None,
            "ImageComments": None,
            "Skipping": False
    }


def init_json_3D():
    return {
            "GraphicAnnotationSequence": [
                # {
                #     "GraphicLayer": None,
                #     "Objects": [],
                #     "Probability": None,
                # }
            ],
            "ReportArray": None,
            "ImageComments": None
    }


class DicomModule(object):
    def __init__(self, web_json):
        self.read_config(web_json)
        self.device = self.web_operation['device']
        self.gpu_index = self.web_operation['gpuIndex']
        self.init_models()

        self.app_name = os.environ["APP_NAME"]
        self.app_version = os.environ["APP_VERSION"]
        self.write_version = True

        self.graphic_layer_sequence = [
            get_graphic_layer_dataset('Logo', 0),
            get_graphic_layer_dataset('No Finding', 1)
        ]

        self.logo = Image.open(os.path.join(medical_image.__path__, 'logo/deepnoid.png'))
        self.colorbar = Image.open(os.path.join(medical_image.__path__, 'logo/colorbar.png'))

    def read_config(self, web_json):
        self.web_json = web_json
        self.web_vis = web_json['models']['visualization']
        self.web_sc = web_json['models']['secondary_capture']
        if 'report' in web_json['models']:
            self.web_report = web_json['models']['report']
        else:
            self.web_report = None
        if 'grayscale_softcopy_presentation_state' in web_json['models']:
            self.web_gsps = web_json['models']['grayscale_softcopy_presentation_state']
        else:
            self.web_gsps = None
        self.web_operation = web_json['configurations']['operation']
            
    def init_models(self):
        raise NotImplementedError

    def run(self, web_json, input_path, output_path):
        self.read_config(web_json)

        scanned_studies = scan_directory(input_path)
        study_uids = [key for key in scanned_studies.keys()]

        print(study_uids, flush=True)
        if len(study_uids) > 1:
            print(len(study_uids), flush=True)
            raise AssertionError
        study_uid = study_uids[0]

        study_paths = scanned_studies[study_uid]
        series_uids = [key for key in study_paths.keys()]

        ds_init = pydicom.dcmread(study_paths[series_uids[0]][0], force=True)
        
        if os.environ['AI_REQUEST_TAG'] == 'Private3355':
            if (0x3355, 0x1002) in ds_init:
                req_types = ds_init[0x3355, 0x1002].value.split('::')[-1].split('&')
                preferred_charsets = ds_init[0x3355, 0x1003].value.split('::')[-1]
            else:
                req_types = []
                if self.web_sc['@create']['selected'] == 'On':
                    req_types.append('Secondary-Capture')
                if self.web_gsps['@create']['selected'] == 'On':
                    req_types.append('GSPS')
                preferred_charsets = ''
        elif os.environ['AI_REQUEST_TAG'] == 'StudyComments':
            _, req_types, preferred_charsets = ds_init.StudyComments.split('::')[2::2]
            req_types = req_types.split('&')
        else:
            raise KeyError
        self.preferred_charsets = preferred_charsets.split('\\')[-1]
        print("req_types:", req_types)
        print("preferred_charsets:", self.preferred_charsets)

        referenced_series_sequence = []
        displayed_area_selection_sequence = []
        graphic_annotation_sequence = []
        VOI_LUT_sequence = []
        finding_sequences = []
        conversion_source_attributes_sequence = []

        self.generated_uuid = uuid.uuid4().int
        for series_uid in series_uids:
            series_paths = study_paths[series_uid]
            referenced_image_sequence = []
            finding_sequence = []
            for path in series_paths:
                ds = pydicom.dcmread(str(path), force=True)
                result_json = self.run_models(ds)
                if not result_json['Skipping']:
                    referenced_image = get_referenced_image_dataset(ds)
                    referenced_image_sequence.append(referenced_image)
                    conversion_source_attributes_sequence.append(referenced_image)
                    displayed_area_selection_sequence.append(get_displayed_area_selection(ds, [referenced_image]))
                    VOI_LUT_sequence.append(get_VOI_LUT_dataset(ds, [referenced_image]))

                    finding_sequence += self.update_gsps(graphic_annotation_sequence, referenced_image, result_json)
                    self.add_logo_annotation(graphic_annotation_sequence, referenced_image, result_json)

                    if 'Secondary-Capture' in req_types:
                        self.make_sc_dcm(ds, result_json, output_path)

            if len(finding_sequence):
                finding_sequences += finding_sequence
                referenced_series_sequence.append(get_referenced_series_dataset(series_uid, referenced_image_sequence))

        if len(finding_sequences) and ('GSPS' in req_types):
            self.make_gsps_dcm(
                ds,
                finding_sequences,
                displayed_area_selection_sequence,
                graphic_annotation_sequence,
                referenced_series_sequence,
                conversion_source_attributes_sequence,
                VOI_LUT_sequence,
                output_path
            )

    def make_sc_dcm(self, ds, result_json, output_path):
        # assert (type(result_json['InputArray']) == np.ndarray) or type(result_json['SCArray']) == np.ndarray
        referenced_image = get_referenced_image_dataset(ds)

        ds_sc = make_dataset(ds, self.app_name, self.app_version, 'SC', self.generated_uuid, self.preferred_charsets)
        ds_sc.WindowCenter = 128
        ds_sc.WindowWidth = 255
        if '@append_series_number' in self.web_sc:
            if self.web_sc['@append_series_number']['selected'] == 'On':
                ds_sc.SeriesNumber = self.web_sc['series_number'] + str(ds.SeriesNumber)
            else:
                ds_sc.SeriesNumber = self.web_sc['series_number']
        else:
            ds_sc.SeriesNumber = self.web_sc['series_number']

        if type(result_json["SCArray"]) == np.ndarray:
            ds_sc.PixelData = result_json["SCArray"].tobytes()
        else:
            assert "InputArray" not in result_json
            assert result_json["SCArray"] == None
            assert result_json['Height'] == ds.Rows
            assert result_json['Width'] == ds.Columns
            result_json['InputArray'] = read_dcm(ds)
            ds_sc.PixelData = self.make_sc_array(result_json).tobytes()
            
        ds_sc.Rows = result_json["Height"]
        ds_sc.Columns = result_json["Width"]

        if len(result_json["GraphicAnnotationSequence"]):
            finding_sequence = []
            for graphic_annotation in result_json["GraphicAnnotationSequence"]:
                dataset = Dataset()
                dataset.add_new((0x2021, 0x0010), 'LO', 'DEEPNOID')
                dataset.add_new((0x2021, 0x1009), 'UT', json.dumps({
                    'Name': graphic_annotation['GraphicLayer'],
                    'Probability': float(graphic_annotation['Probability'])
                }))
                dataset.ReferencedImageSequence = [referenced_image]
                finding_sequence.append(dataset)
            finding_sequence = sorted(finding_sequence, key=lambda seq: json.loads(seq[0x2021, 0x1009].value)['Probability'], reverse=True)
        else:
            dataset = Dataset()
            dataset.add_new((0x2021, 0x0010), 'LO', 'DEEPNOID')
            dataset.add_new((0x2021, 0x1009), 'UT', json.dumps({
                'Name': 'No Finding',
                'Probability': 0.0000
            }))
            dataset.ReferencedImageSequence = [referenced_image]
            finding_sequence = [dataset]

        set_private_tags(ds_sc, finding_sequence, self.app_name, self.app_version)
        if type(result_json['ImageComments']) == str:
            ds_sc.ImageComments = result_json['ImageComments']
        ds_sc.save_as(str(output_path / (ds_sc.SOPInstanceUID + '.dcm')))
        print(str(output_path / (ds_sc.SOPInstanceUID + '.dcm')))

    def make_sc_array(self, result_json):
        mfont = ImageFont.truetype(os.path.join(medical_image.__path__, 'fonts/NotoSansCJKkr-Medium.otf'), int(result_json['Height'] / 50))
        image = Image.fromarray(result_json['InputArray'].astype(np.uint8))
        img_editable = ImageDraw.Draw(image)

        ## Logo
        logo_w = self.logo.size[0] * result_json['Height'] * 0.55 / 2472
        logo_h = self.logo.size[1] * result_json['Height'] * 0.55 / 2472
        x = (result_json['Width'] - logo_w) / 2
        y = result_json['Height'] * 0.945 - logo_h
        paste_image(image, self.logo, x, y, logo_w, logo_h, 1)

        ## App Info
        msg = APP2PROUDCT[self.app_name]
        if self.write_version:
            msg += ' v.' + self.app_version
        mw, mh = mfont.getsize(msg)
        x = (result_json['Width'] - mw) / 2
        y = result_json['Height'] * 0.940 - logo_h - mh
        shadow = int(mh / 20)
        img_editable.text((x + shadow, y + shadow), msg, (167, 167, 167), mfont)
        img_editable.text((x, y), msg, (255, 255, 255), mfont)

        ## Colorbar
        heatmaps = [g['Heatmap'] for g in result_json["GraphicAnnotationSequence"]]
        # if len(heatmaps) != heatmaps.count(""):
        if len(heatmaps) != heatmaps.count(None):
            w, h = self.colorbar.size
            w *= result_json['Height'] * 0.4 / 2472 * 1000 / 1757
            h *= result_json['Height'] * 0.4 / 2472 * 1000 / 1757
            x = result_json['Width'] * 0.05
            y = result_json['Height'] * 0.945 - h
            paste_image(image, self.colorbar, x, y, w, h, 1)
            sfont = ImageFont.truetype(os.path.join(medical_image.__path__, 'fonts/NotoSansCJKkr-Medium.otf'), int(result_json['Height'] / 90))
            hw, _ = sfont.getsize('High')
            img_editable.text((x, y + h), 'Low', (255, 255, 255), sfont)
            img_editable.text((x + w - hw, y + h), 'High', (255, 255, 255), sfont)

        ## GraphicObject
        contour_image_array = np.array(image)
        for graphic_annotation in result_json["GraphicAnnotationSequence"]:
            heatmap_str = graphic_annotation['Heatmap']
            if heatmap_str != None:
                heatmap = np.array(Image.open(BytesIO(base64.b64decode(heatmap_str))))
                heatmap = cv2.cvtColor(heatmap, cv2.COLOR_BGRA2BGR)
                contour_image_array = cv2.addWeighted(contour_image_array, 1, heatmap, float(self.web_vis['opacity']), 0)

            for graphic_object in graphic_annotation['GraphicObjectSequence']:
                graphic_data = np.array(graphic_object['graphic_data'], np.int32)
                line_color = graphic_object['color'] if 'color' in graphic_object else (255, 255, 255)
                if ('shadow_color' not in graphic_object) or (graphic_object['shadow_color'] != 'OFF'):
                    shadow_color = graphic_object['shadow_color'] if 'shadow_color' in graphic_object else (0, 0, 0)
                    contour_image_array = cv2.polylines(contour_image_array, [graphic_data], False, shadow_color, 10)
                contour_image_array = cv2.polylines(contour_image_array, [graphic_data], False, line_color, max(int(min(result_json['Width'], result_json['Height']) / 500), 1))

        image = Image.fromarray(contour_image_array)
        img_editable = ImageDraw.Draw(image)
        ## TextObject
        for graphic_annotation in result_json["GraphicAnnotationSequence"]:
            for text_object in graphic_annotation['TextObjectSequence']:
                text_color = text_object['color'] if 'color' in text_object else (255, 255, 255)
                msg = text_object['text_data']
                x, y, _, _ = text_object['bbox']
                if ('shadow_color' not in text_object) or (text_object['shadow_color'] != 'OFF'):
                    mw, mh = mfont.getsize(msg)
                    shadow = int(mh / 20)
                    shadow_color = text_object['shadow_color'] if 'shadow_color' in text_object else (167, 167, 167)
                    img_editable.text((x + shadow, y + shadow), msg, shadow_color, mfont)
                img_editable.text((x, y), msg, text_color, mfont)
        return np.array(image)

    def update_gsps(self, graphic_annotation_sequence, referenced_image, result_json):
        def get_text_object_dcm(text_objects):
            text_object_sequence = []
            for text_object in text_objects:
                text_color = text_object['color'] if 'color' in text_object else [255, 255, 255]
                if 'shadow_color' not in text_object:
                    text_style = get_text_style_dataset(
                        color=text_color,
                        shadow_style='NORMAL',
                        shadow_color=[167, 167, 167],
                        shadow_offset=[int((text_object['bbox'][3] - text_object['bbox'][1]) / 20)] * 2
                    )
                elif text_object['shadow_color'] == 'OFF':
                    text_style = get_text_style_dataset(
                        color=text_color,
                        shadow_style='OFF'
                    )
                else:
                    text_style = get_text_style_dataset(
                        color=text_color,
                        shadow_style='NORMAL',
                        shadow_color=text_object['shadow_color'],
                        shadow_offset=[int((text_object['bbox'][3] - text_object['bbox'][1]) / 20)] * 2
                    )
                text_object_sequence.append(
                    get_text_object_dataset(
                        text_data=text_object["text_data"],
                        text_style=text_style,
                        bbox=text_object["bbox"]
                    )
                )
            return text_object_sequence

        def get_graphic_object_dcm(graphic_objects):
            graphic_object_sequence = []
            for graphic_object in graphic_objects:
                line_color = graphic_object['color'] if 'color' in graphic_object else [255, 255, 255]
                if 'shadow_color' not in graphic_object:
                    line_style = get_line_style_dataset(
                        color=line_color,
                        thickness=3.0,
                        shadow_style='OUTLINED',
                        shadow_color=[0, 0, 0],
                        shadow_offset=[6, 6]
                    )
                elif graphic_object['shadow_color'] == 'OFF':
                    line_style = get_line_style_dataset(
                        color=line_color,
                        thickness=3.0,
                        shadow_style='OFF'
                    )
                else:
                    line_style = get_line_style_dataset(
                        color=line_color,
                        thickness=3.0,
                        shadow_style='OUTLINED',
                        shadow_color=graphic_object['shadow_color'],
                        shadow_offset=[6, 6]
                    )
                graphic_object_sequence.append(
                    get_graphic_object_dataset(
                        graphic_data=list(graphic_object["graphic_data"].ravel()),
                        line_style=line_style
                    )
                )
            return graphic_object_sequence

        if len(result_json["GraphicAnnotationSequence"]):
            finding_sequence = []
            for graphic_annotation in result_json["GraphicAnnotationSequence"]:
                graphic_annotation_sequence.append(
                    get_graphic_annotation_dataset(
                        referenced_image_sequence=[referenced_image],
                        layer_name=graphic_annotation['GraphicLayer'],
                        text_object_sequence=get_text_object_dcm(graphic_annotation["TextObjectSequence"]),
                        graphic_object_sequence=get_graphic_object_dcm(graphic_annotation["GraphicObjectSequence"])
                    )
                )
                dataset = Dataset()
                dataset.add_new((0x2021, 0x0010), 'LO', 'DEEPNOID')
                if graphic_annotation['Heatmap'] == None:
                    heatmap_str = encoding_heatmap(np.zeros([result_json['Height'], result_json['Width']]), 0.5)
                    opacity = 0.5
                else:
                    heatmap_str = graphic_annotation['Heatmap']
                    opacity = self.web_vis['opacity']
                dataset.add_new((0x2021, 0x1009), 'UT', json.dumps({
                    'Name': graphic_annotation['GraphicLayer'],
                    'Probability': float(graphic_annotation['Probability']),
                    'Heatmap': heatmap_str,
                    'BlendingRatio': float(opacity)
                }))
                dataset.ReferencedImageSequence = [referenced_image]
                finding_sequence.append(dataset)

        else:
            dataset = Dataset()
            dataset.add_new((0x2021, 0x0010), 'LO', 'DEEPNOID')
            dataset.add_new((0x2021, 0x1009), 'UT', json.dumps({
                'Name': 'No Finding',
                'Probability': 0.0000,
                'Heatmap': encoding_heatmap(np.zeros([result_json['Height'], result_json['Width']]), 0.5),
                'BlendingRatio': float(0.5)
            }))
            dataset.ReferencedImageSequence = [referenced_image]
            finding_sequence = [dataset]
        return finding_sequence

    def add_logo_object(self, text_object_sequence, result_json):
        mfont = ImageFont.truetype(os.path.join(medical_image.__path__, 'fonts/NotoSansCJKkr-Medium.otf'), int(result_json['Height'] / 50))
        ## Logo
        msg = 'D E E P N O I D'
        w0, h0 = mfont.getsize(msg)
        x = (result_json['Width'] - w0) / 2
        y = result_json['Height'] * 0.945 - h0
        shadow = int(h0 / 20)
        text_style = get_text_style_dataset(
            color=[255, 255, 255],
            shadow_style='NORMAL',
            shadow_color=[255, 255, 255],
            shadow_offset=[shadow, shadow],
        )
        text_object_sequence.append(
            get_text_object_dataset(
                text_data=msg,
                text_style=text_style,
                bbox=[x, y, x + w0, y + h0]
            )
        )

        ## App
        msg = APP2PROUDCT[self.app_name]
        if self.write_version:
            msg += ' v.' + self.app_version
        w1, h1 = mfont.getsize(msg)
        x = (result_json['Width'] - w1) / 2
        y = result_json['Height'] * 0.940 - h0 - h1
        shadow = int(h1 / 20)
        text_style = get_text_style_dataset(
            color=[255, 255, 255],
            shadow_style='NORMAL',
            shadow_color=[255, 255, 255],
            shadow_offset=[shadow, shadow],
        )
        text_object_sequence.append(
            get_text_object_dataset(
                text_data=msg,
                text_style=text_style,
                bbox=[x, y, x + w1, y + h1]
            )
        )

    def add_logo_annotation(self, graphic_annotation_sequence, referenced_image, result_json):
        text_object_sequence = []
        self.add_logo_object(text_object_sequence, result_json)
        graphic_annotation = get_graphic_annotation_dataset(
            referenced_image_sequence=[referenced_image],
            layer_name='Logo',
            text_object_sequence=text_object_sequence,
            graphic_object_sequence=[]
        )
        graphic_annotation_sequence.append(graphic_annotation)

    def make_gsps_dcm(
        self,
        ds,
        finding_sequences,
        displayed_area_selection_sequence,
        graphic_annotation_sequence,
        referenced_series_sequence,
        conversion_source_attributes_sequence,
        VOI_LUT_sequence,
        output_path
        ):
        ds_gsps = make_dataset(ds, self.app_name, self.app_version, 'GSPS', self.generated_uuid, self.preferred_charsets)
        if '@append_series_number' in self.web_gsps:
            if self.web_gsps['@append_series_number']['selected'] == 'On':
                ds_gsps.SeriesNumber = self.web_gsps['series_number'] + str(ds.SeriesNumber)
            else:
                ds_gsps.SeriesNumber = self.web_gsps['series_number']
        else:
            ds_gsps.SeriesNumber = os.environ['GSPS_SERIES_NUMBER'] #self.web_gsps['series_number']
        ds_gsps.GraphicLayerSequence = self.graphic_layer_sequence

        if len(finding_sequences):
            finding_sequences = sorted(finding_sequences, key=lambda seq: json.loads(seq[0x2021, 0x1009].value)['Probability'], reverse=True)

        if ds.PhotometricInterpretation == 'MONOCHROME1':
            ds_gsps.PresentationLUTShape = 'INVERSE'

        set_private_tags(ds_gsps, finding_sequences, self.app_name, self.app_version)
        ds_gsps.DisplayedAreaSelectionSequence = displayed_area_selection_sequence
        ds_gsps.GraphicAnnotationSequence = graphic_annotation_sequence
        ds_gsps.ReferencedSeriesSequence = referenced_series_sequence
        ds_gsps.ConversionSourceAttributesSequence = conversion_source_attributes_sequence
        ds_gsps.SoftcopyVOILUTSequence = VOI_LUT_sequence

        ds_gsps.save_as(str(output_path / 'gsps.dcm'))
        print(str(output_path / 'gsps.dcm'))


class DicomModule3D(object):
    def __init__(self, web_json):
        self.read_config(web_json)
        self.device = self.web_operation['device']
        self.gpu_index = self.web_operation['gpuIndex']
        self.init_models()

        self.app_name = os.environ["APP_NAME"]
        self.app_version = os.environ["APP_VERSION"]
        self.write_version = True

        self.graphic_layer_sequence = [
            get_graphic_layer_dataset('Logo', 0),
            get_graphic_layer_dataset('No Finding', 1)
        ]

        self.logo = Image.open(os.path.join(medical_image.__path__, 'logo/deepnoid.png'))
        self.colorbar = Image.open(os.path.join(medical_image.__path__, 'logo/colorbar.png'))

    def read_config(self, web_json):
        self.web_json = web_json
        self.web_vis = web_json['models']['visualization']
        self.web_sc = web_json['models']['secondary_capture']
        if 'report' in web_json['models']:
            self.web_report = web_json['models']['report']
        else:
            self.web_report = None
        if 'grayscale_softcopy_presentation_state' in web_json['models']:
            self.web_gsps = web_json['models']['grayscale_softcopy_presentation_state']
        else:
            self.web_gsps = None
        # TODO 나중에 3D에서도 GSPS 적용할 수 있도록 수정
        # self.web_gsps = web_json['models']['grayscale_softcopy_presentation_state']
        self.web_operation = web_json['configurations']['operation']
            
    def init_models(self):
        raise NotImplementedError

    def run(self, web_json, input_path, output_path):
        self.read_config(web_json)
        if (self.device != self.web_operation['device']) or (self.gpu_index != self.web_operation['gpuIndex']):
            print('Configuration of Device has been changed. Reload model.', flush=True)
            self.__delattr__('models')
            self.device = self.web_operation['device']
            self.gpu_index = self.web_operation['gpuIndex']
            self.init_models()

        scanned_studies = scan_directory(input_path)
        study_uids = [key for key in scanned_studies.keys()]

        print(study_uids, flush=True)
        if len(study_uids) > 1:
            print(len(study_uids), flush=True)
            raise AssertionError
        study_uid = study_uids[0]

        study_paths = scanned_studies[study_uid]
        series_uids = [key for key in study_paths.keys()]

        ds_init = pydicom.dcmread(study_paths[series_uids[0]][0], force=True)
        
        if os.environ['AI_REQUEST_TAG'] == 'Private3355':
            if (0x3355, 0x1002) in ds_init:
                req_types = ds_init[0x3355, 0x1002].value.split('::')[-1].split('&')
                preferred_charsets = ds_init[0x3355, 0x1003].value.split('::')[-1]
            else:
                req_types = []
                if self.web_sc['@create']['selected'] == 'On':
                    req_types.append('Secondary-Capture')
                if self.web_report and self.web_report['@create']['selected'] == 'On':
                    req_types.append('Report')
                # if self.web_gsps['@create']['selected'] == 'On':
                #     req_types.append('GSPS')
                preferred_charsets = ''
        elif os.environ['AI_REQUEST_TAG'] == 'StudyComments':
            _, req_types, preferred_charsets = ds_init.StudyComments.split('::')[2::2]
            req_types = req_types.split('&')
            if 'GSPS' in req_types:
                # TODO 나중에 3D에서도 GSPS 적용할 수 있도록 수정
                raise NotImplementedError
        else:
            raise KeyError
        self.preferred_charsets = preferred_charsets.split('\\')[-1]
        print("req_types:", req_types)
        print("preferred_charsets:", self.preferred_charsets)

        referenced_series_sequence = []
        displayed_area_selection_sequence = []
        graphic_annotation_sequence = []
        VOI_LUT_sequence = []
        finding_sequences = []
        conversion_source_attributes_sequence = []

        self.generated_uuid = uuid.uuid4().int
        for series_uid in series_uids:
            series_paths = study_paths[series_uid]
            referenced_image_sequence = []
            finding_sequence = []
            ds_dict = {'path': series_paths[0].parent}
            for path in series_paths:
                ds = pydicom.dcmread(str(path), force=True)
                ds_dict[int(ds.InstanceNumber)] = ds

                referenced_image = get_referenced_image_dataset(ds)
                referenced_image_sequence.append(referenced_image)
                conversion_source_attributes_sequence.append(referenced_image)
                displayed_area_selection_sequence.append(get_displayed_area_selection(ds, [referenced_image]))
                VOI_LUT_sequence.append(get_VOI_LUT_dataset(ds, [referenced_image]))

                # finding_sequence = self.update_gsps(graphic_annotation_sequence, referenced_image, result_json)
                # self.add_logo_annotation(graphic_annotation_sequence, referenced_image, result_json)


            # finding_sequences += finding_sequence
            # referenced_series_sequence.append(get_referenced_series_dataset(series_uid, referenced_image_sequence))

            result_json = self.run_models(ds_dict)
            
            if 'Secondary-Capture' in req_types:
                self.make_sc_dcm(ds_dict, result_json, output_path)

            if 'Report' in req_types:
                report_array = result_json['ReportArray']
                ds_rp = make_dataset(ds, self.app_name, self.app_version, 'RP', self.generated_uuid, self.preferred_charsets)
                ds_rp.PixelData = report_array.tobytes()
                ds_rp.Rows = report_array.shape[0]
                ds_rp.Columns = report_array.shape[1]
                ds_rp.save_as(str(output_path / (ds_rp.SOPInstanceUID + '.dcm')))
                # print(str(output_path / (ds_rp.SOPInstanceUID + '.dcm')))

        if 'GSPS' in req_types:
            # TODO 나중에 3D에서도 GSPS 적용할 수 있도록 수정
            raise NotImplementedError

    def make_sc_dcm(self, ds_dict, result_json, output_path):
        # assert (type(result_json['InputArray']) == np.ndarray) or type(result_json['SCArray']) == np.ndarray

        instance_numbers = {}
        for i, graphic_annotation in enumerate(result_json["GraphicAnnotationSequence"]):
            objects = graphic_annotation['Objects']
            _ins = [obj['instance_number'] for obj in objects]
            for _in in _ins:
                if _in in instance_numbers:
                    instance_numbers[_in][i] = _ins.index(_in)
                else:
                    instance_numbers[_in] = {i: _ins.index(_in)}

        for instance_number in [key for key in ds_dict.keys() if type(key) == int]:
            ds = ds_dict[instance_number]
            referenced_image = get_referenced_image_dataset(ds)

            ds_sc = make_dataset(ds, self.app_name, self.app_version, 'SC', self.generated_uuid, self.preferred_charsets)
            ds_sc.WindowCenter = 128
            ds_sc.WindowWidth = 255
            if '@append_series_number' in self.web_sc:
                if self.web_sc['@append_series_number']['selected'] == 'On':
                    ds_sc.SeriesNumber = self.web_sc['series_number'] + str(ds.SeriesNumber)
                else:
                    ds_sc.SeriesNumber = self.web_sc['series_number']
            else:
                ds_sc.SeriesNumber = self.web_sc['series_number']

            objects = []
            if instance_number in instance_numbers:
                for i in instance_numbers[instance_number]:
                    objects.append(result_json["GraphicAnnotationSequence"][i]['Objects'][instance_numbers[instance_number][i]])
            ds_sc.PixelData = self.make_sc_array(ds, objects).tobytes()

            if len(objects):
                ds_sc.Rows = objects[0]['Height']
                ds_sc.Columns = objects[0]['Width']
            else:
                ds_sc.Rows = int(ds.Rows)
                ds_sc.Columns = int(ds.Columns)

            if len(result_json["GraphicAnnotationSequence"]):
                finding_sequence = []
                for i, graphic_annotation in enumerate(result_json["GraphicAnnotationSequence"]):
                    dataset = Dataset()
                    dataset.add_new((0x2021, 0x0010), 'LO', 'DEEPNOID')
                    dataset.add_new((0x2021, 0x1009), 'UT', json.dumps({
                        'Name': graphic_annotation['GraphicLayer'],
                        'Probability': float(graphic_annotation['Probability'])
                    }))
                    dataset.ReferencedImageSequence = [referenced_image]
                    finding_sequence.append(dataset)
                finding_sequence = sorted(finding_sequence, key=lambda seq: json.loads(seq[0x2021, 0x1009].value)['Probability'], reverse=True)
            else:
                dataset = Dataset()
                dataset.add_new((0x2021, 0x0010), 'LO', 'DEEPNOID')
                dataset.add_new((0x2021, 0x1009), 'UT', json.dumps({
                    'Name': 'No Finding',
                    'Probability': 0.0000
                }))
                dataset.ReferencedImageSequence = [referenced_image]
                finding_sequence = [dataset]

            set_private_tags(ds_sc, finding_sequence, self.app_name, self.app_version)
            if type(result_json['ImageComments']) == str:
                ds_sc.ImageComments = result_json['ImageComments']
            ds_sc.save_as(str(output_path / (ds_sc.SOPInstanceUID + '.dcm')))
            # print(str(output_path / (ds_sc.SOPInstanceUID + '.dcm')))

    def update_gsps(self, *args, **kwargs):
        raise NotImplementedError

    def make_sc_array(self, ds, objects):
        if len(objects):
            height = objects[0]['Height']
            width = objects[0]['Width']
        else:
            height = int(ds.Rows)
            width = int(ds.Columns)
        mfont = ImageFont.truetype(os.path.join(medical_image.__path__, 'fonts/NotoSansCJKkr-Medium.otf'), int(height / 50))
        # image = Image.fromarray(objects['InputArray'].astype(np.uint8))
        image = Image.fromarray(read_dcm(ds).astype(np.uint8))
        img_editable = ImageDraw.Draw(image)

        ## Logo
        logo_w = self.logo.size[0] * height * 0.55 / 2472
        logo_h = self.logo.size[1] * height * 0.55 / 2472
        x = (width - logo_w) / 2
        y = height * 0.945 - logo_h
        paste_image(image, self.logo, x, y, logo_w, logo_h, 1)

        ## App Info
        msg = APP2PROUDCT[self.app_name]
        if self.write_version:
            msg += ' v.' + self.app_version
        mw, mh = mfont.getsize(msg)
        x = (width - mw) / 2
        y = height * 0.940 - logo_h - mh
        shadow = int(mh / 20)
        img_editable.text((x + shadow, y + shadow), msg, (167, 167, 167), mfont)
        img_editable.text((x, y), msg, (255, 255, 255), mfont)

        for obj in objects:
            ## GraphicObject
            contour_image_array = np.array(image)
            for graphic_object in obj['GraphicObjectSequence']:
                graphic_data = np.array(graphic_object['graphic_data'], np.int32)
                line_color = graphic_object['color'] if 'color' in graphic_object else (255, 255, 255)
                if ('shadow_color' not in graphic_object) or (graphic_object['shadow_color'] != 'OFF'):
                    shadow_color = graphic_object['shadow_color'] if 'shadow_color' in graphic_object else (0, 0, 0)
                    contour_image_array = cv2.polylines(contour_image_array, [graphic_data], False, shadow_color, 10)
                contour_image_array = cv2.polylines(contour_image_array, [graphic_data], False, line_color, max(int(min(width, height) / 500), 1))

            image = Image.fromarray(contour_image_array)
            img_editable = ImageDraw.Draw(image)
            ## TextObject
            for text_object in obj['TextObjectSequence']:
                text_color = text_object['color'] if 'color' in text_object else (255, 255, 255)
                msg = text_object['text_data']
                x, y, _, _ = text_object['bbox']
                if ('shadow_color' not in text_object) or (text_object['shadow_color'] != 'OFF'):
                    mw, mh = mfont.getsize(msg)
                    shadow = int(mh / 20)
                    shadow_color = text_object['shadow_color'] if 'shadow_color' in text_object else (167, 167, 167)
                    img_editable.text((x + shadow, y + shadow), msg, shadow_color, mfont)
                img_editable.text((x, y), msg, text_color, mfont)

        return np.array(image)

    def add_logo_object(self, text_object_sequence, result_json):
        mfont = ImageFont.truetype(os.path.join(medical_image.__path__, 'fonts/NotoSansCJKkr-Medium.otf'), int(result_json['Height'] / 50))
        ## Logo
        msg = 'D E E P N O I D'
        w0, h0 = mfont.getsize(msg)
        x = (result_json['Width'] - w0) / 2
        y = result_json['Height'] * 0.945 - h0
        shadow = int(h0 / 20)
        text_style = get_text_style_dataset(
            color=[255, 255, 255],
            shadow_style='NORMAL',
            shadow_color=[255, 255, 255],
            shadow_offset=[shadow, shadow],
        )
        text_object_sequence.append(
            get_text_object_dataset(
                text_data=msg,
                text_style=text_style,
                bbox=[x, y, x + w0, y + h0]
            )
        )

        ## App
        msg = APP2PROUDCT[self.app_name]
        if self.write_version:
            msg += ' v.' + self.app_version
        w1, h1 = mfont.getsize(msg)
        x = (result_json['Width'] - w1) / 2
        y = result_json['Height'] * 0.940 - h0 - h1
        shadow = int(h1 / 20)
        text_style = get_text_style_dataset(
            color=[255, 255, 255],
            shadow_style='NORMAL',
            shadow_color=[255, 255, 255],
            shadow_offset=[shadow, shadow],
        )
        text_object_sequence.append(
            get_text_object_dataset(
                text_data=msg,
                text_style=text_style,
                bbox=[x, y, x + w1, y + h1]
            )
        )

    def add_logo_annotation(self, graphic_annotation_sequence, referenced_image, result_json):
        text_object_sequence = []
        self.add_logo_object(text_object_sequence, result_json)
        graphic_annotation = get_graphic_annotation_dataset(
            referenced_image_sequence=[referenced_image],
            layer_name='Logo',
            text_object_sequence=text_object_sequence,
            graphic_object_sequence=[]
        )
        graphic_annotation_sequence.append(graphic_annotation)

    def make_gsps_dcm(self, *args,**kwargs):
        raise NotImplementedError
