import unittest
import boto3

class TestGet(unittest.TestCase):

    dynamodb = boto3.resource('dynamodb', region_name='eu-west-1')
    table = dynamodb.Table('resume-challenge-counter')

    def test_table(self):
        """
        Check the dynamodb table exists
        """
        self.assertEqual(self.table.table_name, 'resume-challenge-counter')

    def test_table_empty(self):
        """
        Check whether dynamodb table is empty
        """
        items_number = self.table.item_count

        if items_number == 0:
            is_empty = True
        else:
            is_empty = False

        self.assertFalse(is_empty)

    def test_visitorsNumber_value(self):
        """
        Check the count value is valid
        """
        item = self.table.get_item(Key={"CounterId": "mainCounter"})

        #Get the counter value
        counter_value = int(item["Item"]["visitorsNumber"])

        if 0 <= counter_value:
            is_valid = True
        else:
            is_valid = False

        self.assertTrue(is_valid)

if __name__ == '__main__':
    unittest.main()


import io
import tarfile

from io_collection.keys.check_key import check_key
from io_collection.load.load_buffer import load_buffer
from io_collection.load.load_tar import load_tar
from io_collection.save.save_buffer import save_buffer

def save_tar(location: str, key: str, contents: list[str]) -> None:
    if check_key(location, key):
        existing_tar = load_tar(location, key)
    else:
        existing_tar = None

    with io.BytesIO() as buffer:
        with tarfile.open(fileobj=buffer, mode="w:xz") as tar:
            if existing_tar is not None:
                for member in existing_tar.getmembers():
                    tar.addfile(member, existing_tar.extractfile(member.name))

            for content_key in contents:
                content = load_buffer(location, content_key)
                info = tarfile.TarInfo(content_key.split("/")[-1])
                info.size = content.getbuffer().nbytes
                tar.addfile(info, fileobj=content)

        save_buffer(location, key, buffer)


from __future__ import annotations

from sqlalchemy.ext.asyncio import AsyncSession

from core import tools
from orm import PlayerModel

async def do_fold(session: AsyncSession, player: PlayerModel) -> int:
    to_fold = 0
    await tools.store.game_player_accessor.update_player(
        session=session,
        player_id=player.id,
        values={
            "is_folded": True,
        },
    )

    return to_fold


from argparse import ArgumentParser, Namespace
import itertools
import json
import math
import os
import os.path as osp
import shutil
import tempfile
from collections import defaultdict
from functools import lru_cache
from pathlib import Path
from typing import List, Tuple, Generator

import cv2
import mmcv
import numpy as np
import pandas
import pandas as pd
import torch
import torch.distributed as dist
from PIL import Image as PImage
from PIL.Image import Image
from dateutil.parser import parse
from matplotlib import pyplot as plt
from mmcv import DataLoader, Config
from mmcv.parallel import MMDataParallel, MMDistributedDataParallel
from mmcv.runner import get_dist_info, load_checkpoint
from pandas import DataFrame
from torch.nn import Sequential

from DeepScoresV2_s2anet.analyze_ensembles.draw_WBF_for_multi_model import BboxHelper, load_proposals
from DeepScoresV2_s2anet.analyze_ensembles.wbf_rotated_boxes import rotated_weighted_boxes_fusion
from DeepScoresV2_s2anet.omr_prototype_alignment import prototype_alignment, render
from mmdet.apis import init_dist
from mmdet.core import coco_eval, results2json, wrap_fp16_model, poly_to_rotated_box_single, bbox2result, \
    outputs_rotated_box_to_poly_np
from mmdet.core import rotated_box_to_poly_np
from mmdet.datasets import build_dataloader, build_dataset
from mmdet.models import build_detector

# models/dsv2_no_augment/DS_2022_04_26/epoch_2000.pth
# models/dsv2/DS_2022_04_26/epoch_2000.pth
# models/dsv2_finalize/DS_2022_05_27/epoch_200.pth
# models/dsv2_finalize_snp/DS_2022_05_27/epoch_250.pth
# models/dsv2hybrid/DS_2022_05_27/epoch_600.pth
# models/dsv2hybrid_finalize_snp/DS_2022_05_27/epoch_150.pth
# models/uda_aug/halfrez_crop_lr_d_0.001_pretrained/model_030.pth
# models/uda_aug/halfrez_crop_lr0.0025/model_140.pth
# models/uda_aug/halfrez_crop_lr_d_0.001_no-pretrained/model_030.pth
# models/uda_aug/halfrez_crop_lr0.0075/model_det_900.pth
# models/uda_aug/halfrez_crop_adam/epoch_200.pth
# models/uda_aug/imslp_04_11/model_030.pth
# models/ensemble_cycle_length_20

# models/uda_aug/halfrez_crop_lr0.0075/model_det_800.pth
# models/uda_aug/halfrez_crop_lr0.0075/model_det_300.pth
# models/uda_aug/halfrez_crop_lr0.0075/model_det_700.pth
# models/uda_aug/halfrez_crop_lr0.0075/model_det_600.pth
# models/uda_aug/halfrez_crop_lr0.0075/model_det_200.pth
# models/uda_aug/halfrez_crop_lr0.0075/model_det_400.pth
# models/uda_aug/halfrez_crop_lr0.0075/model_det_500.pth
# models/uda_aug/halfrez_crop_lr0.0075/model_det_100.pth

DEEPSCORES_TEST_SET = {
    'type': 'DeepScoresV2Dataset',
    'ann_file': 'data/deep_scores_dense/deepscores_test.json',
    'img_prefix': 'data/deep_scores_dense/images/',
    'pipeline': [
        {'type': 'LoadImageFromFile'},
        {
            'type': 'MultiScaleFlipAug',
            'img_scale': 0.5,
            'flip': False,
            'transforms': [
                {'type': 'RotatedResize', 'img_scale': 0.5, 'keep_ratio': True},
                {'type': 'RotatedRandomFlip'},
                {'type': 'Normalize', 'mean': [240, 240, 240], 'std': [57, 57, 57], 'to_rgb': False},
                {'type': 'Pad', 'size_divisor': 32},
                {'type': 'ImageToTensor', 'keys': ['img']},
                {'type': 'Collect', 'keys': ['img']}
            ]
        }
    ],
    'use_oriented_bboxes': True
}

IMSLP_TEST_SET = {
    'type': 'DeepScoresV2Dataset',
    'ann_file': 'data/deep_scores_dense/imslp_test.json',
    'img_prefix': 'data/deep_scores_dense/images/',
    'pipeline': [
        {'type': 'LoadImageFromFile'},
        {
            'type': 'MultiScaleFlipAug',
            'img_scale': 1.0,
            'flip': False,
            'transforms': [
                {'type': 'RotatedResize', 'img_scale': 1.0, 'keep_ratio': True},
                {'type': 'RotatedRandomFlip'},
                {'type': 'Normalize', 'mean': [240, 240, 240], 'std': [57, 57, 57], 'to_rgb': False},
                {'type': 'Pad', 'size_divisor': 32},
                {'type': 'ImageToTensor', 'keys': ['img']},
                {'type': 'Collect', 'keys': ['img']}
            ]
        }
    ],
    'use_oriented_bboxes': True
}
TEST_SETS = [DEEPSCORES_TEST_SET, IMSLP_TEST_SET]

class_names = (
    'brace', 'ledgerLine', 'repeatDot', 'segno', 'coda', 'clefG', 'clefCAlto', 'clefCTenor', 'clefF',
    'clefUnpitchedPercussion', 'clef8', 'clef15', 'timeSig0', 'timeSig1', 'timeSig2', 'timeSig3', 'timeSig4',
    'timeSig5', 'timeSig6', 'timeSig7', 'timeSig8', 'timeSig9', 'timeSigCommon', 'timeSigCutCommon',
    'noteheadBlackOnLine', 'noteheadBlackOnLineSmall', 'noteheadBlackInSpace', 'noteheadBlackInSpaceSmall',
    'noteheadHalfOnLine', 'noteheadHalfOnLineSmall', 'noteheadHalfInSpace', 'noteheadHalfInSpaceSmall',
    'noteheadWholeOnLine', 'noteheadWholeOnLineSmall', 'noteheadWholeInSpace', 'noteheadWholeInSpaceSmall',
    'noteheadDoubleWholeOnLine', 'noteheadDoubleWholeOnLineSmall', 'noteheadDoubleWholeInSpace',
    'noteheadDoubleWholeInSpaceSmall', 'augmentationDot', 'stem', 'tremolo1', 'tremolo2', 'tremolo3', 'tremolo4',
    'tremolo5', 'flag8thUp', 'flag8thUpSmall', 'flag16thUp', 'flag32ndUp', 'flag64thUp', 'flag128thUp', 'flag8thDown',
    'flag8thDownSmall', 'flag16thDown', 'flag32ndDown', 'flag64thDown', 'flag128thDown', 'accidentalFlat',
    'accidentalFlatSmall', 'accidentalNatural', 'accidentalNaturalSmall', 'accidentalSharp', 'accidentalSharpSmall',
    'accidentalDoubleSharp', 'accidentalDoubleFlat', 'keyFlat', 'keyNatural', 'keySharp', 'articAccentAbove',
    'articAccentBelow', 'articStaccatoAbove', 'articStaccatoBelow', 'articTenutoAbove', 'articTenutoBelow',
    'articStaccatissimoAbove', 'articStaccatissimoBelow', 'articMarcatoAbove', 'articMarcatoBelow', 'fermataAbove',
    'fermataBelow', 'caesura', 'restDoubleWhole', 'restWhole', 'restHalf', 'restQuarter', 'rest8th', 'rest16th',
    'rest32nd', 'rest64th', 'rest128th', 'restHNr', 'dynamicP', 'dynamicM', 'dynamicF', 'dynamicS', 'dynamicZ',
    'dynamicR', 'graceNoteAcciaccaturaStemUp', 'graceNoteAppoggiaturaStemUp', 'graceNoteAcciaccaturaStemDown',
    'graceNoteAppoggiaturaStemDown', 'ornamentTrill', 'ornamentTurn', 'ornamentTurnInverted', 'ornamentMordent',
    'stringsDownBow', 'stringsUpBow', 'arpeggiato', 'keyboardPedalPed', 'keyboardPedalUp', 'tuplet3', 'tuplet6',
    'fingering0', 'fingering1', 'fingering2', 'fingering3', 'fingering4', 'fingering5', 'slur', 'beam', 'tie',
    'restHBar', 'dynamicCrescendoHairpin', 'dynamicDiminuendoHairpin', 'tuplet1', 'tuplet2', 'tuplet4', 'tuplet5',
    'tuplet7', 'tuplet8', 'tuplet9', 'tupletBracket', 'staff', 'ottavaBracket'
)

GREEN = 32
YELLOW = 33
BLUE = 34
RED = 31

def msg(s: str, level: int, color: int):
    if level == 0:
        print(f"\033[1;{color}m=>\033[m {s}\033[m")
    else:
        print(f"  \033[1;{color}m{'-' * level}>\033[m {s}\033[m")

def msg1(s: str):
    msg(s, 0, GREEN)

def msg2(s: str):
    msg(s, 1, BLUE)

def msg3(s: str):
    msg(s, 2, BLUE)

def msg4(s: str):
    msg(s, 3, BLUE)

WARN = f"\033[1;{YELLOW}m"
def warn(s: str, level: int):
    msg(f"{WARN}{s}\033[m", level, YELLOW)

def warn1(s: str):
    warn(s, 0)

def warn2(s: str):
    warn(s, 1)

def warn3(s: str):
    warn(s, 2)

def warn4(s: str):
    warn(s, 3)

ERR = f"\033[1;{RED}m"
def err(s: str, level: int):
    msg(f"{ERR}{s}\033[m", level, RED)

def err1(s: str):
    err(s, 0)

def err2(s: str):
    err(s, 1)

def err3(s: str):
    err(s, 2)

def err4(s: str):
    err(s, 3)

def _postprocess_bboxes(img, boxes, labels):
    img = PImage.fromarray(img)
    proposal_list = [{'proposal': np.append(box[:5], class_names[int(label) + 1])} for box, label in zip(boxes, labels)]
    processed_proposals = prototype_alignment._process_single(img, proposal_list,
                                                              whitelist=["key", "clef", "accidental", "notehead"])
    new_boxes = np.zeros(boxes.shape)
    new_boxes[..., :5] = np.stack(processed_proposals)
    if new_boxes.shape[1] == 6:
        # copy scores
        new_boxes[..., 5] = boxes[..., 5]
    return new_boxes

def _post_process_bbox_list(img, bbox_list, cfg):
    img = img.cpu().numpy().astype("uint8")
    img = img.transpose(1, 2, 0)
    scale = 1 / cfg['test_pipeline'][1]['img_scale']
    img = cv2.resize(img, dsize=(int(img.shape[1] * scale), int(img.shape[0] * scale)))
    boxes = bbox_list[0][0].cpu().numpy()
    labels = bbox_list[0][1].cpu().numpy()

    boxes_new = _postprocess_bboxes(img, boxes, labels)
    boxes_new = torch.from_numpy(boxes_new)
    return boxes_new

def round_results(result):
    result[:, :4] = torch.round(result[:, :4])
    result[:, 5] = torch.round(result[:, 5] * 1000) / 1000
    return result

def single_gpu_test(model, data_loader, show=False, cfg=None, post_process=False, round_=False):
    model.eval()
    results = []
    dataset = data_loader.dataset
    prog_bar = mmcv.ProgressBar(len(dataset))
    for i, data in enumerate(data_loader):
        with torch.no_grad():
            result, bbox_list = model(return_loss=False, rescale=not show, **data)

        if post_process:
            img = data['img'][0][0]
            boxes = _post_process_bbox_list(img, bbox_list, cfg)
        else:
            boxes = bbox_list[0][0]

        if round_:
            boxes = round_results(boxes)

        result = bbox2result(boxes, bbox_list[0][1], num_classes=cfg['model']['bbox_head']['num_classes'])

        results.append(result)
        if show:
            print("asdf")
            # for nr, sub_list in enumerate(bbox_list):
            #    bbox_list[nr] = [rotated_box_to_poly_np(sub_list[0].cpu().numpy()), sub_list[1].cpu().numpy()]

            model.module.show_result(data, result, show=show, dataset=dataset.CLASSES,
                                     bbox_transform=rotated_box_to_poly_np, score_thr=cfg.test_cfg['score_thr'])
            # typo in bbox_transorm -> bbox_transform?

        batch_size = data['img'][0].size(0)
        for _ in range(batch_size):
            prog_bar.update()
    return results

def multi_gpu_test(model, data_loader, tmpdir=None, cfg=None, post_process=False, round_=False):
    model.eval()
    results = []
    dataset = data_loader.dataset
    rank, world_size = get_dist_info()
    if rank == 0:
        prog_bar = mmcv.ProgressBar(len(dataset))
    for i, data in enumerate(data_loader):
        with torch.no_grad():
            result, bbox_list = model(return_loss=False, rescale=True, **data)

        if post_process:
            img = data['img'][0][0]
            boxes = _post_process_bbox_list(img, bbox_list, cfg)
        else:
            boxes = bbox_list[0][0]

        if round_:
            boxes = round_results(boxes)

        result = bbox2result(boxes, bbox_list[0][1], num_classes=cfg['model']['bbox_head']['num_classes'])

        results.append(result)

        if rank == 0:
            batch_size = data['img'][0].size(0)
            for _ in range(batch_size * world_size):
                prog_bar.update()

    # collect results from all ranks
    results = collect_results(results, len(dataset), tmpdir)

    return results

def collect_results(result_part, size, tmpdir=None):
    rank, world_size = get_dist_info()
    # create a tmp dir if it is not specified
    if tmpdir is None:
        MAX_LEN = 512
        # 32 is whitespace
        dir_tensor = torch.full((MAX_LEN,),
                                32,
                                dtype=torch.uint8,
                                device='cuda')
        if rank == 0:
            tmpdir = tempfile.mkdtemp()
            tmpdir = torch.tensor(
                bytearray(tmpdir.encode()), dtype=torch.uint8, device='cuda')
            dir_tensor[:len(tmpdir)] = tmpdir
        dist.broadcast(dir_tensor, 0)
        tmpdir = dir_tensor.cpu().numpy().tobytes().decode().rstrip()
    else:
        mmcv.mkdir_or_exist(tmpdir)
    # dump the part result to the dir
    mmcv.dump(result_part, osp.join(tmpdir, 'part_{}.pkl'.format(rank)))
    dist.barrier()
    # collect all parts
    if rank != 0:
        return None
    else:
        # load results of all parts from tmp dir
        part_list = []
        for i in range(world_size):
            part_file = osp.join(tmpdir, 'part_{}.pkl'.format(i))
            part_list.append(mmcv.load(part_file))
        # sort the results
        ordered_results = []
        for res in zip(*part_list):
            ordered_results.extend(list(res))
        # the dataloader may pad some samples
        ordered_results = ordered_results[:size]
        # remove tmp dir
        shutil.rmtree(tmpdir)
        return ordered_results

def parse_args():
    parser = ArgumentParser(description='MMDet test detector')
    parser.add_argument('config', help='test config file path')
    parser.add_argument('--checkpoints', nargs='+',
                        help='checkpoint files', required=True)
    parser.add_argument('--out', help='output result file', default='eval.pkl')
    parser.add_argument(
        '--json_out',
        help='output result file name without extension',
        type=str)
    parser.add_argument(
        '--eval',
        type=str,
        nargs='+',
        choices=['proposal', 'proposal_fast', 'bbox', 'segm', 'keypoints'],
        help='eval types')
    parser.add_argument('--show', action='store_true', help='show results')
    parser.add_argument('--tmpdir', help='tmp dir for writing some results')
    parser.add_argument(
        '--launcher',
        choices=['none', 'pytorch', 'slurm', 'mpi'],
        default='none',
        help='job launcher')
    parser.add_argument('--local_rank', type=int, default=0)
    # add dataset type for more dataset eval other than coco
    parser.add_argument(
        '--data',
        choices=['coco', 'dota', 'dota_large', 'dota_hbb', 'hrsc2016', 'voc', 'dota_1024', 'dsv2'],
        default='dota',
        type=str,
        help='eval dataset type')
    parser.add_argument(
        '--cache', '-c',
        action='store_true',
        default=False,
        help='Use cached results/metrics/evaluations instead of recalculating'
    )
    parser.add_argument(
        '--postprocess', '-p',
        action='store_true',
        default=False,
        help='post-process the results'
    )
    parser.add_argument(
        '--round', '-r',
        action='store_true',
        default=False,
        help='round the results (similar to detection service)'
    )
    args = parser.parse_args()
    if 'LOCAL_RANK' not in os.environ:
        os.environ['LOCAL_RANK'] = str(args.local_rank)
    return args

def config_from_str(cfg_str: str, path: Path = None) -> Config:
    if path is None:
        path = Path(cfg_str.splitlines()[0])
    if cfg_str.startswith(str(path)):
        cfg_str = cfg_str[len(str(path)) + 1:]
    with tempfile.NamedTemporaryFile('w', suffix='.py') as fp:
        fp.write(cfg_str)
        fp.seek(0)
        config = Config.fromfile(fp.name)
        cfg_txt = str(config.text).replace(fp.name + "\n", '')
    return Config(getattr(config, '_cfg_dict'), cfg_txt, str(path))

@lru_cache()
def get_test_set(test_config: str) -> DataLoader:
    cfg = json.loads(test_config)
    msg2(f"Loading \033[1m{Path(cfg['ann_file']).name}\033[m ({cfg['type']})")
    return build_dataset(cfg)

def get_test_sets(*test_configs: Config, workers_per_gpu: int = 4, imgs_per_gpu: int = 1, distributed: bool = False) -> List[DataLoader]:
    data_loaders = []
    for test_config in test_configs:
        data_loaders.append(build_dataloader(
            get_test_set(json.dumps(test_config)),
            imgs_per_gpu=imgs_per_gpu,
            workers_per_gpu=workers_per_gpu,
            dist=distributed,
            shuffle=False
        ))
    return data_loaders

def create_plots(stats: DataFrame, dataset_names: List[str], overlap: np.float, folder: Path):
    print('=' * 30)
    CLASSES = {
        'clefs': {'clefG', 'clefCAlto', 'clefCTenor', 'clefF', 'clef8', 'clef15'},
        'noteheads': {'noteheadBlackOnLine', 'noteheadBlackInSpace', 'noteheadHalfOnLine', 'noteheadHalfInSpace',
                      'noteheadWholeOnLine', 'noteheadWholeInSpace', 'noteheadDoubleWholeOnLine',
                      'noteheadDoubleWholeInSpace'},
        'accidentals': {'accidentalFlat', 'accidentalNatural', 'accidentalSharp', 'accidentalDoubleSharp',
                        'accidentalDoubleFlat'},
        'keys': {'keyFlat', 'keyNatural', 'keySharp'},
        'rests': {'restDoubleWhole', 'restWhole', 'restHalf', 'restQuarter', 'rest8th', 'rest16th', 'rest32nd',
                  'rest64th', 'rest128th'},
        'beams': {'beam'},
        'all classes': set(class_names)
    }
    n_datasets = len(dataset_names)
    chkpnt_names = [s.split(' - ')[0] for s in stats.index[::n_datasets]]
    for name, classes in CLASSES.items():
        im_fp = folder / f'AP_{name.replace(" ", "_")}_{overlap:.2f}.png'
        im_fp.unlink(missing_ok=True)
        msg3(f"Plotting \033[1m{name}\033[m")
        sub_stats = stats[list(classes)]
        all_aps = sub_stats.to_numpy()
        # Only use columns where there is no NaN values
        non_nan_aps = all_aps.T[~np.isnan(all_aps.sum(axis=0))].T
        if non_nan_aps.shape[1] == 0:
            err4("No values to compare")
            continue
        mean_aps = non_nan_aps.mean(axis=1).reshape((len(chkpnt_names), n_datasets))
        fig, ax = plt.subplots(figsize=(15, 9))
        X = np.arange(len(chkpnt_names))
        incr = 0.4
        center_offset = (incr * (len(dataset_names) - 1)) / 2
        for i, (ds_name, col, mean_ap) in enumerate(
                zip(dataset_names, itertools.cycle(['b', 'r', 'g', 'y', 'c', 'm']), mean_aps.T)):
            r = ax.bar(X + incr * i - center_offset, mean_ap, color=col, width=incr,
                       label=f'{ds_name} ({stats["samples"][i]} samples)')
            ax.bar_label(r, padding=3)
        ax.set_ylabel('AP')
        ax.set_title(f'AP of {name} by model and training set (overlap = {overlap:.2f})')
        plt.xticks(X, chkpnt_names, rotation=10, horizontalalignment='right', fontsize='small')
        ax.legend()
        fig.tight_layout()
        plt.savefig(im_fp)
        # msg3(f'Saved plot to \033[1m{str(im_fp)}\033[m')
        # plt.show()
        plt.close(fig)

def compile_stats(stats: dict, overlap: np.float, index: list) -> DataFrame:
    overlap_stats = {}
    for cls, overlap_aps in stats.items():
        if cls == 'samples':
            overlap_stats['samples'] = stats['samples']
        else:
            overlap_stats[cls] = []
            for overlap_ap in overlap_aps:
                overlap_stats[cls].append(overlap_ap.get(overlap, np.NaN))
    return DataFrame(overlap_stats, index=index)

def plot_stats(overlaps: np.ndarray, folder: Path, stats: dict, index: list, dataset_names: list):
    for overlap in overlaps:
        msg2(f"Processing stats for overlap = \033[1m{overlap:.2f}\033[m")
        eval_fp = folder / f'eval_{overlap:.2f}.csv'
        stat_df = compile_stats(stats, overlap, index)
        msg2(f"Saving stats to \033[1m{eval_fp}\033[m")
        stat_df.to_csv(eval_fp)
        create_plots(stat_df, dataset_names, overlap, folder)

def from_checkpoint(checkpoint_file: Path, cfg: Config) -> Tuple[str, dict, Sequential]:
    model = build_detector(cfg.model, train_cfg=None, test_cfg=cfg.test_cfg)
    fp16_cfg = cfg.get('fp16', None)
    if fp16_cfg is not None:
        wrap_fp16_model(model)

    try:
        checkpoint = load_checkpoint(model, str(checkpoint_file), map_location='cpu')
    except RuntimeError as e:
        err2(f"!!! Failed loading checkpoint \033[1m{str(checkpoint_file)}\033[m{ERR}: {e}")
        return

    if 'config' in checkpoint['meta'].keys():
        cfg_str = checkpoint['meta']['config']
        chkp_cfg = config_from_str(cfg_str)
        config_file = Path(chkp_cfg.filename)
        parents = [config_file.name]
        for parent in config_file.parents:
            parents.append(parent.name)
            if parent.name == 'configs':
                break
            if parent.name == '/':
                raise Exception("Did not find configs dir")
        config_file = Path(*reversed(parents))
        msg3(
            f"Original config: \033[1m{config_file.name}\033[m (epoch: \033[1m{checkpoint['meta']['epoch']}\033[m)")
        checkpoint_id = config_file.name
    else:
        warn3("No original config found")
        checkpoint_id = f"{checkpoint_file.parent.parent.name}_{checkpoint_file.parent.name}"
    return checkpoint_id, checkpoint, model

def preprocess_checkpoints(checkpoint_list: List[str], cfg: Config) -> dict:
    msg1("Preprocessing checkpoints")
    checkpoints = {}
    for checkpoint_file in map(Path, checkpoint_list):
        if not checkpoint_file.exists():
            warn2(f"!!! Checkpoint file \033[1m{str(checkpoint_file)}\033[m{WARN} does not exist")
            continue
        if checkpoint_file.is_file():
            msg2(f"Pre-loading checkpoint \033[1m{str(checkpoint_file)}\033[m")
            checkpoint_id, checkpoint, model = from_checkpoint(checkpoint_file, cfg)

            other_ckpnt_file, other_ckpnt, other_model = checkpoints.get(checkpoint_id, (None, None, None))
            if other_ckpnt is not None:
                # Select the model of the same config with the highest epoch
                if int(checkpoint['meta']['epoch']) >= int(other_ckpnt['meta']['epoch']):
                    warn4(
                        f"Replaces model with same config: epoch \033[1m{other_ckpnt['meta']['epoch']}\033[m >= \033[1m{checkpoint['meta']['epoch']}\033[m")
                else:
                    checkpoint_file, checkpoint, model = other_ckpnt_file, other_ckpnt, other_model
                    warn4(
                        f"Ignored model with same config: epoch\033[1m {other_ckpnt['meta']['epoch']}\033[m <= \033[1m{checkpoint['meta']['epoch']}\033[m")
            checkpoints[checkpoint_id] = checkpoint_file, checkpoint, model
        elif checkpoint_file.is_dir():
            # Multimodel
            msg2(f"Pre-loading multi-models from \033[1m{str(checkpoint_file)}\033[m")
            checkpoint_id = checkpoint_file.name
            checkpoint_data = []
            for multi_checkpoint_file in checkpoint_file.glob('*.pth'):
                checkpoint_sub_id, checkpoint, model = from_checkpoint(multi_checkpoint_file, cfg)
                checkpoint_data.append((checkpoint_sub_id, multi_checkpoint_file, checkpoint, model))
            checkpoints[checkpoint_id] = checkpoint_data
        else:
            raise Exception(f"Checkpoint {str(checkpoint_file)} is neither a file nor a folder")
    return checkpoints

def prepare_folder(checkpoint_id: str, checkpoint, checkpoint_file: Path, out_folder: Path) -> Tuple[str, Path, dict, list]:
    cfg_str, config_file = None, None
    kwargs = {'workers_per_gpu': 4}
    if 'config' in checkpoint['meta'].keys():
        cfg_str = checkpoint['meta']['config']
        chkp_cfg = config_from_str(cfg_str)
        kwargs['workers_per_gpu'] = chkp_cfg.data.workers_per_gpu
        config_file = Path(chkp_cfg.filename)
        assert config_file.suffix == '.py'
        epoch = checkpoint['meta']['epoch']
        time = parse(checkpoint['meta']['time'])
        print('#' * 30)
        msg1(
            f"Loaded checkpoint \033[1m{checkpoint_file.name}\033[m (\033[1m{epoch}\033[m epochs, created: {str(time)})")
        checkpoint_id = f"{config_file.stem}_epoch_{epoch}"
    else:
        msg1(f"Loaded checkpoint \033[1m{checkpoint_file.name}\033[m")
        suffix = checkpoint_file.stem.split('_')[-1]
        epoch_str = ''
        if suffix.isdigit():
            epoch = int(suffix)
            warn2(f"Assuming epoch to be {epoch}")
            epoch_str = f"_epoch_{epoch}"
        checkpoint_id = checkpoint_id + epoch_str

    chkp_folder = out_folder / checkpoint_id
    chkp_folder.mkdir(parents=True, exist_ok=True)
    if cfg_str is not None and config_file is not None:
        with open(chkp_folder / config_file.name, 'w') as fp:
            fp.write(f"# {cfg_str}")
        msg2(f"Extracted original configuration to \033[1m{config_file.name}\033[m")

    test_sets = []
    for test_set in TEST_SETS:
        new_test_set = test_set.copy()
        if chkp_cfg is not None:
            new_test_set['type'] = chkp_cfg.data.test.type
        test_sets.append(new_test_set)

    # Link original checkpoint
    new_chkpnt = chkp_folder / checkpoint_file.name
    if new_chkpnt.is_symlink():
        new_chkpnt.unlink()
    new_chkpnt.symlink_to(os.path.relpath(checkpoint_file, new_chkpnt.parent))

    return checkpoint_id, chkp_folder, kwargs, test_sets

def save_proposal_stats(proposals: dict, prop_stat_fp: Path, data_loader: DataLoader):
    prop_stats = {}
    for proposal in proposals['proposals']:
        cat_id = int(proposal['cat_id'])
        cat = data_loader.dataset.CLASSES[cat_id - 1]
        x, y, w, h, a = poly_to_rotated_box_single(list(map(float, proposal['bbox'])))
        a *= 180.0 / math.pi
        prop_stats[cat] = prop_stats.get(cat, []) + [a]
    prop_data = {}
    for i, cat in enumerate(data_loader.dataset.CLASSES):
        angles = prop_stats.get(cat, [])
        prop_data[cat] = (len(angles), np.mean(angles), np.std(angles))
        # print(f'[{i + 1}] {cat} ({len(angles)}): avg:{np.mean(angles):.02f}, std: {np.std(angles):.02f}')
    csv_data = pandas.DataFrame(prop_data, index=('occurrences', 'avg', 'std')).transpose()
    csv_data.to_csv(prop_stat_fp)

def get_proposals(checkpoint: dict, cfg: Config, model: Sequential, data_loader: DataLoader, proposals_fp: Path, args: Namespace) -> list:
    # old versions did not save class info in checkpoints, this workaround is
    # for backward compatibility
    if 'CLASSES' in checkpoint['meta']:
        model.CLASSES = checkpoint['meta']['CLASSES']
    else:
        model.CLASSES = data_loader.dataset.CLASSES

    if not proposals_fp.exists() or not args.cache:
        proposals_fp.parent.mkdir(exist_ok=True)
        msg3(f"Testing model on \033[1m{Path(data_loader.dataset.ann_file).stem}\033[m")
        distributed = args.launcher != 'none'
        if not distributed:
            model = MMDataParallel(model, device_ids=[0])
            output = single_gpu_test(model, data_loader, args.show, cfg, args.postprocess, args.round)
        else:
            model = MMDistributedDataParallel(model.cuda())
            output = multi_gpu_test(model, data_loader, args.tmpdir, cfg, args.postprocess, args.round)
        print()  # The tests use ncurses and don't append a new line at the end

        msg3(f'Writing proposals to \033[1m{str(proposals_fp)}\033[m')
        mmcv.dump(output, proposals_fp)
        save_predictions(output, proposals_fp.with_name(args.json_out), data_loader, args)
    else:
        msg3(f'Reading proposals from \033[1m{str(proposals_fp)}\033[m')
        output = mmcv.load(proposals_fp)
    return output

def save_predictions(predictions, result_file: Path, data_loader: DataLoader, args):
    # Save predictions in the COCO json format
    rank, _ = get_dist_info()
    if args.json_out and rank == 0:
        #if not result_file.with_suffix('.bbox.json').exists() or not args.cache:
        msg3(f"Saving predictions to \033[1m{str(result_file.with_suffix('.*'))}\033[m")
        if not isinstance(predictions[0], dict):
            result_file = result_file.with_suffix('')
            if not result_file.with_suffix('.bbox.json').exists() or not args.cache:
                results2json(data_loader.dataset, predictions, result_file)
            result_file = result_file.with_suffix('.bbox.json')  # function sets custom suffix
        else:
            for name in predictions[0]:
                result_file = result_file.with_suffix(f'.{name}{result_file.suffix}')
                outputs_ = [out[name] for out in predictions]
                if not result_file.exists() or not args.cache:
                    results2json(data_loader.dataset, outputs_, result_file)
        standard_results_fp = result_file.with_name(args.json_out)
        if not standard_results_fp.exists() or not args.cache:
            data_loader.dataset.write_results_json(outputs_rotated_box_to_poly_np(predictions), filename=str(standard_results_fp))

def evaluate_proposals(proposals, cfg: Config, checkpoint_file: Path, overlaps: np.ndarray, data_loader: DataLoader, result_folder: Path, args) -> list:
    eval_types = args.eval
    data_name = args.data
    ensure_8_tuple(proposals)
    if data_name == 'coco':
        if eval_types:
            if eval_types == ['proposal_fast']:
                result_file = args.out
                coco_eval(result_file, eval_types, data_loader.dataset.coco)
            else:
                if not isinstance(proposals[0], dict):
                    result_files = results2json(data_loader.dataset, proposals, args.out)
                    coco_eval(result_files, eval_types, data_loader.dataset.coco)
                else:
                    for name in proposals[0]:
                        proposals = [out[name] for out in proposals]
                        result_file = args.out + '.{}'.format(name)
                        result_files = results2json(data_loader.dataset, proposals, result_file)
                        coco_eval(result_files, eval_types, data_loader.dataset.coco)
    elif data_name in ['dota', 'hrsc2016']:
        eval_kwargs = cfg.get('evaluation', {}).copy()
        work_dir = osp.dirname(args.out)
        metrics = data_loader.dataset.evaluate(proposals, work_dir=work_dir, **eval_kwargs)
    elif data_name in ['dsv2']:
        from mmdet.core import outputs_rotated_box_to_poly_np

        for page in proposals:
            page.insert(0, np.array([]))

        proposals = outputs_rotated_box_to_poly_np(proposals)

        metrics_fp = result_folder / "dsv2_metrics.pkl"
        result_file = None
        if args.json_out is not None:
            result_file = result_folder / args.json_out
        needs_to_be_exported = False
        if not metrics_fp.exists() or not args.cache:
            msg3(
                f"Evaluating: \033[1m{str(checkpoint_file)}\033[m on \033[1m{data_loader.dataset.ann_file}\033[m in \033[1m{str(result_folder)}\033[m")
            data_loader.dataset.evaluate(
                proposals,
                result_json_filename=str(result_file) if result_file is not None else None,
                work_dir=str(result_folder),
                iou_thrs=overlaps
            )  # Extremely slow...
            needs_to_be_exported = True
        msg3(f'Reading calculated metrics from \033[1m{str(metrics_fp)}\033[m')
        metrics = mmcv.load(metrics_fp)

        prop_stat_fp = result_folder / 'proposal_stats.csv'
        if (result_file is not None and not prop_stat_fp.exists()) or needs_to_be_exported:
            msg3(f"Calculating statistics for results -> {prop_stat_fp.name}")
            with open(result_file, 'r') as fp:
                save_proposal_stats(json.load(fp), prop_stat_fp, data_loader)
    return metrics

def eval_checkpoint(
        cfg,
        model,
        checkpoint_id: str,
        checkpoint_file: Path,
        checkpoint: dict,
        out_folder: Path,
        proposals_fp: Path,
        overlaps: np.ndarray,
        stats: dict,
        index: list,
        dataset_names: list,
        outputs_m: dict,
        metrics: dict,
        distributed: bool,
        args
):
    sub_index = []
    overlaps_str = str(overlaps).replace("\n", "")
    checkpoint_file = Path(checkpoint_file)
    chkp_cfg = None
    checkpoint_id, chkp_folder = prepare_folder(checkpoint_id, checkpoint, checkpoint_file, out_folder)

    new_chkpnt = chkp_folder / checkpoint_file.name
    if new_chkpnt.is_symlink():
        new_chkpnt.unlink()
    new_chkpnt.symlink_to(os.path.relpath(checkpoint_file, new_chkpnt.parent))

    test_sets = []
    workers_per_gpu = 4
    if chkp_cfg is not None:
        workers_per_gpu = chkp_cfg.data.workers_per_gpu
    for test_set in TEST_SETS:
        new_test_set = test_set.copy()
        if chkp_cfg is not None:
            new_test_set['type'] = chkp_cfg.data.test.type
        test_sets.append(new_test_set)

    sub_stats = defaultdict(list)
    for data_loader in get_test_sets(
            *test_sets,
            workers_per_gpu=workers_per_gpu,
            distributed=distributed):
        sub_stats['samples'].append(len(data_loader.dataset.img_ids))
        stats['samples'].append(len(data_loader.dataset.img_ids))
        ann_file = Path(data_loader.dataset.ann_file)
        msg2(f"Selecting dataset: \033[1m{ann_file.stem}\033[m")
        sub_index.append(f"{checkpoint_id} - {ann_file.stem}")
        result_folder = chkp_folder / ann_file.stem
        result_folder.mkdir(exist_ok=True)

        outputs_m[checkpoint_file] = {}
        outputs_m[checkpoint_file][data_loader] = get_proposals(checkpoint, cfg, model, data_loader, result_folder / proposals_fp, distributed, args)

        metrics = evaluate_proposals(outputs_m[checkpoint_file][data_loader], cfg, checkpoint_file, overlaps, data_loader, result_folder, args)
        eval_types = args.eval
        data_name = args.data
        if data_name == 'coco':
            if eval_types:
                if eval_types == ['proposal_fast']:
                    result_file = args.out
                    coco_eval(result_file, eval_types, data_loader.dataset.coco)
                else:
                    if not isinstance(outputs_m[checkpoint_file][data_loader][0], dict):
                        result_files = results2json(data_loader.dataset, outputs_m[checkpoint_file][data_loader],
                                                    args.out)
                        coco_eval(result_files, eval_types, data_loader.dataset.coco)
                    else:
                        for name in outputs_m[checkpoint_file][data_loader][0]:
                            outputs_m[checkpoint_file][data_loader] = [out[name] for out in
                                                                       outputs_m[checkpoint_file][data_loader]]
                            result_file = args.out + '.{}'.format(name)
                            result_files = results2json(data_loader.dataset, outputs_m[checkpoint_file][data_loader],
                                                        result_file)
                            coco_eval(result_files, eval_types, data_loader.dataset.coco)
        elif data_name in ['dota', 'hrsc2016']:
            eval_kwargs = cfg.get('evaluation', {}).copy()
            work_dir = osp.dirname(args.out)
            data_loader.dataset.evaluate(outputs_m[checkpoint_file][data_loader], work_dir=work_dir, **eval_kwargs)
        elif data_name in ['dsv2']:
            from mmdet.core import outputs_rotated_box_to_poly_np

            for page in outputs_m[checkpoint_file][data_loader]:
                page.insert(0, np.array([]))

            outputs_m[checkpoint_file][data_loader] = outputs_rotated_box_to_poly_np(
                outputs_m[checkpoint_file][data_loader])

        metrics_fp = result_folder / "dsv2_metrics.pkl"
        result_file = None
        if args.json_out is not None:
            result_file = result_folder / args.json_out
        needs_to_be_exported = False
        if not metrics_fp.exists() or not args.cache:
            msg3(
                f"Evaluating: \033[1m{str(checkpoint_file)}\033[m on \033[1m{data_loader.dataset.ann_file}\033[m in \033[1m{str(result_folder)}\033[m")
            data_loader.dataset.evaluate(
                outputs_m[checkpoint_file][data_loader],
                result_json_filename=str(result_file) if result_file is not None else None,
                work_dir=str(result_folder),
                iou_thrs=overlaps
            )  # Extremely slow...
            needs_to_be_exported = True
        msg3(f'Reading calculated metrics from \033[1m{str(metrics_fp)}\033[m')
        metrics = mmcv.load(metrics_fp)

        prop_stat_fp = result_folder / 'proposal_stats.csv'
        if (result_file is not None and not prop_stat_fp.exists()) or needs_to_be_exported:
            msg3(f"Calculating statistics for results -> {prop_stat_fp.name}")
            with open(result_file, 'r') as fp:
                save_proposal_stats(json.load(fp), prop_stat_fp, data_loader)

        msg3(f'Compiling metrics with overlaps \033[1m{overlaps_str}\033[m')
        for cls, overlap_metrics in metrics.items():
            for overlap in overlaps:
                overlap_metrics[overlap] = overlap_metrics[overlap].get('ap', np.NaN)
            metrics[cls] = overlap_metrics
        for cls in data_loader.dataset.CLASSES:
            stats[cls].append(metrics.get(cls, {}))
        for cls, metrics in stats.items():
            sub_stats[cls].append(metrics[-1])
            stats[cls].append(metrics[-1])

        save_predictions(outputs_m[checkpoint_file][data_loader], result_folder / args.json_out, data_loader, args)
    plot_stats(overlaps, chkp_folder, sub_stats, sub_index, dataset_names)
    index.extend(sub_index)  # We need the meta index for the meta plots (duh)

def evaluate_results(outputs: list, result_folder: Path, data_loader: DataLoader, checkpoint_id: str, overlaps: np.ndarray, stats: dict, args: Namespace):
    overlaps_str = str(overlaps).replace("\n", "")
    metrics_fp = result_folder / "dsv2_metrics.pkl"
    result_folder = result_folder / Path(data_loader.dataset.ann_file).stem
    result_file = None
    if args.json_out is not None:
        result_file = result_folder / args.json_out
    needs_to_be_exported = False
    if not metrics_fp.exists() or not args.cache:
        msg3(
            f"Evaluating: \033[1m{str(checkpoint_id)}\033[m on \033[1m{data_loader.dataset.ann_file}\033[m in \033[1m{str(result_folder)}\033[m")
        data_loader.dataset.evaluate(
            ensure_8_tuple(outputs),
            result_json_filename=str(result_file) if result_file is not None else None,
            work_dir=str(result_folder),
            iou_thrs=overlaps
        )  # Extremely slow...
        needs_to_be_exported = True
    msg3(f'Reading calculated metrics from \033[1m{str(metrics_fp)}\033[m')
    metrics = mmcv.load(metrics_fp)

    prop_stat_fp = result_folder / 'proposal_stats.csv'
    if (result_file is not None and not prop_stat_fp.exists()) or needs_to_be_exported:
        msg3(f"Calculating statistics for results -> {prop_stat_fp.name}")
        with open(result_file, 'r') as fp:
            save_proposal_stats(json.load(fp), prop_stat_fp, data_loader)

    msg3(f'Compiling metrics with overlaps \033[1m{overlaps_str}\033[m')
    for cls, overlap_metrics in metrics.items():
        for overlap in overlaps:
            overlap_metrics[overlap] = overlap_metrics[overlap].get('ap', np.NaN)
        metrics[cls] = overlap_metrics
    for cls in data_loader.dataset.CLASSES:
        stats[cls].append(metrics.get(cls, {}))
    # for cls, metrics in stats.items():
    #     stats[cls].append(metrics[-1])
    save_predictions(outputs, result_folder / args.json_out, data_loader, args)

def test_checkpoint(
        main_config,
        model,
        checkpoint_id: str,
        checkpoint_file: Path,
        checkpoint: dict,
        out_folder: Path,
        proposals_fp: Path,
        distributed: bool,
        args
) -> Generator[Tuple[str, DataLoader, list], None, None]:
    checkpoint_id, chkp_folder, test_set_kwargs, test_sets = prepare_folder(checkpoint_id, checkpoint, checkpoint_file, out_folder)
    test_set_kwargs['distributed'] = distributed
    for data_loader in get_test_sets(*test_sets, **test_set_kwargs):
        ann_file = Path(data_loader.dataset.ann_file)
        index = f"{checkpoint_id} - {ann_file.stem}"
        msg2(f"Selecting dataset: \033[1m{ann_file.stem}\033[m")
        result_folder = chkp_folder / ann_file.stem
        result_folder.mkdir(exist_ok=True)

        outputs = get_proposals(checkpoint, main_config, model, data_loader, result_folder / proposals_fp, distributed, args)
        yield index, data_loader, outputs, result_folder / proposals_fp

        # metrics = evaluate_proposals(outputs_m[checkpoint_file][data_loader], cfg, checkpoint_file, overlaps, data_loader, result_folder, args)
        # eval_types = args.eval
        # data_name = args.data
        # assert data_name in ['dsv2']
        # from mmdet.core import outputs_rotated_box_to_poly_np
        #
        # for page in outputs_m[checkpoint_file][data_loader]:
        #     page.insert(0, np.array([]))
        #
        # outputs_m[checkpoint_file][data_loader] = outputs_rotated_box_to_poly_np(outputs_m[checkpoint_file][data_loader])

def infer_checkpoint(checkpoint: dict, main_config: Config, model: Sequential, data_loader: DataLoader, folder: Path,
                     args: Namespace) -> Tuple[list, Path]:
    ann_file = Path(data_loader.dataset.ann_file)
    msg2(f"Running checkpoint on dataset: \033[1m{ann_file.stem}\033[m")
    result_folder = folder / ann_file.stem
    result_folder.mkdir(exist_ok=True)
    eval_fp = result_folder / args.out
    outputs = get_proposals(checkpoint, main_config, model, data_loader, eval_fp, args)
    return outputs, eval_fp

def prepocess_WBF(props):
    for i, row in props.iterrows():
        if row.cat_id == 42 or row.cat_id == 2:
            # 2 is stem, 42 is ledgerLine
            props.at[i, 'bbox'] = BboxHelper(row.bbox).get_sorted_angle_zero()

def wbf_proposals_to_output(proposals: pd.DataFrame) -> List[List[np.ndarray]]:
    output = []
    last_img_idx = 0
    def new_sample():
        sample_output = []
        for _ in range(135):
            sample_output.append(np.zeros((0, 6)))
        output.append(sample_output)
        return sample_output
    sample_output = new_sample()
    for bbox, cat_id, img_idx, score in proposals.values:
        if img_idx != last_img_idx:
            sample_output = new_sample()
            last_img_idx = img_idx
        bboxes: np.ndarray = sample_output[cat_id - 1]
        #bbox = rotated_box_to_poly_np(bbox[:5])
        bbox = np.concatenate([bbox, np.array([score])])
        sample_output[cat_id - 1] = np.concatenate([bboxes.reshape((-1, 9)), bbox.reshape((1, 9))])
    return output

def ensure_8_tuple(outputs: List[List[np.ndarray]]) -> List[List[np.ndarray]]:
    for sample in outputs:
        for i, cls in enumerate(sample):
            if cls.shape[0] == 0:
                cls = cls.reshape((0, 9))
            elif cls.shape[1] == 6:
                cls = np.concatenate([rotated_box_to_poly_np(cls[:,:5]), cls[:,5].reshape(-1, 1)], axis=1)
            sample[i] = cls
    return outputs

def main():
    args = parse_args()

    assert args.out or args.show or args.json_out, \
        ('Please specify at least one operation (save or show the results) '
         'with the argument "--out" or "--show" or "--json_out"')

    if args.out is not None and not args.out.endswith(('.pkl', '.pickle')):
        raise ValueError('The output file must be a pkl file.')

    if args.postprocess:
        render.fill_cache()

    cfg = mmcv.Config.fromfile(args.config)
    # set cudnn_benchmark
    if cfg.get('cudnn_benchmark', False):
        torch.backends.cudnn.benchmark = True
    cfg.model.pretrained = None
    # cfg.model.rpn_pretrained = None
    # cfg.model.rcnn_pretrained = None

    cfg.data.test.test_mode = True

    # init distributed env first, since logger depends on the dist info.
    if args.launcher == 'none':
        distributed = False
    else:
        distributed = True
        init_dist(args.launcher, **cfg.dist_params)

    out_folder = Path('eval')
    proposals_fp = Path(args.out)
    if args.json_out:
        json_out_fp = Path(args.json_out)

    index = []
    stats = {'samples': []}
    msg1("Loading basic test sets")
    tmp_test_sets = get_test_sets(*TEST_SETS)
    stats.update({key: [] for key in tmp_test_sets[0].dataset.CLASSES})
    dataset_names = []
    for tmp_test_set in tmp_test_sets:
        dataset_names.append(tmp_test_set.dataset.obb.dataset_info['description'])

    # Make sure we only use the best epochs for each config
    checkpoints = preprocess_checkpoints(args.checkpoints, cfg)

    overlaps = np.arange(0.1, 0.96, 0.05)
    outputs_m = defaultdict(dict)
    metrics = {}
    for checkpoint_id, checkpoint_data in checkpoints.items():
        if isinstance(checkpoint_data, tuple):
            checkpoint_file, checkpoint, model = checkpoint_data
            outputs = {}
            sub_stats = defaultdict(list)
            checkpoint_id, chkp_folder, test_set_kwargs, test_sets = prepare_folder(checkpoint_id, checkpoint, checkpoint_file, out_folder)
            test_set_kwargs['distributed'] = distributed
            for data_loader in get_test_sets(*test_sets, **test_set_kwargs):
                ann_file = Path(data_loader.dataset.ann_file)
                index.append(f"{checkpoint_id} - {ann_file.stem}")
                output, _ = infer_checkpoint(checkpoint, cfg, model, data_loader, chkp_folder, args)
                outputs[data_loader] = output
                sub_stats['samples'].append(len(data_loader.dataset.img_ids))
                # Evaluate results
                evaluate_results(output, chkp_folder, data_loader, checkpoint_id, overlaps, sub_stats, args)
                outputs_m[checkpoint_id][data_loader] = output
            for cls, aps in sub_stats.items():
                stats[cls].extend(aps)
        elif isinstance(checkpoint_data, list):
            mm_index = []
            mm_stats = {'samples': []}
            sub_stats = defaultdict(list)
            ensemble_folder = out_folder / checkpoint_id
            ensemble_folder.mkdir(parents=True, exist_ok=True)
            test_set_kwargs = {
                'workers_per_gpu': 4,
                'distributed': distributed,
            }
            for data_loader in get_test_sets(*TEST_SETS, **test_set_kwargs):
                ann_file = Path(data_loader.dataset.ann_file)
                wbf_glob_dir = ensemble_folder / ann_file.stem
                wbf_glob_dir.mkdir(exist_ok=True)
                for checkpoint_sub_id, checkpoint_file, checkpoint, model in checkpoint_data:
                    checkpoint_sub_id, chkp_folder, _, test_sets = prepare_folder(checkpoint_sub_id, checkpoint, checkpoint_file, ensemble_folder)
                    idx = f"{checkpoint_sub_id} - {ann_file.stem}"
                    output, eval_fp = infer_checkpoint(checkpoint, cfg, model, data_loader, chkp_folder, args)

                    # When using the WBF load_proposals function, copy the results.json file into a special folder for
                    # recursively finding the results.json files
                    folder = wbf_glob_dir / checkpoint_sub_id
                    folder.mkdir(exist_ok=True)
                    shutil.copyfile(chkp_folder / ann_file.stem / args.json_out, folder / 'result.json')
                sub_stats['samples'].append(len(data_loader.dataset.img_ids))
                mm_index.append(f"{checkpoint_id} - {ann_file.stem}")
                index.append(f"{checkpoint_id} - {ann_file.stem}")
                proposal_fp = ensemble_folder / f"wbf_proposals.pkl"
                if not proposal_fp.exists():
                    msg2("Running weighted box fusion")
                    wbf_proposals: DataFrame = load_proposals(Namespace(inp=wbf_glob_dir), data_loader.dataset, iou_thr=0.3)
                    wbf_proposals.to_pickle(proposal_fp)
                else:
                    msg2("Loading weighted box fusion results")
                    wbf_proposals: DataFrame = pd.read_pickle(proposal_fp)
                output2 = wbf_proposals_to_output(wbf_proposals)
                outputs_m[checkpoint_id][data_loader] = output2
                args.cache = False
                evaluate_results(outputs_m[checkpoint_id][data_loader], ensemble_folder, data_loader, checkpoint_id, overlaps, sub_stats, args)
                args.cache = True
            for cls, aps in sub_stats.items():
                stats[cls].extend(aps)
    print('#' * 30)
    print('#' * 30)
    print('#' * 30)
    msg1(f"Evaluating stats")
    plot_stats(overlaps, out_folder, stats, index, dataset_names)

if __name__ == '__main__':
    main()


# Suppress Scapy IPv6 warning
import logging
logging.getLogger("scapy.runtime").setLevel(logging.ERROR)

# Begin our Scapy script.
from scapy.all import *

old_server = '10.230.228.146'
new_server = '10.230.228.104'
old_port = 8000
new_port = 8888
timeout = 5
filter = 'tcp'

def rewrite(pkt):
    if pkt[IP].dst == old_server and pkt[TCP].dport == old_port:
        # Modify the packet
        print('ORIG: {0}'.format(pkt.summary()))
        pkt[IP].src = new_server
        pkt[TCP].dport = new_port
        print('NEW: {0}'.format(pkt.summary()))
        print()

        # Send the modified packet
        sendp(pkt)

sniff(filter=filter, prn=rewrite)


from typing import Tuple, List, NewType

import csv

import torch
from torch import Tensor

from utils import Encoder

##################################################
# Types
##################################################

# Input: person name (Bach, Reynolds, etc.)
Name = str

# Input character
Char = str

# Output: the language (German, English, etc.)
Lang = str

##################################################
# Data extraction
##################################################

def load_data(file_path: str) -> List[Tuple[Name, Lang]]:
    """Load the dataset from a .csv file."""
    data_set = []
    with open(file_path, 'r', encoding='utf8') as file:
        csv_reader = csv.reader(file, delimiter=',')
        for name, lang in csv_reader:
            data_set.append((name, lang))
    return data_set

##################################################
# Encoding
##################################################

def create_encoders(data: List[Tuple[Name, Lang]]) \
        -> Tuple[Encoder[Char], Encoder[Lang]]:
    """Create the encoders for the input characters and
    the output languages."""
    char_enc = Encoder(char for name, lang in data for char in name)
    lang_enc = Encoder(lang for name, lang in data)
    return char_enc, lang_enc

def encode_with(
    data: List[Tuple[Name, Lang]],
    char_enc: Encoder[Char],
    lang_enc: Encoder[Lang],
) -> List[Tuple[Tensor, Tensor]]:
    enc_data = []
    for name, lang in data:
        enc_name = torch.tensor([char_enc.encode(char) for char in name])
        enc_lang = torch.tensor(lang_enc.encode(lang))
        enc_data.append((enc_name, enc_lang))
    return enc_data


import boto3
import time
from boto3.dynamodb.conditions import Key, Attr
import pandas as pd

dynamodb = boto3.resource('dynamodb',
                        aws_access_key_id = 'XXXXXX',
                        aws_secret_access_key = 'XXXXXXXXX',
                        region_name = 'us-east-2')

# Functions to get concrete table from aws service

def amazon_database(name):
    amazon_table = dynamodb.Table(name)

    return amazon_table

table = amazon_database("Lego")
smyk_table = amazon_database("Lego_smyk")

# Function to get all LEGO set title from dynamodb

def get_set_title(amazon_table):

    items = amazon_table.scan(AttributesToGet=['title'])
    items = items['Items']

    list_of_set_title = []

    for item in items:
        item = item['title']

        if item not in list_of_set_title:
            list_of_set_title.append(item)

    return list_of_set_title

list_of_sets = get_set_title(table)
#list_of_sets_smyk = get_set_title(smyk_table)

# Get prices for concrete set f.e. Ferrari Daytona

def get_price_df(amazon_table, set_title):

    response = amazon_table.scan(
        FilterExpression = Attr('title').eq(set_title)
    )

    response = response['Items']

    list_of_modified_items = []

    for item in response:
        tuple_single_item = tuple()

        tuple_single_item = (item['Date'], item['Price'])

        list_of_modified_items.append(tuple_single_item)

    df = pd.DataFrame(list_of_modified_items, columns=['Date', 'Price'])
    df['Price'] = pd.to_numeric(df['Price'])

    df = df.drop_duplicates(subset=['Date'])
    df = df.sort_values(by='Date')
    df = df.reset_index()
    df = df.drop(['index'], axis=1)

    return df

# Get min historical set price + date

def get_min_price(df):
    filtered_df = df[df['Price'] == df['Price'].min()]
    filtered_df = filtered_df.reset_index()
    filtered_df = filtered_df.drop(['index'], axis=1)

    if len(filtered_df) > 1:
        min_price = filtered_df['Price'].min()
        first_date = filtered_df['Date'][0]
        last_date = filtered_df['Date'][len(filtered_df) - 1]

        return min_price, first_date, last_date
    else:
        min_price = filtered_df['Price'].min()
        historical_min = filtered_df["Date"][0]

        return min_price, historical_min

    #return filtered_df


import json
import socket
import traceback
from abc import ABC, abstractmethod
from datetime import datetime
from enum import Enum
from logging import Logger
from os import getenv
from typing import Dict, Optional

from confluent_kafka import KafkaError, KafkaException, Producer
from docarray import Document, DocumentArray
from simpletimer import StopwatchKafka

class Broker(str, Enum):
    kafka: str = "kafka"
    zmq: str = "zmq"
    none: str = ""

class Component(ABC):
    def __init__(
        self, msg_broker: Optional[Broker] = None, name: str = "baseline-component"
    ):
        self.logger = Logger(name)

        self.conf = {
            "bootstrap.servers": getenv("KAFKA_ADDRESS", "127.0.0.1:9092"),
            "client.id": socket.gethostname(),
            "message.max.bytes": 1000000000,
        }
        self.metrics_topic = getenv("KAFKA_METRICS_TOPIC", "metrics")
        self.executor_name = name
        self.executor_id = self.executor_name + "-" + datetime.now().isoformat()

        # Set up producer for Kafka metrics
        self.timer = StopwatchKafka(
            bootstrap_servers=getenv("KAFKA_ADDRESS", "127.0.0.1:9092"),
            kafka_topic=self.metrics_topic,
            metadata={"executor": self.executor_name},
            kafka_parition=-1,
        )

        self.last_frame: Dict[str, str] = {}
        self.metric_producer = Producer(self.conf)

    @abstractmethod
    def __call__(
        self, data: DocumentArray, parameters: Optional[dict] = {}, **kwargs
    ) -> DocumentArray:
        return data

    def serve(self, send_tensors: bool = True, filter_stream: Optional[str] = None):
        """
        This is a wrapper around __call__ that will do the following:

        1. Consume using either Kafka or ZMQ
        2. Process consumed data into DocArray
        3. Pass DocArray to __call__
        4. Process output
        5. Produce

        The choice of processor will depend on an environment variable
        """
        if self.broker == Broker.zmq:
            self._process_zmq(send_tensors, filter_stream)
        elif self.broker == Broker.kafka:
            self._process_kafka(send_tensors, filter_stream)

    def _process_zmq(
        self, send_tensors: bool = True, filter_stream: Optional[str] = None
    ):
        try:
            while True:
                # Get frames
                data = self.consumer.receive(timeout=1)
                if data is None:
                    continue
                # Convert metadata to docarray
                assert isinstance(data, bytes), "Is byte"
                frame_docs = DocumentArray.from_bytes(data)
                if filter_stream is not None:
                    frame_docs = frame_docs.find(
                        {"tags__output_stream": {"$eq": filter_stream}}, limit=None
                    )
                if len(frame_docs) == 0:
                    continue  # skip frames if pod not meant to receive them
                result = self._call_main(frame_docs, send_tensors)
                # Process Results
                if self.producer:
                    self.producer.zmq_socket.send(result.to_bytes())
        except (SystemExit, KeyboardInterrupt):
            print("Exit due to keyboard interrupt")
        except Exception as ex:
            print("Python error with no Exception handler:")
            print("Traceback error:", ex)
            traceback.print_exc()
        finally:
            self.consumer.close()

    def _process_kafka(
        self, send_tensors: bool = True, filter_stream: Optional[str] = None
    ):
        try:
            if not self.consume_topic:
                raise ValueError("No consumer topic set!")
            self.consumer.subscribe([self.consume_topic])
            while True:
                # Get frames
                data = self.consumer.poll(timeout=1)
                if data is None:
                    continue
                if data.error():
                    if data.error().code() == KafkaError._PARTITION_EOF:
                        continue
                    else:
                        raise KafkaException(data.error())
                # Convert metadata to docarray
                frame_docs = DocumentArray.from_bytes(data.value())
                if filter_stream is not None:
                    frame_docs = frame_docs.find(
                        {"tags__output_stream": {"$eq": filter_stream}}, limit=None
                    )
                if len(frame_docs) == 0:
                    continue  # skip frames if pod not meant to receive them
                result = self._call_main(frame_docs, send_tensors)
                # Process Results
                if self.produce_topic:
                    self.producer.produce(self.produce_topic, value=result.to_bytes())
                    self.producer.poll(0)
        except (SystemExit, KeyboardInterrupt):
            print("Exit due to keyboard interrupt")
        except Exception as ex:
            print("Python error with no Exception handler:")
            print("Traceback error:", ex)
            traceback.print_exc()
        finally:
            self.consumer.close()

    def _call_main(
        self, docs: DocumentArray, send_tensors: bool = True
    ) -> DocumentArray:
        if not send_tensors:
            if docs[..., "uri"] is not None:
                docs[...].apply(self._load_uri_to_image_tensor)

        # NOTE: Assume only 1 doc inside docarray
        # Check for dropped frames
        frame_id = docs[0].tags["frame_id"]
        output_stream = docs[0].tags["output_stream"]
        video_source = docs[0].tags["video_path"]
        if output_stream not in self.last_frame:
            self.last_frame[output_stream] = frame_id
        if frame_id < self.last_frame[output_stream]:
            self.metric_producer.produce(
                self.metrics_topic,
                value=json.dumps(
                    {
                        "type": "dropped_frame",
                        "timestamp": datetime.now().isoformat(),
                        "executor": self.executor_name,
                        "executor_id": self.executor_id,  # contain time the exec was initialized
                        "frame_id": frame_id,
                        "output_stream": output_stream,
                        "video_source": video_source,
                    }
                ).encode("utf-8"),
            )
            self.metric_producer.poll(0)
            self.logger.warn("Dropped frame")

        with self.timer(
            metadata={
                "event": "overall",
                "frame_id": frame_id,
                "video_path": video_source,
                "output_stream": output_stream,
                "timestamp": datetime.now().isoformat(),
                "executor": self.executor_name,
                "executor_id": self.executor_id,
            }
        ):
            docs = self.__call__(docs)
        if not send_tensors:
            docs[...].tensors = None
        return docs

    @staticmethod
    def _load_uri_to_image_tensor(doc: Document) -> Document:
        if doc.uri:
            doc = doc.load_uri_to_image_tensor()
            # NOTE: Testing shows not necessary and actually breaks stuff
            # Convert channels from NHWC to NCHW
            # doc.tensor = np.transpose(doc.tensor, (2, 1, 0))
        return doc


import logging
from datetime import datetime

from django.db.models import Q
from django.http import Http404
from rest_framework.generics import get_object_or_404
from rest_framework import status
from rest_framework.response import Response

from ..core.utils import deserialize_response, get_response, BulkCreateManager
from .models import Book, Author, Category
from .exceptions import BooksNotFound, IncorrectPublishedDateOfBook, BookDoesNotExists, BookParserException, \
    InvalidQueryParameterInBody

# Get an instance of a logger
logger = logging.getLogger(__name__)

class BookAuthorNameMixin(object):
    def filter_by_author_name(self, queryset):
        """
        Return filtered queryset by author's name
        Allow multiple filtering
        """
        authors_names = self.request.GET.getlist('author')
        if authors_names:
            queries = [Q(authors__name__icontains=author_name) for author_name in authors_names]
            query = queries.pop()
            for item in queries:
                query |= item
            queryset = queryset.filter(query)
        return queryset

class BookPublishedDateMixin(object):
    def filter_by_published_date(self, queryset):
        """
        Return filtered queryset by published_date
        Allow multiple filtering
        """
        published_dates = self.request.GET.getlist('published_date')
        if published_dates:
            years = []
            try:
                for date in published_dates:
                    year = int(date)
                    if datetime.now().year >= year > 0:
                        years.append(year)
                if years:
                    queryset = queryset.filter(published_date__year__in=years)
                else:
                    raise BooksNotFound
            except (TypeError, ValueError):
                raise BooksNotFound
        return queryset

class BookListMixin(BookPublishedDateMixin, BookAuthorNameMixin):
    def filter_queryset(self, queryset):
        """
        Run filters on queryset
        """
        queryset = self.filter_by_published_date(queryset)
        queryset = self.filter_by_author_name(queryset)
        return queryset

    def get_queryset(self):
        """
        Get queryset and apply custom publish date filter
        """
        queryset = self.model.objects.all()
        queryset = self.filter_queryset(queryset)

        return queryset

class BookRetrieveMixin:
    def get_object(self, *args, **kwargs):
        """
        Get
        """
        book_id = kwargs['book_id']
        try:
            return self.model.objects.get(book_id=book_id)
        except self.model.DoesNotExist:
            raise BooksNotFound

class BookDownloader:
    source_url = "https://www.googleapis.com/books/v1/volumes?q="

    def __init__(self, query):

        # "q" parameter from POST body
        self.query = query

        # A json response placeholder
        self.response = None

        # Dictionaries of books from json document and of book currently being created
        self.books, self.book_dict = {}, {}

        # List of ids of books from json document
        self.books_ids = []

        # Currently iterated book from json document
        self.book = None

        # List of not existing and already existing in database books
        self.existing, self.not_existing = [], []

        # URL from which json is downloaded
        self.url = self.source_url + (self.query or '')

        # New books bulk insert manager
        self.bulk_manager = BulkCreateManager()

        # List of books to update
        self.objects_to_update = []

        # Lists of categories and authors from ManyToMany relations to add after saving objects
        self.book_category_m2m_list, self.book_author_m2m_list = [], []

        # Lists of created/updated authors and categories (m2m relation of books)
        self.authors, self.categories = [], []

    def get_books(self):
        """
        Get and deserialize response.
        Return books dictionary if success, else if failed raise BooksNotFound.
        """

        # Get and set response. May raise GetResponseError.
        self.set_response()

        volumes = deserialize_response(self.response)
        if 'items' in volumes:
            books = volumes['items']
            return books
        else:
            logger.error(f"Books not found")
            raise BooksNotFound

    def get_information(self):
        """
        Get book information.
        Create or update many 2 many relation objects.
        """
        self.book_dict['book_id'] = self.book['id']

        if 'title' in self.book['volumeInfo']:
            self.book_dict['title'] = self.book['volumeInfo']['title']

        # When book is created, M2M related authors/categories need to be added later due to bulk insert
        # When book is updated, authors/categories can be added immediately (no bulk operation)
        # Authors/Categories to be added to self.book_dict later
        if 'authors' in self.book['volumeInfo']:
            self.authors = self.update_or_create_authors()
        if 'categories' in self.book['volumeInfo']:
            self.categories = self.update_or_create_categories()
        if 'imageLinks' in self.book['volumeInfo']:
            if 'thumbnail' in self.book['volumeInfo']['imageLinks']:
                self.book_dict['thumbnail'] = self.book['volumeInfo']['imageLinks']['thumbnail']
        if 'publishedDate' in self.book['volumeInfo']:
            self.book_dict['published_date'] = self.get_published_date()
        if 'averageRating' in self.book['volumeInfo']:
            self.book_dict['average_rating'] = self.book['volumeInfo']['averageRating']
        if 'ratingsCount' in self.book['volumeInfo']:
            self.book_dict['ratings_count'] = self.book['volumeInfo']['ratingsCount']

    def update_or_create_authors(self):
        """
        Update or create Author objects and return a list of these objects.
        """
        objects = []
        authors = self.book['volumeInfo']['authors']
        for author in authors:
            obj, created = Author.objects.update_or_create(name=author)
            objects.append(obj)

        return objects

    def update_or_create_categories(self):
        """
        Update or create Category objects and return a list of these objects.
        """
        objects = []
        categories = self.book['volumeInfo']['categories']
        for category in categories:
            obj, created = Category.objects.update_or_create(name=category)
            objects.append(obj)
        return objects

    def get_published_date(self):
        """
        Get published date from books dict which is based on source json.
        Set exact_date if provided full date
        """
        published_date_str = self.book['volumeInfo']['publishedDate']
        try:
            if len(published_date_str) == 4:
                pattern = '%Y'
            elif len(published_date_str) == 7:
                pattern = '%Y-%m'
            else:
                pattern, self.book_dict['exact_date'] = '%Y-%m-%d', True

            published_date = datetime.strptime(published_date_str, pattern)
            return published_date

        except (ValueError, TypeError) as err:
            logger.error(f'Incorrect published date "{published_date_str}" for book - {err}')
            raise IncorrectPublishedDateOfBook

    def get_books_ids(self):
        """
        Get new books ids from book dict.
        Purpose: To check later if already exists in the database
        """
        ids = []
        for book in self.books:
            if 'id' in book:
                ids.append(book['id'])
        return ids

    def get_existing(self):
        """
        Return list of ids of already existing books in database
        """
        return list(Book.get_ids_which_already_exists(self.books_ids))

    def get_not_existing(self):
        """
        Return list of ids of not existing yet books,
        which is based on list of new and already existing books
        """
        return [item for item in self.books_ids if item not in self.existing]

    def create_new_books(self):
        """
        Commit bulk inserts
        """
        self.bulk_manager.done()

    def create_m2m_book_authors(self):
        """
        Create many2many relation objects for authors of the books
        """
        BookAuthor = Book.authors.through
        BookAuthor.objects.bulk_create(
            [BookAuthor(
                book_id=book.id,
                author_id=author.id
            )
                for book, author in self.book_author_m2m_list]
        )
        self.book_author_m2m_list = []

    def create_m2m_book_categories(self):
        """
            Create many2many relation objects for categories of the books
        """
        BookCategory = Book.categories.through
        BookCategory.objects.bulk_create(
            [BookCategory(
                book_id=book.id,
                category_id=category.id
            )
                for book, category in self.book_category_m2m_list]
        )
        self.book_category_m2m_list = []

    def create_m2m_objects(self):
        """
        Create many2many relation objects of the books
        """
        self.create_m2m_book_authors()
        self.create_m2m_book_categories()

    def update_existing_book(self):
        """
        Update existing book.
        Raise Http404 and BookDoesNotExists if book does not exists.
        """
        try:
            # Try to get book object to update - raise an exception failed
            updated_book = get_object_or_404(Book, book_id=self.book_dict['book_id'])
            # Update modified book
            for attr, value in self.book_dict.items():
                setattr(updated_book, attr, value)
            updated_book.save()

            # Update m2m relations
            updated_book.authors.set(self.authors)
            updated_book.categories.set(self.categories)

        except Http404 as err:
            logger.error(f"Book {self.book_dict['id']} does not exists - {err}")
            raise BookDoesNotExists

    def manage_not_existing_book(self):
        """
        Manage not existing yet book:
        - create new book and add to bulk manager
        - commit with bulk create if batch size is exceeded
        """
        # Create new Book object, wait with commit
        new_book = Book(**self.book_dict)

        # Add newly created book to bulk insert manager
        # Commit when batch chuck size exceeded (init parameter)
        # Else add book to waiting queue
        self.bulk_manager.add(new_book)

        # Add authors and categories to m2m lists in order to create object
        self.set_m2m_lists(new_book)

    def manage_existing_book(self):
        """
        Set M2M fields (authors, categories) and update existing book
        """
        self.update_existing_book()

    def set_response(self):
        self.response = get_response(self.url)

    def set_books_ids(self):
        self.books_ids = self.get_books_ids()

    def set_books(self):
        self.books = self.get_books()

    def set_existing_books(self):
        self.existing = self.get_existing()

    def set_not_existing_books(self):
        self.not_existing = self.get_not_existing()

    def set_m2m_lists(self, new_book):
        self.book_author_m2m_list += [[new_book, author] for author in self.authors]
        self.book_category_m2m_list += [[new_book, category] for category in self.categories]

    def perform_create(self):
        """
        Create/update books on two ways.
        Logic split into operations on existing and new books duo performance improvement
        """
        self.set_books()  # Try to get books from source and raise BooksNotFound if failed
        self.set_books_ids()  # Get ids of all new books
        self.set_existing_books()  # Mark already existing books
        self.set_not_existing_books()  # Mark new books

        # Gather book information in loop
        for book in self.books:
            self.book = book

            # Get book data - missing anything critical breaks iteration
            try:
                self.get_information()
            except KeyError as err:
                logger.error(f"Incorrect input data. Critical book data is not provided - {err}")
                raise BookParserException

            if self.book_dict['book_id'] in self.existing:
                self.manage_existing_book()
            else:
                self.manage_not_existing_book()

        # Try to bulk create new books and m2m relation objects if new books exist
        # New objects could be already created if batch size was reached
        if self.not_existing:
            self.create_new_books()

            # Create authors and categories ManyToMany relation objects and add to M2M lists.
            self.create_m2m_objects()

class BookCreateUpdateMixin(object):
    """
    Create/update Book model instances.
    """

    @staticmethod
    def get_parameter(request):
        """
        Get and set POST request "q" parameter.
        Return value of parameter if success, else raise InvalidQueryParameterInBody exception
        """
        query = request.POST.get("q", "")
        if not query:
            raise InvalidQueryParameterInBody
        return query

    def create_or_update(self, request, *args, **kwargs):
        """
        Process request to create/update books.
        Return Response with status code 201 if success.
        """
        # Get query "q" parameter. May raise InvalidQueryParameterInBody exception
        query = self.get_parameter(request)

        # Proper create/update operation
        BookDownloader(query=query).perform_create()

        # Return success response with 201 code if books has been created/updated
        return Response(f"Success: q={query}", status=status.HTTP_201_CREATED)


# coding: utf-8

"""
    IP Address Management API

    The IPAM/DHCP Application is a BloxOne DDI service providing IP address management and DHCP protocol features. The IPAM component provides visibility into and provisioning tools to manage networking spaces, monitoring and reporting of entire IP address infrastructures, and integration with DNS and DHCP protocols. The DHCP component provides DHCP protocol configuration service with on-prem host serving DHCP protocol. It is part of the full-featured, DDI cloud solution that enables customers to deploy large numbers of protocol servers to deliver DNS and DHCP throughout their enterprise network.     # noqa: E501

    OpenAPI spec version: v1

    Generated by: https://github.com/swagger-api/swagger-codegen.git
"""

import pprint
import re  # noqa: F401

import six

class IpamsvcASM(object):
    """NOTE: This class is auto generated by the swagger code generator program.

    Do not edit the class manually.
    """

    """
    Attributes:
      swagger_types (dict): The key is attribute name
                            and the value is attribute type.
      attribute_map (dict): The key is attribute name
                            and the value is json key in definition.
    """
    swagger_types = {
        'back_end': 'str',
        'back_start': 'str',
        'both_end': 'str',
        'both_start': 'str',
        'front_end': 'str',
        'front_start': 'str',
        'growth': 'int',
        'id': 'str',
        'lookahead': 'int',
        'range_end': 'str',
        'range_id': 'str',
        'range_start': 'str',
        'subnet_address': 'str',
        'subnet_cidr': 'int',
        'subnet_direction': 'str',
        'subnet_id': 'str',
        'subnet_range': 'str',
        'subnet_range_end': 'str',
        'subnet_range_start': 'str',
        'suppress': 'str',
        'suppress_time': 'int',
        'threshold_utilization': 'int',
        'update': 'str',
        'utilization': 'int'
    }

    attribute_map = {
        'back_end': 'back_end',
        'back_start': 'back_start',
        'both_end': 'both_end',
        'both_start': 'both_start',
        'front_end': 'front_end',
        'front_start': 'front_start',
        'growth': 'growth',
        'id': 'id',
        'lookahead': 'lookahead',
        'range_end': 'range_end',
        'range_id': 'range_id',
        'range_start': 'range_start',
        'subnet_address': 'subnet_address',
        'subnet_cidr': 'subnet_cidr',
        'subnet_direction': 'subnet_direction',
        'subnet_id': 'subnet_id',
        'subnet_range': 'subnet_range',
        'subnet_range_end': 'subnet_range_end',
        'subnet_range_start': 'subnet_range_start',
        'suppress': 'suppress',
        'suppress_time': 'suppress_time',
        'threshold_utilization': 'threshold_utilization',
        'update': 'update',
        'utilization': 'utilization'
    }

    def __init__(self, back_end=None, back_start=None, both_end=None, both_start=None, front_end=None, front_start=None, growth=None, id=None, lookahead=None, range_end=None, range_id=None, range_start=None, subnet_address=None, subnet_cidr=None, subnet_direction=None, subnet_id=None, subnet_range=None, subnet_range_end=None, subnet_range_start=None, suppress=None, suppress_time=None, threshold_utilization=None, update=None, utilization=None):  # noqa: E501
        """IpamsvcASM - a model defined in Swagger"""  # noqa: E501

        self._back_end = None
        self._back_start = None
        self._both_end = None
        self._both_start = None
        self._front_end = None
        self._front_start = None
        self._growth = None
        self._id = None
        self._lookahead = None
        self._range_end = None
        self._range_id = None
        self._range_start = None
        self._subnet_address = None
        self._subnet_cidr = None
        self._subnet_direction = None
        self._subnet_id = None
        self._subnet_range = None
        self._subnet_range_end = None
        self._subnet_range_start = None
        self._suppress = None
        self._suppress_time = None
        self._threshold_utilization = None
        self._update = None
        self._utilization = None
        self.discriminator = None

        if back_end is not None:
            self.back_end = back_end
        if back_start is not None:
            self.back_start = back_start
        if both_end is not None:
            self.both_end = both_end
        if both_start is not None:
            self.both_start = both_start
        if front_end is not None:
            self.front_end = front_end
        if front_start is not None:
            self.front_start = front_start
        if growth is not None:
            self.growth = growth
        if id is not None:
            self.id = id
        if lookahead is not None:
            self.lookahead = lookahead
        if range_end is not None:
            self.range_end = range_end
        if range_id is not None:
            self.range_id = range_id
        if range_start is not None:
            self.range_start = range_start
        if subnet_address is not None:
            self.subnet_address = subnet_address
        if subnet_cidr is not None:
            self.subnet_cidr = subnet_cidr
        if subnet_direction is not None:
            self.subnet_direction = subnet_direction
        self.subnet_id = subnet_id
        if subnet_range is not None:
            self.subnet_range = subnet_range
        if subnet_range_end is not None:
            self.subnet_range_end = subnet_range_end
        if subnet_range_start is not None:
            self.subnet_range_start = subnet_range_start
        if suppress is not None:
            self.suppress = suppress
        if suppress_time is not None:
            self.suppress_time = suppress_time
        if threshold_utilization is not None:
            self.threshold_utilization = threshold_utilization
        if update is not None:
            self.update = update
        if utilization is not None:
            self.utilization = utilization

    @property
    def back_end(self):
        """Gets the back_end of this IpamsvcASM.  # noqa: E501

        :return: The back_end of this IpamsvcASM.  # noqa: E501
        :rtype: str
        """
        return self._back_end

    @back_end.setter
    def back_end(self, back_end):
        """Sets the back_end of this IpamsvcASM.

        :param back_end: The back_end of this IpamsvcASM.  # noqa: E501
        :type: str
        """

        self._back_end = back_end

    @property
    def back_start(self):
        """Gets the back_start of this IpamsvcASM.  # noqa: E501

        :return: The back_start of this IpamsvcASM.  # noqa: E501
        :rtype: str
        """
        return self._back_start

    @back_start.setter
    def back_start(self, back_start):
        """Sets the back_start of this IpamsvcASM.

        :param back_start: The back_start of this IpamsvcASM.  # noqa: E501
        :type: str
        """

        self._back_start = back_start

    @property
    def both_end(self):
        """Gets the both_end of this IpamsvcASM.  # noqa: E501

        :return: The both_end of this IpamsvcASM.  # noqa: E501
        :rtype: str
        """
        return self._both_end

    @both_end.setter
    def both_end(self, both_end):
        """Sets the both_end of this IpamsvcASM.

        :param both_end: The both_end of this IpamsvcASM.  # noqa: E501
        :type: str
        """

        self._both_end = both_end

    @property
    def both_start(self):
        """Gets the both_start of this IpamsvcASM.  # noqa: E501

        :return: The both_start of this IpamsvcASM.  # noqa: E501
        :rtype: str
        """
        return self._both_start

    @both_start.setter
    def both_start(self, both_start):
        """Sets the both_start of this IpamsvcASM.

        :param both_start: The both_start of this IpamsvcASM.  # noqa: E501
        :type: str
        """

        self._both_start = both_start

    @property
    def front_end(self):
        """Gets the front_end of this IpamsvcASM.  # noqa: E501

        :return: The front_end of this IpamsvcASM.  # noqa: E501
        :rtype: str
        """
        return self._front_end

    @front_end.setter
    def front_end(self, front_end):
        """Sets the front_end of this IpamsvcASM.

        :param front_end: The front_end of this IpamsvcASM.  # noqa: E501
        :type: str
        """

        self._front_end = front_end

    @property
    def front_start(self):
        """Gets the front_start of this IpamsvcASM.  # noqa: E501

        :return: The front_start of this IpamsvcASM.  # noqa: E501
        :rtype: str
        """
        return self._front_start

    @front_start.setter
    def front_start(self, front_start):
        """Sets the front_start of this IpamsvcASM.

        :param front_start: The front_start of this IpamsvcASM.  # noqa: E501
        :type: str
        """

        self._front_start = front_start

    @property
    def growth(self):
        """Gets the growth of this IpamsvcASM.  # noqa: E501

        :return: The growth of this IpamsvcASM.  # noqa: E501
        :rtype: int
        """
        return self._growth

    @growth.setter
    def growth(self, growth):
        """Sets the growth of this IpamsvcASM.

        :param growth: The growth of this IpamsvcASM.  # noqa: E501
        :type: int
        """

        self._growth = growth

    @property
    def id(self):
        """Gets the id of this IpamsvcASM.  # noqa: E501

        The resource identifier.  # noqa: E501

        :return: The id of this IpamsvcASM.  # noqa: E501
        :rtype: str
        """
        return self._id

    @id.setter
    def id(self, id):
        """Sets the id of this IpamsvcASM.

        The resource identifier.  # noqa: E501

        :param id: The id of this IpamsvcASM.  # noqa: E501
        :type: str
        """

        self._id = id

    @property
    def lookahead(self):
        """Gets the lookahead of this IpamsvcASM.  # noqa: E501

        RO: either the value from the ASM configuration or -1 if the estimate is that utilization will not exceed the threshold.  # noqa: E501

        :return: The lookahead of this IpamsvcASM.  # noqa: E501
        :rtype: int
        """
        return self._lookahead

    @lookahead.setter
    def lookahead(self, lookahead):
        """Sets the lookahead of this IpamsvcASM.

        RO: either the value from the ASM configuration or -1 if the estimate is that utilization will not exceed the threshold.  # noqa: E501

        :param lookahead: The lookahead of this IpamsvcASM.  # noqa: E501
        :type: int
        """

        self._lookahead = lookahead

    @property
    def range_end(self):
        """Gets the range_end of this IpamsvcASM.  # noqa: E501

        RW: The end IP Address of the range.  # noqa: E501

        :return: The range_end of this IpamsvcASM.  # noqa: E501
        :rtype: str
        """
        return self._range_end

    @range_end.setter
    def range_end(self, range_end):
        """Sets the range_end of this IpamsvcASM.

        RW: The end IP Address of the range.  # noqa: E501

        :param range_end: The range_end of this IpamsvcASM.  # noqa: E501
        :type: str
        """

        self._range_end = range_end

    @property
    def range_id(self):
        """Gets the range_id of this IpamsvcASM.  # noqa: E501

        The resource identifier.  # noqa: E501

        :return: The range_id of this IpamsvcASM.  # noqa: E501
        :rtype: str
        """
        return self._range_id

    @range_id.setter
    def range_id(self, range_id):
        """Sets the range_id of this IpamsvcASM.

        The resource identifier.  # noqa: E501

        :param range_id: The range_id of this IpamsvcASM.  # noqa: E501
        :type: str
        """

        self._range_id = range_id

    @property
    def range_start(self):
        """Gets the range_start of this IpamsvcASM.  # noqa: E501

        RW: The start IP Address of the range.  # noqa: E501

        :return: The range_start of this IpamsvcASM.  # noqa: E501
        :rtype: str
        """
        return self._range_start

    @range_start.setter
    def range_start(self, range_start):
        """Sets the range_start of this IpamsvcASM.

        RW: The start IP Address of the range.  # noqa: E501

        :param range_start: The range_start of this IpamsvcASM.  # noqa: E501
        :type: str
        """

        self._range_start = range_start

    @property
    def subnet_address(self):
        """Gets the subnet_address of this IpamsvcASM.  # noqa: E501

        :return: The subnet_address of this IpamsvcASM.  # noqa: E501
        :rtype: str
        """
        return self._subnet_address

    @subnet_address.setter
    def subnet_address(self, subnet_address):
        """Sets the subnet_address of this IpamsvcASM.

        :param subnet_address: The subnet_address of this IpamsvcASM.  # noqa: E501
        :type: str
        """

        self._subnet_address = subnet_address

    @property
    def subnet_cidr(self):
        """Gets the subnet_cidr of this IpamsvcASM.  # noqa: E501

        RO: The CIDR of the Subnet object.  # noqa: E501

        :return: The subnet_cidr of this IpamsvcASM.  # noqa: E501
        :rtype: int
        """
        return self._subnet_cidr

    @subnet_cidr.setter
    def subnet_cidr(self, subnet_cidr):
        """Sets the subnet_cidr of this IpamsvcASM.

        RO: The CIDR of the Subnet object.  # noqa: E501

        :param subnet_cidr: The subnet_cidr of this IpamsvcASM.  # noqa: E501
        :type: int
        """

        self._subnet_cidr = subnet_cidr

    @property
    def subnet_direction(self):
        """Gets the subnet_direction of this IpamsvcASM.  # noqa: E501

        :return: The subnet_direction of this IpamsvcASM.  # noqa: E501
        :rtype: str
        """
        return self._subnet_direction

    @subnet_direction.setter
    def subnet_direction(self, subnet_direction):
        """Sets the subnet_direction of this IpamsvcASM.

        :param subnet_direction: The subnet_direction of this IpamsvcASM.  # noqa: E501
        :type: str
        """

        self._subnet_direction = subnet_direction

    @property
    def subnet_id(self):
        """Gets the subnet_id of this IpamsvcASM.  # noqa: E501

        The resource identifier.  # noqa: E501

        :return: The subnet_id of this IpamsvcASM.  # noqa: E501
        :rtype: str
        """
        return self._subnet_id

    @subnet_id.setter
    def subnet_id(self, subnet_id):
        """Sets the subnet_id of this IpamsvcASM.

        The resource identifier.  # noqa: E501

        :param subnet_id: The subnet_id of this IpamsvcASM.  # noqa: E501
        :type: str
        """
        if subnet_id is None:
            raise ValueError("Invalid value for `subnet_id`, must not be `None`")  # noqa: E501

        self._subnet_id = subnet_id

    @property
    def subnet_range(self):
        """Gets the subnet_range of this IpamsvcASM.  # noqa: E501

        The resource identifier.  # noqa: E501

        :return: The subnet_range of this IpamsvcASM.  # noqa: E501
        :rtype: str
        """
        return self._subnet_range

    @subnet_range.setter
    def subnet_range(self, subnet_range):
        """Sets the subnet_range of this IpamsvcASM.

        The resource identifier.  # noqa: E501

        :param subnet_range: The subnet_range of this IpamsvcASM.  # noqa: E501
        :type: str
        """

        self._subnet_range = subnet_range

    @property
    def subnet_range_end(self):
        """Gets the subnet_range_end of this IpamsvcASM.  # noqa: E501

        :return: The subnet_range_end of this IpamsvcASM.  # noqa: E501
        :rtype: str
        """
        return self._subnet_range_end

    @subnet_range_end.setter
    def subnet_range_end(self, subnet_range_end):
        """Sets the subnet_range_end of this IpamsvcASM.

        :param subnet_range_end: The subnet_range_end of this IpamsvcASM.  # noqa: E501
        :type: str
        """

        self._subnet_range_end = subnet_range_end

    @property
    def subnet_range_start(self):
        """Gets the subnet_range_start of this IpamsvcASM.  # noqa: E501

        :return: The subnet_range_start of this IpamsvcASM.  # noqa: E501
        :rtype: str
        """
        return self._subnet_range_start

    @subnet_range_start.setter
    def subnet_range_start(self, subnet_range_start):
        """Sets the subnet_range_start of this IpamsvcASM.

        :param subnet_range_start: The subnet_range_start of this IpamsvcASM.  # noqa: E501
        :type: str
        """

        self._subnet_range_start = subnet_range_start

    @property
    def suppress(self):
        """Gets the suppress of this IpamsvcASM.  # noqa: E501

        :return: The suppress of this IpamsvcASM.  # noqa: E501
        :rtype: str
        """
        return self._suppress

    @suppress.setter
    def suppress(self, suppress):
        """Sets the suppress of this IpamsvcASM.

        :param suppress: The suppress of this IpamsvcASM.  # noqa: E501
        :type: str
        """

        self._suppress = suppress

    @property
    def suppress_time(self):
        """Gets the suppress_time of this IpamsvcASM.  # noqa: E501

        :return: The suppress_time of this IpamsvcASM.  # noqa: E501
        :rtype: int
        """
        return self._suppress_time

    @suppress_time.setter
    def suppress_time(self, suppress_time):
        """Sets the suppress_time of this IpamsvcASM.

        :param suppress_time: The suppress_time of this IpamsvcASM.  # noqa: E501
        :type: int
        """

        self._suppress_time = suppress_time

    @property
    def threshold_utilization(self):
        """Gets the threshold_utilization of this IpamsvcASM.  # noqa: E501

        :return: The threshold_utilization of this IpamsvcASM.  # noqa: E501
        :rtype: int
        """
        return self._threshold_utilization

    @threshold_utilization.setter
    def threshold_utilization(self, threshold_utilization):
        """Sets the threshold_utilization of this IpamsvcASM.

        :param threshold_utilization: The threshold_utilization of this IpamsvcASM.  # noqa: E501
        :type: int
        """

        self._threshold_utilization = threshold_utilization

    @property
    def update(self):
        """Gets the update of this IpamsvcASM.  # noqa: E501

        :return: The update of this IpamsvcASM.  # noqa: E501
        :rtype: str
        """
        return self._update

    @update.setter
    def update(self, update):
        """Sets the update of this IpamsvcASM.

        :param update: The update of this IpamsvcASM.  # noqa: E501
        :type: str
        """

        self._update = update

    @property
    def utilization(self):
        """Gets the utilization of this IpamsvcASM.  # noqa: E501

        :return: The utilization of this IpamsvcASM.  # noqa: E501
        :rtype: int
        """
        return self._utilization

    @utilization.setter
    def utilization(self, utilization):
        """Sets the utilization of this IpamsvcASM.

        :param utilization: The utilization of this IpamsvcASM.  # noqa: E501
        :type: int
        """

        self._utilization = utilization

    def to_dict(self):
        """Returns the model properties as a dict"""
        result = {}

        for attr, _ in six.iteritems(self.swagger_types):
            value = getattr(self, attr)
            if isinstance(value, list):
                result[attr] = list(map(
                    lambda x: x.to_dict() if hasattr(x, "to_dict") else x,
                    value
                ))
            elif hasattr(value, "to_dict"):
                result[attr] = value.to_dict()
            elif isinstance(value, dict):
                result[attr] = dict(map(
                    lambda item: (item[0], item[1].to_dict())
                    if hasattr(item[1], "to_dict") else item,
                    value.items()
                ))
            else:
                result[attr] = value
        if issubclass(IpamsvcASM, dict):
            for key, value in self.items():
                result[key] = value

        return result

    def to_str(self):
        """Returns the string representation of the model"""
        return pprint.pformat(self.to_dict())

    def __repr__(self):
        """For `print` and `pprint`"""
        return self.to_str()

    def __eq__(self, other):
        """Returns true if both objects are equal"""
        if not isinstance(other, IpamsvcASM):
            return False

        return self.__dict__ == other.__dict__

    def __ne__(self, other):
        """Returns true if both objects are not equal"""
        return not self == other