import re
import copy
import json
from abc import abstractmethod

from .base_entity import ItemEntity
from .lib import (
    NOT_SET,
    STRING_TYPE,
    OverrideState
)
from .exceptions import (
    DefaultsNotDefined,
    StudioDefaultsNotDefined,
    EntitySchemaError
)

from openpype.settings.constants import METADATA_KEYS

class EndpointEntity(ItemEntity):
    """Entity that is a endpoint of settings value.

    In most of cases endpoint entity does not have children entities and if has
    then they are dynamic and can be removed/created. Is automatically set as
    group if any parent is not, that is because of override metadata.
    """

    def __init__(self, *args, **kwargs):
        super(EndpointEntity, self).__init__(*args, **kwargs)

        if (
            not (self.group_item is not None or self.is_group)
            and not (self.is_dynamic_item or self.is_in_dynamic_item)
        ):
            self.is_group = True

    def schema_validations(self):
        """Validation of entity schema and schema hierarchy."""
        # Default value when even defaults are not filled must be set
        if self.value_on_not_set is NOT_SET:
            reason = "Attribute `value_on_not_set` is not filled. {}".format(
                self.__class__.__name__
            )
            raise EntitySchemaError(self, reason)

        super(EndpointEntity, self).schema_validations()

    def collect_dynamic_schema_entities(self, collector):
        if self.is_dynamic_schema_node:
            collector.add_entity(self)

    @abstractmethod
    def _settings_value(self):
        pass

    def collect_static_entities_by_path(self):
        if self.is_dynamic_item or self.is_in_dynamic_item:
            return {}
        return {self.path: self}

    def settings_value(self):
        if self._override_state is OverrideState.NOT_DEFINED:
            return NOT_SET

        if self.is_group:
            if self._override_state is OverrideState.STUDIO:
                if not self.has_studio_override:
                    return NOT_SET
            elif self._override_state is OverrideState.PROJECT:
                if not self.has_project_override:
                    return NOT_SET
        return self._settings_value()

    def on_change(self):
        for callback in self.on_change_callbacks:
            callback()

        if self.require_restart_on_change:
            if self.require_restart:
                self.root_item.add_item_require_restart(self)
            else:
                self.root_item.remove_item_require_restart(self)
        self.parent.on_child_change(self)

    @property
    def require_restart(self):
        return self.has_unsaved_changes

    def update_default_value(self, value, log_invalid_types=True):
        self._default_log_invalid_types = log_invalid_types
        value = self._check_update_value(
            value, "default", log_invalid_types
        )
        self._default_value = value
        self.has_default_value = value is not NOT_SET

    def update_studio_value(self, value, log_invalid_types=True):
        self._studio_log_invalid_types = log_invalid_types
        value = self._check_update_value(
            value, "studio override", log_invalid_types
        )
        self._studio_override_value = value
        self.had_studio_override = bool(value is not NOT_SET)

    def update_project_value(self, value, log_invalid_types=True):
        self._project_log_invalid_types = log_invalid_types
        value = self._check_update_value(
            value, "project override", log_invalid_types
        )
        self._project_override_value = value
        self.had_project_override = bool(value is not NOT_SET)

class InputEntity(EndpointEntity):
    """Endpoint entity without children."""
    def __init__(self, *args, **kwargs):
        super(InputEntity, self).__init__(*args, **kwargs)
        self._value_is_modified = False
        self._current_value = NOT_SET

    def __eq__(self, other):
        if isinstance(other, ItemEntity):
            return self.value == other.value
        return self.value == other

    def has_child_with_key(self, key):
        return False

    def get_child_path(self, child_obj):
        raise TypeError("{} can't have children".format(
            self.__class__.__name__
        ))

    def schema_validations(self):
        # Input entity must have file parent.
        if (
            not self.is_dynamic_schema_node
            and not self.is_in_dynamic_schema_node
            and self.file_item is None
        ):
            raise EntitySchemaError(self, "Missing parent file entity.")

        super(InputEntity, self).schema_validations()

    @property
    def value(self):
        """Entity's value without metadata."""
        return self._current_value

    @property
    def require_restart(self):
        return self._value_is_modified

    def _settings_value(self):
        return copy.deepcopy(self.value)

    def set(self, value):
        """Change value."""
        self._current_value = self.convert_to_valid_type(value)
        self._on_value_change()

    def _on_value_change(self):
        # Change has_project_override attr value
        if self._override_state is OverrideState.PROJECT:
            self._has_project_override = True

        elif self._override_state is OverrideState.STUDIO:
            self._has_studio_override = True

        self.on_change()

    def on_change(self):
        """Callback triggered on change.

        There are cases when this method may be called from other entity.
        """
        value_is_modified = None
        if self._override_state is OverrideState.PROJECT:
            # Only value change
            if (
                self._has_project_override
                and self._project_override_value is not NOT_SET
            ):
                value_is_modified = (
                    self._current_value != self._project_override_value
                )

        if (
            self._override_state is OverrideState.STUDIO
            or value_is_modified is None
        ):
            if (
                self._has_studio_override
                and self._studio_override_value is not NOT_SET
            ):
                value_is_modified = (
                    self._current_value != self._studio_override_value
                )

        if value_is_modified is None:
            value_is_modified = self._current_value != self._default_value

        self._value_is_modified = value_is_modified

        super(InputEntity, self).on_change()

    def on_child_change(self, child_obj):
        raise TypeError("Input entities do not contain children.")

    @property
    def has_unsaved_changes(self):
        if self._override_state is OverrideState.NOT_DEFINED:
            return False

        if self._value_is_modified:
            return True

        # These may be stored on value change
        if self._override_state is OverrideState.DEFAULTS:
            if not self.has_default_value:
                return True

        elif self._override_state is OverrideState.STUDIO:
            if self._has_studio_override != self.had_studio_override:
                return True

            if not self._has_studio_override and not self.has_default_value:
                return True

        elif self._override_state is OverrideState.PROJECT:
            if self._has_project_override != self.had_project_override:
                return True

            if (
                not self._has_project_override
                and not self._has_studio_override
                and not self.has_default_value
            ):
                return True
        return False

    def set_override_state(self, state, ignore_missing_defaults):
        # Trigger override state change of root if is not same
        if self.root_item.override_state is not state:
            self.root_item.set_override_state(state)
            return

        self._override_state = state
        self._ignore_missing_defaults = ignore_missing_defaults
        # Ignore if is dynamic item and use default in that case
        if not self.is_dynamic_item and not self.is_in_dynamic_item:
            if state > OverrideState.DEFAULTS:
                if (
                    not self.has_default_value
                    and not ignore_missing_defaults
                ):
                    raise DefaultsNotDefined(self)

            elif state > OverrideState.STUDIO:
                if (
                    not self.had_studio_override
                    and not ignore_missing_defaults
                ):
                    raise StudioDefaultsNotDefined(self)

        if state is OverrideState.STUDIO:
            self._has_studio_override = (
                self._studio_override_value is not NOT_SET
            )

        elif state is OverrideState.PROJECT:
            self._has_project_override = (
                self._project_override_value is not NOT_SET
            )
            self._has_studio_override = self.had_studio_override

        value = NOT_SET
        if state is OverrideState.PROJECT:
            value = self._project_override_value

        if value is NOT_SET and state >= OverrideState.STUDIO:
            value = self._studio_override_value

        if value is NOT_SET and state >= OverrideState.DEFAULTS:
            value = self._default_value

        if value is NOT_SET:
            value = self.value_on_not_set
            self.has_default_value = False
        else:
            self.has_default_value = True
        self._value_is_modified = False

        self._current_value = copy.deepcopy(value)

    def _discard_changes(self, on_change_trigger=None):
        if not self._can_discard_changes:
            return

        self._value_is_modified = False
        if self._override_state >= OverrideState.PROJECT:
            self._has_project_override = self.had_project_override
            if self.had_project_override:
                self._current_value = copy.deepcopy(
                    self._project_override_value
                )
                on_change_trigger.append(self.on_change)
                return

        if self._override_state >= OverrideState.STUDIO:
            self._has_studio_override = self.had_studio_override
            if self.had_studio_override:
                self._current_value = copy.deepcopy(
                    self._studio_override_value
                )
                on_change_trigger.append(self.on_change)
                return

        if self._override_state >= OverrideState.DEFAULTS:
            if self.has_default_value:
                value = self._default_value
            else:
                value = self.value_on_not_set
            self._current_value = copy.deepcopy(value)
            on_change_trigger.append(self.on_change)
            return

        raise NotImplementedError("BUG: Unexcpected part of code.")

    def _add_to_studio_default(self, _on_change_trigger):
        self._has_studio_override = True
        self.on_change()

    def _remove_from_studio_default(self, on_change_trigger):
        if not self._can_remove_from_studio_default:
            return

        value = self._default_value
        if value is NOT_SET:
            value = self.value_on_not_set
        self._current_value = copy.deepcopy(value)

        self._has_studio_override = False
        self._value_is_modified = False

        on_change_trigger.append(self.on_change)

    def _add_to_project_override(self, _on_change_trigger):
        self._has_project_override = True
        self.on_change()

    def _remove_from_project_override(self, on_change_trigger):
        if not self._can_remove_from_project_override:
            return

        self._has_project_override = False
        if self._has_studio_override:
            current_value = self._studio_override_value
        elif self.has_default_value:
            current_value = self._default_value
        else:
            current_value = self.value_on_not_set

        self._current_value = copy.deepcopy(current_value)
        on_change_trigger.append(self.on_change)

class NumberEntity(InputEntity):
    schema_types = ["number"]
    float_number_regex = re.compile(r"^\d+\.\d+$")
    int_number_regex = re.compile(r"^\d+$")

    def _item_initialization(self):
        self.minimum = self.schema_data.get("minimum", -99999)
        self.maximum = self.schema_data.get("maximum", 99999)
        self.decimal = self.schema_data.get("decimal", 0)

        value_on_not_set = self.schema_data.get("default", 0)
        if self.decimal:
            valid_value_types = (float, )
            value_on_not_set = float(value_on_not_set)
        else:
            valid_value_types = (int, )
            value_on_not_set = int(value_on_not_set)
        self.valid_value_types = valid_value_types
        self.value_on_not_set = value_on_not_set

        # UI specific attributes
        self.show_slider = self.schema_data.get("show_slider", False)
        steps = self.schema_data.get("steps", None)
        # Make sure that steps are not set to `0`
        if steps == 0:
            steps = None
        self.steps = steps

    def _convert_to_valid_type(self, value):
        if isinstance(value, str):
            new_value = None
            if self.float_number_regex.match(value):
                new_value = float(value)
            elif self.int_number_regex.match(value):
                new_value = int(value)

            if new_value is not None:
                self.log.info("{} - Converted str {} to {} {}".format(
                    self.path, value, type(new_value).__name__, new_value
                ))
                value = new_value

        if self.decimal:
            if isinstance(value, float):
                return value
            if isinstance(value, int):
                return float(value)
        else:
            if isinstance(value, int):
                return value
            if isinstance(value, float):
                new_value = int(value)
                if new_value != value:
                    self.log.info("{} - Converted float {} to int {}".format(
                        self.path, value, new_value
                    ))
                return new_value
        return NOT_SET

class BoolEntity(InputEntity):
    schema_types = ["boolean"]

    def _item_initialization(self):
        self.valid_value_types = (bool, )
        value_on_not_set = self.convert_to_valid_type(
            self.schema_data.get("default", True)
        )
        self.value_on_not_set = value_on_not_set

class TextEntity(InputEntity):
    schema_types = ["text"]

    def _item_initialization(self):
        self.valid_value_types = (STRING_TYPE, )
        self.value_on_not_set = self.convert_to_valid_type(
            self.schema_data.get("default", "")
        )

        # GUI attributes
        self.multiline = self.schema_data.get("multiline", False)
        self.placeholder_text = self.schema_data.get("placeholder")
        self.value_hints = self.schema_data.get("value_hints") or []
        self.minimum_lines_count = (
            self.schema_data.get("minimum_lines_count") or 0)

    def schema_validations(self):
        if self.multiline and self.value_hints:
            reason = (
                "TextEntity entity can't use value hints"
                " for multiline input (yet)."
            )
            raise EntitySchemaError(self, reason)
        super(TextEntity, self).schema_validations()

    def _convert_to_valid_type(self, value):
        # Allow numbers converted to string
        if isinstance(value, (int, float)):
            return str(value)
        return NOT_SET

class PathInput(InputEntity):
    schema_types = ["path-input"]

    def _item_initialization(self):
        self.valid_value_types = (STRING_TYPE, )
        self.value_on_not_set = ""

        # GUI attributes
        self.placeholder_text = self.schema_data.get("placeholder")

    def set(self, value):
        # Strip value
        super(PathInput, self).set(value.strip())

    def set_override_state(self, state, ignore_missing_defaults):
        super(PathInput, self).set_override_state(
            state, ignore_missing_defaults
        )
        # Strip current value
        self._current_value = self._current_value.strip()

class RawJsonEntity(InputEntity):
    schema_types = ["raw-json"]

    def _item_initialization(self):
        # Schema must define if valid value is dict or list
        store_as_string = self.schema_data.get("store_as_string", False)
        is_list = self.schema_data.get("is_list", False)
        if is_list:
            valid_value_types = (list, )
            value_on_not_set = []
        else:
            valid_value_types = (dict, )
            value_on_not_set = {}

        self.store_as_string = store_as_string

        self._is_list = is_list
        self.valid_value_types = valid_value_types
        self.value_on_not_set = value_on_not_set

        self.default_metadata = {}
        self.studio_override_metadata = {}
        self.project_override_metadata = {}

    @property
    def is_list(self):
        return self._is_list

    @property
    def is_dict(self):
        return not self._is_list

    def set(self, value):
        new_value = self.convert_to_valid_type(value)

        if isinstance(new_value, dict):
            for key in METADATA_KEYS:
                if key in new_value:
                    new_value.pop(key)
        self._current_value = new_value
        self._on_value_change()

    @property
    def metadata(self):
        return {}

    @property
    def has_unsaved_changes(self):
        result = super(RawJsonEntity, self).has_unsaved_changes
        if not result:
            result = self.metadata != self._metadata_for_current_state()
        return result

    def _convert_to_valid_type(self, value):
        if isinstance(value, STRING_TYPE):
            try:
                return json.loads(value)
            except Exception:
                pass
        return super(RawJsonEntity, self)._convert_to_valid_type(value)

    def _metadata_for_current_state(self):
        if (
            self._override_state is OverrideState.PROJECT
            and self._project_override_value is not NOT_SET
        ):
            return self.project_override_metadata

        if (
            self._override_state >= OverrideState.STUDIO
            and self._studio_override_value is not NOT_SET
        ):
            return self.studio_override_metadata

        return self.default_metadata

    def _settings_value(self):
        value = super(RawJsonEntity, self)._settings_value()
        if self.store_as_string:
            return json.dumps(value)
        return value

    def _prepare_value(self, value):
        metadata = {}
        if isinstance(value, dict):
            value = copy.deepcopy(value)
            for key in METADATA_KEYS:
                if key in value:
                    metadata[key] = value.pop(key)
        return value, metadata

    def update_default_value(self, value, log_invalid_types=True):
        value = self._check_update_value(value, "default", log_invalid_types)
        self.has_default_value = value is not NOT_SET
        value, metadata = self._prepare_value(value)
        self._default_value = value
        self.default_metadata = metadata

    def update_studio_value(self, value, log_invalid_types=True):
        value = self._check_update_value(
            value, "studio override", log_invalid_types
        )
        self.had_studio_override = value is not NOT_SET
        value, metadata = self._prepare_value(value)
        self._studio_override_value = value
        self.studio_override_metadata = metadata

    def update_project_value(self, value, log_invalid_types=True):
        value = self._check_update_value(
            value, "project override", log_invalid_types
        )
        self.had_project_override = value is not NOT_SET
        value, metadata = self._prepare_value(value)
        self._project_override_value = value
        self.project_override_metadata = metadata


#!/usr/bin/env python
# coding: utf-8

# # MNIST with Recurrent Neural Networks
# This program runs in about 46.033 seconds (Windows 11, i7, 16 GB)

# ## Prerequisites
#
# Install the following packages

try:
    from cloudmesh.common.StopWatch import StopWatch
except:  # noqa: E722
    get_ipython().system(' pip install cloudmesh-common')
    from cloudmesh.common.StopWatch import StopWatch

# ## Exporting Output Graphs

def save(file):
    if os.path.exists("images"):
        pass
    else:
        Shell.mkdir("images")
    plot_model(file, to_file=f'images/rnn-mnist.png', show_shapes=True)
    plot_model(file, to_file=f'images/rnn-mnist.pdf', show_shapes=True)

# ## Import Libraries

StopWatch.start("total")
StopWatch.start("import")
StopWatch.progress(0)

import numpy as np    # noqa: E402
from keras.models import Sequential    # noqa: E402
from keras.layers import Dense, Activation, SimpleRNN    # noqa: E402
from keras.utils import to_categorical, plot_model    # noqa: E402
from keras.datasets import mnist    # noqa: E402
from cloudmesh.common.StopWatch import StopWatch    # noqa: E402
from cloudmesh.common.Shell import Shell    # noqa: E402
import os    # noqa: E402

StopWatch.stop("import")
StopWatch.progress(7)

# ## Download Data and Pre-Process

StopWatch.start("data-load")

(x_train, y_train), (x_test, y_test) = mnist.load_data()

StopWatch.stop("data-load")
StopWatch.progress(8)

StopWatch.start("data-pre-process")

num_labels = len(np.unique(y_train))

y_train = to_categorical(y_train)
y_test = to_categorical(y_test)

image_size = x_train.shape[1]
x_train = np.reshape(x_train,[-1, image_size, image_size])
x_test = np.reshape(x_test,[-1, image_size, image_size])
x_train = x_train.astype('float32') / 255
x_test = x_test.astype('float32') / 255

StopWatch.stop("data-pre-process")
StopWatch.progress(9)

# ## Define Model

StopWatch.start("compile")

input_shape = (image_size, image_size)
batch_size = 128
units = 256
dropout = 0.2

model = Sequential()
model.add(SimpleRNN(units=units,
                    dropout=dropout,
                    input_shape=input_shape, return_sequences=True))
model.add(SimpleRNN(units=units,
                    dropout=dropout,
                    return_sequences=True))
model.add(SimpleRNN(units=units,
                    dropout=dropout,
                    return_sequences=False))
model.add(Dense(num_labels))
model.add(Activation('softmax'))
model.summary()
save(model)

model.compile(loss='categorical_crossentropy',
              optimizer='sgd',
              metrics=['accuracy'])

StopWatch.stop("compile")
StopWatch.progress(10)

# ## Train

StopWatch.start("train")

model.fit(x_train, y_train, epochs=1, batch_size=batch_size)

StopWatch.stop("train")
StopWatch.progress(95)

# ## Test

StopWatch.start("evaluate")

loss, acc = model.evaluate(x_test, y_test, batch_size=batch_size)
print("\nTest accuracy: %.1f%%" % (100.0 * acc))

StopWatch.stop("evaluate")
StopWatch.stop("total")
StopWatch.benchmark()
StopWatch.progress(100)


import sys
import os

sys.path.append(os.path.abspath("."))
sys.dont_write_bytecode = True

__author__ = "bigfatnoob"

import numpy as np

from utils import logger
from utils.lib import O
from misconceptions.common import mongo_driver, props, differences

LOGGER = logger.get_logger(os.path.basename(__file__.split(".")[0]))

class SemanticSummary(O):
  def __init__(self, **kwargs):
    self.sim_score = None
    self.n_mismatched = 0
    self.size_diff = None
    self.row_diff = None
    self.col_diff = None
    self.n_val1_empty = 0
    self.n_val2_empty = 0
    self.n_both_empty = 0
    O.__init__(self, **kwargs)

  def update_summary(self, diff):
    if diff.message == "Mismatched types":
      self.n_mismatched += 1
      return
    elif isinstance(diff, differences.DataFrameDiffMeta) or isinstance(diff, differences.MatrixDiffMeta):
      if diff.row_diff is not None:
        if not self.row_diff: self.row_diff = []
        self.row_diff.append(diff.row_diff)
      if diff.col_diff is not None:
        if not self.col_diff: self.col_diff = []
        self.col_diff.append(diff.col_diff)
    elif isinstance(diff, differences.ArrayDiffMeta):
      if diff.size_diff is not None:
        if not self.size_diff: self.size_diff = []
        self.size_diff.append(diff.size_diff)
    self.n_both_empty += 1 if diff.is_val1_empty and diff.is_val2_empty else 0
    self.n_val1_empty += 1 if diff.is_val1_empty else 0
    self.n_val2_empty += 1 if diff.is_val2_empty else 0
    if not self.sim_score:
      self.sim_score = []
    self.sim_score.append(diff.sim_score)

  def summarize(self):
    report = {}
    if self.sim_score:
      report["semantic_score"] = np.mean(self.sim_score)
    report["n_mismatched"] = self.n_mismatched
    if self.row_diff:
      report["row_diff"] = np.mean(self.row_diff)
    if self.col_diff:
      report["col_diff"] = np.mean(self.col_diff)
    if self.size_diff:
      report["size_diff"] = np.mean(self.size_diff)
    report["n_both_empty"] = self.n_both_empty
    report["n_val1_empty"] = self.n_val1_empty
    report["n_val2_empty"] = self.n_val2_empty
    return report

def update_semantic_scores(start=0, end=None, log_interval=100, limit=0):
  store = mongo_driver.MongoStore(props.DATASET)
  diff_records = store.load_differences(additional_queries={"n_mismatched": {"$exists": False}}, limit=limit)
  n_records = diff_records.count()
  LOGGER.info("Retrieved %d records ..." % n_records)
  semantic_columns = ["semantic_score", "n_mismatched", "row_diff", "col_diff", "size_diff",
                      "n_both_empty", "n_val1_empty", "n_val2_empty"]
  store.create_semantic_indices(semantic_columns)
  for i, diff_record in enumerate(diff_records):
    if i < start or (end and i >= end):
      continue
    if (i + 1) % log_interval == 0:
      LOGGER.info("Processing semantic difference for %d / %d" % (i+1, n_records))
    summary = SemanticSummary()
    for d in diff_record["diff"]:
      diff = differences.DiffMeta.from_dict(d)
      summary.update_summary(diff)
    query = {"_id": diff_record["_id"]}
    updates = summary.summarize()
    store.update_difference(query, updates)

if __name__ == "__main__":
  update_semantic_scores()


#!venv/bin/python

# informal testing of concurrency using gevent

import random
import sys

import gevent

import lolog

log = lolog.get_logger('gevent-test')

def main():
    lolog.init(stream=sys.stdout)

    num_tasks = 10
    tasks = []
    log.info('starting gevent test', num_tasks=num_tasks)
    for idx in range(num_tasks):
        count = random.randint(10, 20)
        tasks.append(gevent.spawn(worker, idx, count))

    gevent.joinall(tasks)

def worker(worker_id, count):
    log.add_local_context('wid1', worker_id)
    log.info('starting worker', count=count)
    for idx in range(1, count + 1):
        log.info('doing some work', iter=idx, wid2=worker_id)
        gevent.sleep(random.uniform(0.001, 0.050))

main()


# -------------------------------------------------------------------------------------------------------------------------------------
# Following code curated for Bio-Diffusion (https://github.com/BioinfoMachineLearning/bio-diffusion):
# -------------------------------------------------------------------------------------------------------------------------------------

import hydra
import signal
import time
import warnings

from importlib.util import find_spec
from pathlib import Path
from typing import Any, Callable, Dict, List, Union

from contextlib import contextmanager
from omegaconf import DictConfig
from pytorch_lightning import Callback
from pytorch_lightning.loggers import LightningLoggerBase
from pytorch_lightning.utilities import rank_zero_only

from src.utils import pylogger, rich_utils

from torchtyping import patch_typeguard
from typeguard import typechecked

patch_typeguard()  # use before @typechecked

log = pylogger.get_pylogger(__name__)

def task_wrapper(task_func: Callable) -> Callable:
    """Optional decorator that wraps the task function in extra utilities.

    Makes multirun more resistant to failure.

    Utilities:
    - Calling the `utils.extras()` before the task is started
    - Calling the `utils.close_loggers()` after the task is finished
    - Logging the exception if occurs
    - Logging the task total execution time
    - Logging the output dir
    """

    def wrap(cfg: DictConfig):

        # apply extra utilities
        extras(cfg)

        # execute the task
        try:
            start_time = time.time()
            metric_dict, object_dict = task_func(cfg=cfg)
        except Exception as ex:
            log.exception("")  # save exception to `.log` file
            raise ex
        finally:
            path = Path(cfg.paths.output_dir, "exec_time.log")
            content = f"'{cfg.task_name}' execution time: {time.time() - start_time} (s)"
            save_file(path, content)  # save task execution time (even if exception occurs)
            close_loggers()  # close loggers (even if exception occurs so multirun won't fail)

        log.info(f"Output dir: {cfg.paths.output_dir}")

        return metric_dict, object_dict

    return wrap

def extras(cfg: DictConfig) -> None:
    """Applies optional utilities before the task is started.

    Utilities:
    - Ignoring python warnings
    - Setting tags from command line
    - Rich config printing
    """

    # return if no `extras` config
    if not cfg.get("extras"):
        log.warning("Extras config not found! <cfg.extras=null>")
        return

    # disable python warnings
    if cfg.extras.get("ignore_warnings"):
        log.info("Disabling python warnings! <cfg.extras.ignore_warnings=True>")
        warnings.filterwarnings("ignore")

    # prompt user to input tags from command line if none are provided in the config
    if cfg.extras.get("enforce_tags"):
        log.info("Enforcing tags! <cfg.extras.enforce_tags=True>")
        rich_utils.enforce_tags(cfg, save_to_file=True)

    # pretty print config tree using Rich library
    if cfg.extras.get("print_config"):
        log.info("Printing config tree with Rich! <cfg.extras.print_config=True>")
        rich_utils.print_config_tree(cfg, resolve=True, save_to_file=True)

@rank_zero_only
def save_file(path: str, content: str) -> None:
    """Save file in rank zero mode (only on one process in multi-GPU setup)."""
    with open(path, "w+") as file:
        file.write(content)

def instantiate_callbacks(callbacks_cfg: DictConfig) -> List[Callback]:
    """Instantiates callbacks from config."""
    callbacks: List[Callback] = []

    if not callbacks_cfg:
        log.warning("Callbacks config is empty.")
        return callbacks

    if not isinstance(callbacks_cfg, DictConfig):
        raise TypeError("Callbacks config must be a DictConfig!")

    for _, cb_conf in callbacks_cfg.items():
        if isinstance(cb_conf, DictConfig) and "_target_" in cb_conf:
            log.info(f"Instantiating callback <{cb_conf._target_}>")
            callbacks.append(hydra.utils.instantiate(cb_conf))

    return callbacks

def instantiate_loggers(logger_cfg: DictConfig) -> List[LightningLoggerBase]:
    """Instantiates loggers from config."""
    logger: List[LightningLoggerBase] = []

    if not logger_cfg:
        log.warning("Logger config is empty.")
        return logger

    if not isinstance(logger_cfg, DictConfig):
        raise TypeError("Logger config must be a DictConfig!")

    for _, lg_conf in logger_cfg.items():
        if isinstance(lg_conf, DictConfig) and "_target_" in lg_conf:
            log.info(f"Instantiating logger <{lg_conf._target_}>")
            logger.append(hydra.utils.instantiate(lg_conf))

    return logger

@rank_zero_only
def log_hyperparameters(object_dict: dict) -> None:
    """Controls which config parts are saved by lightning loggers.

    Additionally saves:
    - Number of model parameters
    """

    hparams = {}

    cfg = object_dict["cfg"]
    model = object_dict["model"]
    trainer = object_dict["trainer"]

    if not trainer.logger:
        log.warning("Logger not found! Skipping hyperparameter logging...")
        return

    hparams["model"] = cfg["model"]

    # save number of model parameters
    hparams["model/params/total"] = sum(p.numel() for p in model.parameters())
    hparams["model/params/trainable"] = sum(
        p.numel() for p in model.parameters() if p.requires_grad
    )
    hparams["model/params/non_trainable"] = sum(
        p.numel() for p in model.parameters() if not p.requires_grad
    )

    hparams["datamodule"] = cfg["datamodule"]
    hparams["trainer"] = cfg["trainer"]

    hparams["callbacks"] = cfg.get("callbacks")
    hparams["extras"] = cfg.get("extras")

    hparams["task_name"] = cfg.get("task_name")
    hparams["tags"] = cfg.get("tags")
    hparams["ckpt_path"] = cfg.get("ckpt_path")
    hparams["seed"] = cfg.get("seed")

    # send hparams to all loggers
    trainer.logger.log_hyperparams(hparams)

def get_metric_value(metric_dict: dict, metric_name: str) -> float:
    """Safely retrieves value of the metric logged in LightningModule."""

    if not metric_name:
        log.info("Metric name is None! Skipping metric value retrieval...")
        return None

    if metric_name not in metric_dict:
        raise Exception(
            f"Metric value not found! <metric_name={metric_name}>\n"
            "Make sure metric name logged in LightningModule is correct!\n"
            "Make sure `optimized_metric` name in `hparams_search` config is correct!"
        )

    metric_value = metric_dict[metric_name].item()
    log.info(f"Retrieved metric value! <{metric_name}={metric_value}>")

    return metric_value

def close_loggers() -> None:
    """Makes sure all loggers closed properly (prevents logging failure during multirun)."""

    log.info("Closing loggers...")

    if find_spec("wandb"):  # if wandb is installed
        import wandb

        if wandb.run:
            log.info("Closing wandb!")
            wandb.finish()

@typechecked
def replace_dict_str_values_unconditionally(
    obj: Any,
    unconditional_key_value_replacements: Dict[str, str]
) -> Any:
    """Using a replacement mapping, replace all string values found in `obj` recursively."""
    if isinstance(obj, dict):
        return {key: replace_dict_str_values_unconditionally(val, unconditional_key_value_replacements) for key, val in obj.items()}
    elif isinstance(obj, list):
        if len(obj) > 0 and isinstance(obj[0], str):
            new_obj_list = []
            for o in obj:
                filled_obj = o
                for u, r in unconditional_key_value_replacements.items():
                    filled_obj = filled_obj.replace(u, r)
                new_obj_list.append(filled_obj)
            return new_obj_list
        elif len(obj) > 0 and isinstance(obj[0], dict):
            return [
                {
                    key: replace_dict_str_values_unconditionally(val, unconditional_key_value_replacements)
                    for key, val in o.items()
                }
                for o in obj
            ]
        else:
            return obj
    elif isinstance(obj, str):
        filled_obj = obj
        for u, r in unconditional_key_value_replacements.items():
            filled_obj = filled_obj.replace(u, r)
        return filled_obj
    else:
        return obj

def replace_dict_str_values_conditionally_for_dicts(
    obj: dict,
    conditional_key_value_replacements: Dict[str, Union[str, List[str]]]
):
    """Using a replacement mapping for dictionaries, recursively replace all string values found in `obj` that correspond to keys in the mapping."""
    for k in obj.keys():
        if k in conditional_key_value_replacements:
            obj[k] = conditional_key_value_replacements[k]
    return {key: replace_dict_str_values_conditionally(val, conditional_key_value_replacements) for key, val in obj.items()}

@typechecked
def replace_dict_str_values_conditionally(
    obj: Any,
    conditional_key_value_replacements: Dict[str, Union[str, List[str]]]
) -> Any:
    """Using a replacement mapping, recursively replace all string values found in `obj` that correspond to keys in the mapping."""
    if isinstance(obj, dict):
        return replace_dict_str_values_conditionally_for_dicts(
            obj, conditional_key_value_replacements=conditional_key_value_replacements
        )
    elif isinstance(obj, list):
        new_obj_list = []
        for o in obj:
            if isinstance(o, dict):
                new_obj_list.append(
                    replace_dict_str_values_conditionally_for_dicts(
                        o, conditional_key_value_replacements=conditional_key_value_replacements
                    )
                )
            else:
                new_obj_list.append(o)
        return new_obj_list
    else:
        return obj

class TimeoutException(Exception):
    pass

@contextmanager
@typechecked
def time_limit(seconds: int):
    def signal_handler(signum, frame):
        raise TimeoutException("Timed out!")
    signal.signal(signal.SIGALRM, signal_handler)
    signal.alarm(seconds)
    try:
        yield
    finally:
        signal.alarm(0)


from django.urls import include, path
from . import views

urlpatterns = [
    path('', views.home, name='crypto'),
    path('Collection/<int:pk>/Details/', views.details, name="websites"),
    path('Collection/<int:pk>/Edit/', views.edit, name="editWebsites"),
    path('Collection/<int:pk>/Delete/', views.delete, name="deleteWebsite"),
    path('Collection/', views.index, name="cryptoList"),
    path('AddToCollection/', views.add_collection, name="addToList"),
    path('getRates/<str:name>', views.get_bit, name="getRates")
]


from flask import Flask
from flask_assets import Environment, Bundle
from flask.cli import AppGroup
import os

from PANEL.model import db

app = Flask(__name__)

def config_db(db_url='sqlite:///db/panel.db'):
    app.config['SQLALCHEMY_DATABASE_URI'] = db_url
    app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
    if app.config.get('PROFILE', False): # pragma: no cover
        from werkzeug.contrib.profiler import ProfilerMiddleware
        app.wsgi_app = ProfilerMiddleware(app.wsgi_app, restrictions=[30])
    app.secret_key = "#########"
    db.init_app(app)

def config_sass():
    #Converting Sass file to Css
    scss = Bundle('scss/main.scss', filters='pyscss', output='styles/css/main.css')
    #SCSS Connection
    assets = Environment(app)
    # assets.url= app.static_url_path
    assets.debug=True
    assets.register('scss_main', scss)

config_sass()
config_db()

from PANEL import routes

db_cli = AppGroup('db')

@db_cli.command('init')
def db_init():
    try: os.mkdir('PANEL/db')
    except: pass
    from PANEL.model import create_db
    create_db()

app.cli.add_command(db_cli)


import requests
from datetime import datetime
import logging

def available_symbols(headers):
    url = "https://fixer-fixer-currency-v1.p.rapidapi.com/symbols"
    symbols = requests.get(url=url, headers=headers).json().get('symbols').keys()
    for symbol in symbols:
        yield symbol

def remove_wrong_symbols(symbols, all_symbols):
    s1 = set(symbols)
    s2 = set(all_symbols)
    s_result = s1 & s2

    if len(s1 - s_result):
        print(f'Remove {s1 - s_result}, does not exists')

    return s_result

def request(base: str, symbols: list, headers: dict) -> dict:
    logger = logging.getLogger(__name__)

    url = "https://fixer-fixer-currency-v1.p.rapidapi.com/latest"

    availables = list(available_symbols(headers))
    symbols = ','.join(remove_wrong_symbols(symbols, availables))
    if base not in availables:
        raise ValueError(f'base {base} does not exists, replace by {availables}')

    logger.info(f"Fetching curreny of {symbols} use {base} as base")
    querystring = {"base": base, "symbols": symbols}
    return requests.get(url=url, headers=headers, params=querystring).json()

def clean_resquest(dict) -> dict:
    if dict.get('success') == True and dict.get('success') is not None:
        return {"timestamp": datetime.utcfromtimestamp(dict.get('timestamp')).strftime('%Y-%m-%d %H:%M:%S'),
                "base": dict.get('base'),
                "rates": dict.get('rates')
                }
    else:
        raise ValueError(f'Wrong dict {dict}')

def execute(base: str, symbols: list, headers: dict) -> dict:
    result = request(base, symbols, headers)
    return clean_resquest(result)


from __future__ import absolute_import

import argparse
import os
import sys
# sys.path.append(".")
import time

import torch
from tensorboardX import SummaryWriter

from metrics.evaluation import evaluate
from metrics.vae_metrics import VaeEvaluator
from struct_self.dataset import Dataset
from struct_self.dataset import to_example
from utils.config_utils import dict_to_args
from utils.config_utils import yaml_load_dict
from utils.vae_utils import get_eval_dir
from utils.vae_utils import get_exp_info
from utils.vae_utils import create_model
from utils.vae_utils import load_data
from utils.vae_utils import load_model
from utils.vae_utils import log_tracker
from utils.vae_utils import lr_schedule

def train_ae(main_args, model_args, model=None):
    train_set, dev_set = load_data(main_args)
    model, optimizer, vocab = create_model(main_args, model_args, model)
    print('begin training, %d training examples, %d dev examples' % (len(train_set), len(dev_set)), file=sys.stderr)
    print('vocab: %s' % repr(vocab.src), file=sys.stderr)
    epoch = 0
    train_iter = 0
    report_loss = report_examples = 0.
    history_dev_scores = []
    num_trial = patience = 0

    model_dir, log_dir = get_exp_info(main_args=main_args, model_args=model_args)
    model_file = model_dir + '.bin'
    writer = SummaryWriter(log_dir)

    while True:
        epoch += 1
        epoch_begin = time.time()
        for batch_examples in train_set.batch_iter(batch_size=main_args.batch_size, shuffle=True):
            train_iter += 1
            optimizer.zero_grad()
            loss = -model.score(batch_examples)
            loss_val = torch.sum(loss).item()
            report_loss += loss_val
            report_examples += len(batch_examples)
            loss = torch.mean(loss)
            loss.backward()

            if main_args.clip_grad > 0.:
                torch.nn.utils.clip_grad_norm_(model.parameters(), main_args.clip_grad)

            optimizer.step()

            if train_iter % main_args.log_every == 0:
                print('\r[Iter %d] encoder loss=%.5f' %
                      (train_iter,
                       report_loss / report_examples),
                      file=sys.stderr, end=" ")

                writer.add_scalar(
                    tag='AutoEncoder/Train/loss',
                    scalar_value=report_loss / report_examples,
                    global_step=train_iter
                )
                writer.add_scalar(
                    tag='optimize/lr',
                    scalar_value=optimizer.param_groups[0]['lr'],
                    global_step=train_iter,
                )

                report_loss = report_examples = 0.

            if train_iter % main_args.dev_every == 0:
                print()
                print('\r[Iter %d] begin validation' % train_iter, file=sys.stderr)
                eval_start = time.time()
                eval_results = evaluate(examples=dev_set.examples, model=model, eval_src='src', eval_tgt='src')
                dev_acc = eval_results['accuracy']
                print('\r[Iter %d] auto_encoder %s=%.5f took %ds' % (
                    train_iter, model.args.eval_mode, dev_acc, time.time() - eval_start),
                      file=sys.stderr)
                writer.add_scalar(
                    tag='AutoEncoder/Dev/%s' % model.args.eval_mode,
                    scalar_value=dev_acc,
                    global_step=train_iter
                )

                is_better = history_dev_scores == [] or dev_acc > max(history_dev_scores)
                history_dev_scores.append(dev_acc)

                writer.add_scalar(
                    tag='AutoEncoder/Dev/best %s' % model.args.eval_mode,
                    scalar_value=max(history_dev_scores),
                    global_step=train_iter
                )

                model, optimizer, num_trial, patience = lr_schedule(
                    is_better=is_better,
                    model_dir=model_dir,
                    model_file=model_file,
                    main_args=main_args,
                    patience=patience,
                    num_trial=num_trial,
                    epoch=epoch,
                    model=model,
                    optimizer=optimizer,
                    reload_model=False
                )

        epoch_time = time.time() - epoch_begin
        print('\r[Epoch %d] epoch elapsed %ds' % (epoch, epoch_time), file=sys.stderr)
        writer.add_scalar(
            tag='AutoEncoder/epoch elapsed',
            scalar_value=epoch_time,
            global_step=epoch
        )

def train_vae(main_args, model_args, model=None):
    ts = time.strftime('%Y-%b-%d-%H:%M:%S', time.gmtime())
    train_set, dev_set = load_data(main_args)
    model, optimizer, vocab = create_model(main_args, model_args, model)

    model_dir, logdir = get_exp_info(main_args=main_args, model_args=model_args)
    model_file = model_dir + '.bin'

    eval_dir = get_eval_dir(main_args=main_args, model_args=model_args, mode='Trains')

    evaluator = VaeEvaluator(
        model=model,
        out_dir=eval_dir,
        train_batch_size=main_args.batch_size,
        eval_batch_size=model_args.eval_bs,

    )

    # if model_args.tensorboard_logging:
    writer = SummaryWriter(logdir)
    writer.add_text("model", str(model))
    writer.add_text("args", str(main_args))
    writer.add_text("ts", ts)

    train_iter = main_args.start_iter
    epoch = num_trial = patience = 0
    history_elbo = []
    history_bleu = []
    max_kl_item = -1
    max_kl_weight = None

    continue_anneal = model_args.peak_anneal

    if model_args.peak_anneal:
        model_args.warm_up = 0

    memory_temp_count = 0

    t_type = torch.Tensor
    adv_select = ["ADVCoupleVAE", "VSAE", "ACVAE", "DVAE", "SVAE"]
    if model_args.model_select in adv_select:
        if not model_args.dis_train:
            x = input("you forget set the dis training?,switch it?[Y/N]")
            model_args.dis_train = (x.lower() == "y")

    adv_training = model_args.dis_train and model_args.model_select in adv_select
    if adv_training:
        print("has the adv training process")
    adv_syn = model_args.adv_syn > 0. or model_args.infer_weight * model_args.inf_sem
    adv_sem = model_args.adv_sem > 0. or model_args.infer_weight * model_args.inf_syn

    print(model_args.dev_item.lower())
    while True:
        epoch += 1
        train_track = {}
        for batch_examples in train_set.batch_iter(batch_size=main_args.batch_size, shuffle=True):
            train_iter += 1
            if adv_training:
                ret_loss = model.get_loss(batch_examples, train_iter, is_dis=True)
                if adv_syn:
                    dis_syn_loss = ret_loss['dis syn']
                    optimizer.zero_grad()
                    dis_syn_loss.backward()
                    if main_args.clip_grad > 0.:
                        torch.nn.utils.clip_grad_norm_(model.parameters(), main_args.clip_grad)
                        # optimizer.step()
                if adv_sem:
                    ret_loss = model.get_loss(batch_examples, train_iter, is_dis=True)
                    dis_sem_loss = ret_loss['dis sem']
                    optimizer.zero_grad()
                    dis_sem_loss.backward()
                    if main_args.clip_grad > 0.:
                        torch.nn.utils.clip_grad_norm_(model.parameters(), main_args.clip_grad)
                        # optimizer.step()

            ret_loss = model.get_loss(batch_examples, train_iter)
            loss = ret_loss['Loss']
            optimizer.zero_grad()
            loss.backward()
            if main_args.clip_grad > 0.:
                torch.nn.utils.clip_grad_norm_(model.parameters(), main_args.clip_grad)

            optimizer.step()
            train_iter += 1
            # tracker = update_track(loss, train_avg_kl, train_avg_nll, tracker)
            train_track = log_tracker(ret_loss, train_track)
            if train_iter % main_args.log_every == 0:
                train_avg_nll = ret_loss['NLL Loss']
                train_avg_kl = ret_loss['KL Loss']
                _kl_weight = ret_loss['KL Weight']
                for key, val in ret_loss.items():
                    writer.add_scalar(
                        'Train-Iter/VAE/{}'.format(key),
                        val.item() if isinstance(val, t_type) else val,
                        train_iter
                    )

                print("\rTrain-Iter %04d, Loss %9.4f, NLL-Loss %9.4f, KL-Loss %9.4f, KL-Weight %6.3f, WD-Drop %6.3f"
                      % (train_iter, loss.item(), train_avg_nll, train_avg_kl, _kl_weight, model.step_unk_rate),
                      end=' ')
                writer.add_scalar(
                    tag='optimize/lr',
                    scalar_value=optimizer.param_groups[0]['lr'],
                    global_step=train_iter,
                )

            if train_iter % main_args.dev_every == 0 and train_iter > model_args.warm_up:
                # dev_track, eval_results = _test_vae(model, dev_set, main_args, train_iter)
                dev_track, eval_results = evaluator.evaluate_reconstruction(examples=dev_set.examples,
                                                                            eval_desc="dev{}".format(train_iter),
                                                                            eval_step=train_iter, write_down=False)
                _weight = model.get_kl_weight(step=train_iter)
                _kl_item = torch.mean(dev_track['KL Item'])
                # writer.add_scalar("VAE/Valid-Iter/KL Item", _kl_item, train_iter)
                for key, val in dev_track.items():
                    writer.add_scalar(
                        'Valid-Iter/VAE/{}'.format(key),
                        torch.mean(val) if isinstance(val, t_type) else val,
                        train_iter
                    )
                if continue_anneal and model.step_kl_weight is None:
                    if _kl_item > max_kl_item:
                        max_kl_item = _kl_item
                        max_kl_weight = _weight
                    else:
                        if (max_kl_item - _kl_item) > model_args.stop_clip_kl:
                            model.step_kl_weight = max_kl_weight
                            writer.add_text(tag='peak_anneal',
                                            text_string="fixed the kl weight:{} with kl peak:{} at step:{}".format(
                                                max_kl_weight,
                                                max_kl_item,
                                                train_iter
                                            ), global_step=train_iter)
                            continue_anneal = False
                dev_elbo = torch.mean(dev_track['Model Score'])
                writer.add_scalar("Evaluation/VAE/Dev Score", dev_elbo, train_iter)

                # evaluate bleu
                dev_bleu = eval_results['accuracy']
                print()
                print("Valid-Iter %04d, NLL_Loss:%9.4f, KL_Loss: %9.4f, Sum Score:%9.4f BLEU:%9.4f" % (
                    train_iter,
                    torch.mean(dev_track['NLL Loss']),
                    torch.mean(dev_track['KL Loss']),
                    dev_elbo,
                    eval_results['accuracy']), file=sys.stderr
                      )
                writer.add_scalar(
                    tag='Evaluation/VAE/Iter %s' % model.args.eval_mode,
                    scalar_value=dev_bleu,
                    global_step=train_iter
                )
                if model_args.dev_item == "ELBO" or model_args.dev_item.lower() == "para-elbo" or model_args.dev_item.lower() == "gen-elbo":
                    is_better = history_elbo == [] or dev_elbo < min(history_elbo)
                elif model_args.dev_item == "BLEU" or model_args.dev_item.lower() == "para-bleu" or model_args.dev_item.lower() == "gen-bleu":
                    is_better = history_bleu == [] or dev_bleu > max(history_bleu)

                history_elbo.append(dev_elbo)
                writer.add_scalar("Evaluation/VAE/Best Score", min(history_elbo), train_iter)
                history_bleu.append(dev_bleu)
                writer.add_scalar("Evaluation/VAE/Best BLEU Score", max(history_bleu), train_iter)

                if is_better:
                    writer.add_scalar(
                        tag='Evaluation/VAE/Best %s' % model.args.eval_mode,
                        scalar_value=dev_bleu,
                        global_step=train_iter
                    )
                    writer.add_scalar(
                        tag='Evaluation/VAE/Best NLL-LOSS',
                        scalar_value=torch.mean(dev_track['NLL Loss']),
                        global_step=train_iter
                    )
                    writer.add_scalar(
                        tag='Evaluation/VAE/Best KL-LOSS',
                        scalar_value=torch.mean(dev_track['KL Loss']),
                        global_step=train_iter
                    )
                    if train_iter * 2 > model_args.x0:
                        memory_temp_count = 3

                if model_args.dev_item.lower().startswith("gen") and memory_temp_count > 0:
                    evaluator.evaluate_generation(
                        sample_size=len(dev_set.examples),
                        eval_desc="gen_iter{}".format(train_iter),
                    )
                    memory_temp_count -= 1

                if model_args.dev_item.lower().startswith("para") and memory_temp_count > 0:

                    para_score = evaluator.evaluate_para(
                        eval_dir="/home/user_data/baoy/projects/seq2seq_parser/data/quora-mh/unsupervised",
                        # eval_list=["para.raw.text", "para.text"])
                        #  eval_list=["para.raw.text"])
                        eval_list=["dev.para.txt", "test.para.txt"],
                        eval_desc="para_iter{}".format(train_iter))
                    if memory_temp_count == 3:
                        writer.add_scalar(
                            tag='Evaluation/VAE/Para Dev Ori-BLEU',
                            scalar_value=para_score[0][0],
                            global_step=train_iter
                        )
                        writer.add_scalar(
                            tag='Evaluation/VAE/Para Dev Tgt-BLEU',
                            scalar_value=para_score[0][1],
                            global_step=train_iter
                        )
                        if len(para_score) > 1:
                            writer.add_scalar(
                                tag='Evaluation/VAE/Para Test Ori-BLEU',
                                scalar_value=para_score[1][0],
                                global_step=train_iter
                            )
                            writer.add_scalar(
                                tag='Evaluation/VAE/Para Test Tgt-BLEU',
                                scalar_value=para_score[1][1],
                                global_step=train_iter
                            )
                    memory_temp_count -= 1

                model, optimizer, num_trial, patience = lr_schedule(
                    is_better=is_better,
                    model_dir=model_dir,
                    model_file=model_file,
                    main_args=main_args,
                    patience=patience,
                    num_trial=num_trial,
                    epoch=epoch,
                    model=model,
                    optimizer=optimizer,
                    reload_model=model_args.reload_model,
                )
                model.train()
        elbo = torch.mean(train_track['Model Score'])
        print()
        print("Train-Epoch %02d, Score %9.4f" % (epoch, elbo))
        for key, val in train_track.items():
            writer.add_scalar(
                'Train-Epoch/VAE/{}'.format(key),
                torch.mean(val) if isinstance(val, t_type) else val,
                epoch
            )

def test_vae(main_args, model_args, input_mode=0):
    model = load_model(main_args, model_args, check_dir=False)
    out_dir = get_eval_dir(main_args=main_args, model_args=model_args, mode="Test")
    model.eval()
    if not os.path.exists(out_dir):
        sys.exit(-1)
    if model_args.model_select.startswith("Origin"):
        model_args.eval_bs = 20 if model_args.eval_bs < 20 else model_args.eval_bs
    evaluator = VaeEvaluator(
        model=model,
        out_dir=out_dir,
        eval_batch_size=model_args.eval_bs,
        train_batch_size=main_args.batch_size
    )
    train_exam = Dataset.from_bin_file(main_args.train_file).examples

    para_eval_dir = "/home/user_data/baoy/projects/seq2seq_parser/data/quora-mh/unsupervised"
    para_eval_list = ["dev.para.txt"]
    # ["dev.para.txt", "test.para.txt"]

    if input_mode == 0:
        print("========dev reconstructor========")
        test_set = Dataset.from_bin_file(main_args.dev_file)
        evaluator.evaluate_reconstruction(examples=test_set.examples, eval_desc="dev")
        print("finish")
        print("========test reconstructor=======")
        test_set = Dataset.from_bin_file(main_args.test_file)
        evaluator.evaluate_reconstruction(examples=test_set.examples, eval_desc="test")
        print("finish")
        print("========generating samples=======")
        evaluator.evaluate_generation(corpus_examples=train_exam, sample_size=len(test_set.examples), eval_desc="gen")
        print("finish")
    elif input_mode == 1:
        print("========generating samples=======")
        test_exam = Dataset.from_bin_file(main_args.test_file).examples
        evaluator.evaluate_generation(corpus_examples=train_exam, sample_size=len(test_exam), eval_desc="gen")
        print("finish")
    elif input_mode == 2:
        print("========generating paraphrase========")
        evaluator.evaluate_para(eval_dir=para_eval_dir, eval_list=para_eval_list)
        print("finish")
    elif input_mode == 3:
        print("========supervised generation========")
        # evaluator.evaluate_control()
        evaluator.evaluate_control(eval_dir=para_eval_dir, eval_list=para_eval_list)
        print("finish")
    elif input_mode == 4:
        trans_eval_list = ["trans.length.txt", "trans.random.txt"]
        print("========style transfer========")
        evaluator.evaluate_style_transfer(eval_dir=para_eval_dir, eval_list=trans_eval_list, eval_desc="unmatch")
        evaluator.evaluate_style_transfer(eval_dir=para_eval_dir, eval_list=para_eval_list, eval_desc="match")
        print("finish")
    elif input_mode == 5:
        print("========random syntax select========")
        evaluator.evaluate_pure_para(eval_dir=para_eval_dir, eval_list=para_eval_list)
        print("finish")
    else:
        raw = input("raw sent: ")
        while not raw.startswith("EXIT"):
            e = to_example(raw)
            words = model.predict(e)
            print("origin:", " ".join(words[0][0][0]))
            to_ref = input("ref syn : ")
            while not to_ref.startswith("NEXT"):
                syn_ref = to_example(to_ref)
                ret = model.eval_adv(e, syn_ref)
                if not model_args.model_select == "OriginVAE":
                    print("ref syntax: ", " ".join(ret['ref syn'][0][0][0]))
                    print("ori syntax: ", " ".join(ret['ori syn'][0][0][0]))
                print("switch result: ", " ".join(ret['res'][0][0][0]))
                to_ref = input("ref syn: ")
            raw = input("input : ")

def process_args():
    opt_parser = argparse.ArgumentParser()
    opt_parser.add_argument('--config_files', type=str, help='config_files')
    opt_parser.add_argument('--exp_name', type=str, help='config_files')
    opt_parser.add_argument('--load_src_lm', type=str, default=None)
    opt_parser.add_argument('--mode', type=str, default=None)
    opt = opt_parser.parse_args()

    configs = yaml_load_dict(opt.config_files)

    base_args = dict_to_args(configs['base_configs']) if 'base_configs' in configs else None
    baseline_args = dict_to_args(configs['baseline_configs']) if 'baseline_configs' in configs else None
    prior_args = dict_to_args(configs['prior_configs']) if 'prior_configs' in configs else None
    encoder_args = dict_to_args(configs['encoder_configs']) if 'encoder_configs' in configs else None
    decoder_args = dict_to_args(configs['decoder_configs']) if 'decoder_configs' in configs else None
    vae_args = dict_to_args(configs['vae_configs']) if 'vae_configs' in configs else None
    ae_args = dict_to_args(configs["ae_configs"]) if 'ae_configs' in configs else None

    if base_args is not None:
        if opt.mode is not None:
            base_args.mode = opt.mode
        if opt.exp_name is not None:
            base_args.exp_name = opt.exp_name
        if opt.load_src_lm is not None:
            base_args.load_src_lm = opt.load_src_lm

    return {
        'base': base_args,
        "baseline": baseline_args,
        'prior': prior_args,
        'encoder': encoder_args,
        "decoder": decoder_args,
        "vae": vae_args,
        "ae": ae_args,
    }

if __name__ == "__main__":
    config_args = process_args()
    args = config_args['base']
    if args.mode == "train_sent":
        train_vae(args, config_args['vae'])
    elif args.mode == "train_ae":
        train_ae(args, config_args['ae'])
    elif args.mode == "test_vae":
        raw_sent = int(input("select test mode: "))
        test_vae(args, config_args['vae'], input_mode=raw_sent)
    elif args.mode == "test_vaea":
        test_vae(args, config_args['vae'], input_mode=0)
    elif args.mode == "test_generating":
        test_vae(args, config_args['vae'], input_mode=1)
    elif args.mode == "test_paraphrase":
        test_vae(args, config_args['vae'], input_mode=2)
    elif args.mode == "test_control":
        test_vae(args, config_args['vae'], input_mode=3)
    elif args.mode == "test_transfer":
        test_vae(args, config_args['vae'], input_mode=4)
    elif args.mode == "test_pure_para":
        test_vae(args, config_args['vae'], input_mode=5)
    else:
        raise NotImplementedError


import pygame
from pygame.locals import *
import random
from OpenGL.GL import *
from OpenGL.GLU import *

vertices = (
    (1, 1, 1),
    (1, 1, -1),
    (1, -1, -1),
    (1, -1, 1),
    (-1, 1, 1),
    (-1, 1, -1),
    (-1, -1, -1),
    (-1, -1, 1))
edges = (
    (0, 1),
    (0, 3),
    (0, 4),
    (2, 1),
    (2, 3),
    (2, 6),
    (5, 1),
    (5, 4),
    (5, 6),
    (7, 3),
    (7, 4),
    (7, 6))

surfaces = (
    (0, 1, 2, 3),
    (4, 5, 6, 7),
    (0, 1, 5, 4),
    (2, 3, 7, 6),
    (0, 3, 7, 4),
    (1, 2, 6, 5))

colors = (
    (1, 0, 0),
    (0, 1, 0),
    (0, 0, 1),
    (0.5, 0.5, 0.5),
    (1, 1, 1),
    (0, 1, 1),
)

# ground_vertices = (
#     (-20, -0.5, 20),
#     (20, -0.5, 20),
#     (-20, -0.5, -300),
#     (20, -0.5, -300))
#
# def ground():
#     glBegin(GL_QUADS)
#     for vertex in ground_vertices:
#         glColor3fv((0, 0.5, 0.5))
#         glVertex3fv(vertex)
#     glEnd()

def set_vertices(max_distance, min_distance = -20, camera_x = 0, camera_y = 0):

    camera_x = -1*int(camera_x)
    camera_y = -1*int(camera_y)

    x_value_change = random.randrange(camera_x-10, camera_x+10)
    y_value_change = random.randrange(camera_y-10, camera_y+10)
    z_value_change = random.randrange(-1*max_distance, min_distance)

    new_vertices = []

    for vert in vertices:
        new_vert = []

        new_x = vert[0] + x_value_change
        new_y = vert[1] + y_value_change
        new_z = vert[2] + z_value_change
        new_vert.append(new_x)
        new_vert.append(new_y)
        new_vert.append(new_z)

        new_vertices.append(new_vert)
    return tuple(new_vertices)

def cube(vertices):
    glBegin(GL_QUADS)
    for surface in surfaces:
        x = 0
        for vertex in surface:
            glColor3fv(colors[x])
            glVertex3fv(vertices[vertex])
            x += 1
    glEnd()
    glBegin(GL_LINES)
    for edge in edges:
        for vertex in edge:
            glColor3fv((0, 0 , 0))
            glVertex3fv(vertices[vertex])

    glEnd()

def main():
    pygame.init()
    display = (800, 600)
    pygame.display.set_mode(display, DOUBLEBUF|OPENGL) #.

    max_distance = 100

    gluPerspective(45, (display[0]/display[1]), 0.1, max_distance)

    glTranslatef(random.randrange(-5, 5), 0, -40)

    cur_x = 0
    cur_y = 0

    cube_dict = {}
    for x in range(40):
        cube_dict[x] = set_vertices(max_distance)

    while True:
        x_move = 0
        y_move = 0

        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                pygame.quit()
                quit()
            # if event.type == pygame.MOUSEBUTTONDOWN:
            #     if event.button == 4:
            #         glTranslatef(0, 0.0, 1)
            #     if event.button == 5:
            #         glTranslatef(0, 0.0, -1)
        pressed = pygame.key.get_pressed()
        if pressed[K_UP]:
            y_move = -0.3
        if pressed[K_DOWN]:
            y_move = 0.3
        if pressed[K_RIGHT]:
            x_move = -0.3
        if pressed[K_LEFT]:
            x_move = 0.3

        glTranslatef(x_move, y_move, 0)

        view = glGetDoublev(GL_MODELVIEW_MATRIX)

        # camera_x = view[3][0]
        # camera_y = view[3][1]
        camera_z = view[3][2]

        cur_x += x_move
        cur_y += y_move

        glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT)

        glTranslatef(0, 0, 1)

        # ground()

        for each in cube_dict:
            cube(cube_dict[each])

        delete_list = []

        for each in cube_dict:
            if camera_z <= cube_dict[each][0][2]:
                new_max = int(-1*(camera_z - max_distance))

                cube_dict[each] = set_vertices(new_max, int(camera_z - 50), cur_x, cur_y)

        pygame.display.flip()

main()
pygame.quit()
quit()


from __future__ import (
    division, print_function, absolute_import, unicode_literals)

import numpy as np
from scipy.ndimage import binary_dilation, label
from scipy.special import erf
from astropy import units as u

def find_signal_in_cube(
        cube, noisecube, mask=None,
        nchan_hi=3, snr_hi=3.5, nchan_lo=2, snr_lo=2,
        prune_by_npix=None, prune_by_fracbeam=1.,
        expand_by_npix=None, expand_by_fracbeam=0.,
        expand_by_nchan=2, verbose=False):
    """
    Identify (positive) signal in a cube based on S/N ratio.

    This is a revised version of the signal identification scheme
    described in Sun et al. (2018, ApJ, 860, 172).

    Here is a description of the scheme:
    1. Generate a 'core mask' by identifying detections with
       S/N >= `snr_hi` over at least `nchan_hi` consecutive channels.
       (if a user-specified mask exists, do this step inside the mask)
    2. Generate a 'wing mask' by identifying detections with
       S/N >= `snr_lo` over at least `nchan_lo` consecutive channels.
       (if a user-specified mask exists, do this step inside the mask)
    3. Dilate the 'core mask' inside the 'wing mask' to get
       a 'signal mask' that defines 'detections'.
    4. Label 'detections' by connectivity, and prune 'detections'
       in the 'signal mask' if projected area on sky smaller than
       a given number of pixels or a fraction of the beam area.
       (fraction specified by `prune_by_npix` or `prune_by_fracbeam`)
    5. Expand the 'signal mask' along the spatial dimensions by
       a given number of pixels or a fraction of the beam FWHM.
       (fraction specified by `expand_by_npix` or `expand_by_fracbeam`)
    6. Expand the 'signal mask' along the spectral dimension by
       a given number of channels.
       (# of channels specified by `expand_by_nchan`)

    Parameters
    ----------
    cube : SpectralCube object
        Input spectral cube
    noisecube : SpectralCube object
        Estimated rms noise cube
    mask : `np.ndarray` object, optional
        User-specified mask, within which all steps are performed
    nchan_hi : int, optional
        # of consecutive channels specified for the 'core mask'
        (Default: 3)
    snr_hi : float, optional
        S/N threshold specified for the 'core mask'
        (Default: 3.5)
    nchan_lo : int, optional
        # of consecutive channels specified for the 'wing mask'
        (Default: 2)
    snr_lo : float, optional
        S/N threshold specified for the 'wing mask'
        (Default: 2.0)
    prune_by_npix : int, optional
        Threshold for pruning. All detections with projected area
        smaller than this number of pixels will be pruned.
        If not None, `prune_by_fracbeam` will be ignored.
        (Default: None)
    prune_by_fracbeam : float, optional
        Threshold for pruning. All detections with projected area
        smaller than this threshold times the beam area will be pruned.
        (Default: 1.0)
    expand_by_npix : int, optional
        Expand the signal mask along the spatial dimensions by this
        number of pixels. If not None, `expand_by_fracbeam` will be
        ignored. (Default: None)
    expand_by_fracbeam : float, optional
        Expand the signal mask along the spatial dimensions by this
        fraction times the beam HWHM.
        (Default: 0.0)
    expand_by_nchan : int, optional
        Expand the signal mask along the spectral dimensions by this
        number of channels.
        (Default: 2)
    verbose : bool, optional
        Whether to print the detailed processing information
        Default is to not print.

    Returns
    -------
    outcube : SpectralCube object
        Input cube masked by the generated 'signal mask'.
    """

    if not cube.unit.is_equivalent(noisecube.unit):
        raise ValueError(
            "Incompatable units between 'cube' and 'noisecube'!")

    snr = cube.filled_data[:] / noisecube.filled_data[:]
    snr = snr.to(u.dimensionless_unscaled).value
    if mask is None:
        mask = np.ones_like(snr).astype('?')

    # generate core mask
    if verbose:
        print(
            "Generating core mask (S/N >= {} over {} consecutive "
            "channels)...".format(snr_hi, nchan_hi))
    mask_core = (snr > snr_hi)
    for iiter in range(nchan_hi-1):
        mask_core &= np.roll(mask_core, 1, 0)
    mask_core[:nchan_hi-1, :] = False
    for iiter in range(nchan_hi-1):
        mask_core |= np.roll(mask_core, -1, 0)
    mask_core &= mask

    # generate wing mask
    if verbose:
        print(
            "Generating wing mask (S/N >= {} over {} consecutive "
            "channels)...".format(snr_lo, nchan_lo))
    mask_wing = snr > snr_lo
    for iiter in range(nchan_lo-1):
        mask_wing &= np.roll(mask_wing, 1, 0)
    mask_wing[:nchan_lo-1, :] = False
    for iiter in range(nchan_lo-1):
        mask_wing |= np.roll(mask_wing, -1, 0)
    mask_wing &= mask

    # dilate core mask inside wing mask
    if verbose:
        print("Dilating core mask inside wing mask...")
    mask_signal = binary_dilation(
        mask_core, iterations=0, mask=mask_wing)

    # prune detections with small projected area on the sky
    if (prune_by_fracbeam > 0) or (prune_by_npix is not None):
        if verbose:
            print("Pruning detections with small projected area")
        if prune_by_npix is None:
            beamarea_pix = np.abs(
                cube.beam.sr.to(u.deg**2).value /
                cube.wcs.celestial.wcs.cdelt.prod())
            prune_by_npix = beamarea_pix * prune_by_fracbeam
        labels, count = label(mask_signal)
        for ind in np.arange(count)+1:
            if (labels == ind).any(axis=0).sum() < prune_by_npix:
                mask_signal[labels == ind] = False

    # expand along spatial dimensions by a fraction of beam FWHM
    if (expand_by_fracbeam > 0) or (expand_by_npix is not None):
        if verbose:
            print("Expanding signal mask along spatial dimensions")
        if expand_by_npix is None:
            beamHWHM_pix = np.ceil(
                cube.beam.major.to(u.deg).value / 2 /
                np.abs(cube.wcs.celestial.wcs.cdelt.min()))
            expand_by_npix = int(beamHWHM_pix * expand_by_fracbeam)
        structure = np.zeros(
            [3, expand_by_npix*2+1, expand_by_npix*2+1])
        Y, X = np.ogrid[:expand_by_npix*2+1, :expand_by_npix*2+1]
        R = np.sqrt((X - expand_by_npix)**2 + (Y-expand_by_npix)**2)
        structure[1, :] = (R <= expand_by_npix)
        mask_signal = binary_dilation(
            mask_signal, iterations=1, structure=structure, mask=mask)

    # expand along spectral dimension by a number of channels
    if expand_by_nchan > 0:
        if verbose:
            print("Expanding along spectral dimension by {} channels"
                  "".format(expand_by_nchan))
        for iiter in range(expand_by_nchan):
            tempmask = np.roll(mask_signal, 1, axis=0)
            tempmask[0, :] = False
            mask_signal |= tempmask
            tempmask = np.roll(mask_signal, -1, axis=0)
            tempmask[-1, :] = False
            mask_signal |= tempmask
        mask_signal &= mask

    return cube.with_mask(mask_signal)

def censoring_function(
        line_rms_width, channel_width, T_noise,
        spec_resp_kernel=[0, 1, 0],
        nchan_crit=2, snr_crit=2, scenario='optimistic'):
    """
    Calculate the shape of the censoring function.

    Parameters
    ----------
    line_rms_width: float or array-like
        The rms width of the Gaussian line profile
    channel_width: float or array-like
        The width of spectral channels, in the same unit as
        `line_rms_width`
    T_noise: float or array-like
        Noise temperature (1-sigma level)
    spec_resp_kernel: array-like with odd number of elements, optional
        A spectral response kernel designed to reproduce the
        appropriate channel-to-channel correlation
        (see Appendix A in Leroy et al. 2016, ApJ, 831, 16)
    nchan_crit: int, optional (default=2)
        # of channels used in determining significant detections
        (e.g., if the criterion is 2 consecutive channels above S/N=5,
        then `nchan_crit` should be set to 2)
    snr_crit: float, optional (default=2)
        S/R ratio used in determining significant detections
        (e.g., if the criterion is 2 consecutive channels above S/N=5,
        then `snr_crit` should be set to 5)
    scenario: {'optimistic', 'pessimistic'}, optional
        If 'optimistic' (default), this function will return the
        threshold below which the completeness is 0%.
        If 'pessimistic', this function will return the threshold
        above which the completeness is 100%.

    Returns
    -------
    line_intensity: float or array-like
        Completeness threshold in terms of integrated line intensity,
        carrying a unit of [T_noise] * [line_rms_width]
    """
    x = (np.asfarray(channel_width) /
         (2**0.5 * np.asfarray(line_rms_width)))

    if scenario == 'optimistic':
        # line center aligns perfectly with
        # the center of the `nchan_crit` consecutive channels
        uplim = nchan_crit / 2
        lolim = nchan_crit / 2 - 1
    elif scenario == 'pessimistic':
        # line center is off by half a channel width from
        # the center of the `nchan_crit` consecutive channels
        uplim = nchan_crit / 2 + 0.5
        lolim = nchan_crit / 2 - 0.5
    else:
        raise ValueError("Invalid `scenario`")

    kernel = np.asfarray(spec_resp_kernel).flatten()
    if (kernel.size % 2 == 0) or (kernel.sum() != 1):
        raise ValueError("Invalid spectral response kernel")

    ind = np.arange(-(kernel.size-1)/2, (kernel.size+1)/2)
    uplim = (uplim + ind) * x[..., np.newaxis]
    lolim = (lolim + ind) * x[..., np.newaxis]
    fraction = ((erf(uplim) - erf(lolim)) / 2 * kernel).sum(axis=-1)

    return np.asfarray(T_noise) * channel_width * snr_crit / fraction


import falcon
import datetime
from sqlalchemy.orm.exc import NoResultFound
from models.session_model import SessionModel

class SessionResource:
    def on_get(self, req, resp):
        """Handles GET requests"""
        cookies = req.cookies
        if 'pomodoro_login_hash' in cookies:
            my_cookie_hash = cookies.get('pomodoro_login_hash', None)
            if my_cookie_hash is not None:
                try:
                    my_session = req.context['session'].query(
                        SessionModel.hash, SessionModel.modified). \
                        filter_by(hash=my_cookie_hash).one()

                except NoResultFound:
                    resp.unset_cookie('pomodoro_login_hash')
                    raise falcon.HTTPFound('/app/login')

                else:
                    session_modified_time = my_session.modified
                    tdelta = datetime.timedelta(hours=22)
                    now = datetime.datetime.utcnow()
                    session_expire_time = session_modified_time + tdelta

                    if now > session_expire_time:
                        req.context['session'].query(
                            SessionModel).filter_by(
                            user_id=req.context['user'].id).delete(
                            synchronize_session=False)
                        req.context['session'].commit()
                        raise falcon.HTTPFound('/app/session_expired')

        else:
            raise falcon.HTTPFound('/app/login')


__author__ = 'Timothy Lam'

import requests
import config
import trans_

def bbc():
    # BBC news api
    main_url = f" https://newsapi.org/v1/articles?source=bbc-news&sortBy=top&apiKey={config.news_key}"

    # fetching data in json format
    open_bbc_page = requests.get(main_url).json()

    # getting all articles in a string article
    article = open_bbc_page["articles"]
    if config.lang_code == 'en':
        display = "Top 5 BBC News :"
        say = "Top 5 BBC News :"

        nos = 1
        for ar in article[:5]:
            news = ar["title"]
            url = ar["url"]

            display += f'
{nos}. {news} Read
'

            say += f'\n{nos}. {news}'
            nos += 1
        reply_ = {'display': display, 'say': say}
    else:
        ans = trans_.translate_sentence_code("Top 5 BBC News", config.lang_code)
        display = ans['display'] + ':'
        say = "Top 5 BBC News"
        nos = 1
        for ar in article[:5]:
            news = trans_.translate_sentence_code(ar["title"], config.lang_code)['display']
            url = ar["url"]
            read = trans_.translate_sentence_code("Read", config.lang_code)['display']

            display += f'
{nos}. {news} {read}
'

            say += f'\n{nos}. {news}'
            nos += 1
        say = trans_.translate_sentence_code(say, config.lang_code)['say']
        reply_ = {'display': display, 'say': say}
        config.lang_code = 'en'

    return reply_


import os
import os.path as osp
import numpy as np
import math
import cv2
import argparse

from paddlex import transforms as T
import paddlex as pdx

# 读数后处理中有把圆形表盘转成矩形的操作，矩形的宽即为圆形的外周长
# 因此要求表盘图像大小为固定大小，这里设置为[512, 512]
METER_SHAPE = [512, 512]  # 高x宽
# 圆形表盘的中心点
CIRCLE_CENTER = [256, 256]  # 高x宽
# 圆形表盘的半径
CIRCLE_RADIUS = 250
# 圆周率
PI = 3.1415926536
# 在把圆形表盘转成矩形后矩形的高
# 当前设置值约为半径的一半，原因是：圆形表盘的中心区域除了指针根部就是背景了
# 我们只需要把外围的刻度、指针的尖部保存下来就可以定位出指针指向的刻度
RECTANGLE_HEIGHT = 120
# 矩形表盘的宽，即圆形表盘的外周长
RECTANGLE_WIDTH = 1570
# 当前案例中只使用了两种类型的表盘，第一种表盘的刻度根数为50
# 第二种表盘的刻度根数为32。因此，我们通过预测的刻度根数来判断表盘类型
# 刻度根数超过阈值的即为第一种，否则是第二种
TYPE_THRESHOLD = 40
# 两种表盘的配置信息，包含每根刻度的值，量程，单位
METER_CONFIG = [{
    'scale_interval_value': 25.0 / 50.0,
    'range': 25.0,
    'unit': "(MPa)"
}, {
    'scale_interval_value': 1.6 / 32.0,
    'range': 1.6,
    'unit': "(MPa)"
}]
# 分割模型预测类别id与类别名的对应关系
SEG_CNAME2CLSID = {'background': 0, 'pointer': 1, 'scale': 2}

def parse_args():
    parser = argparse.ArgumentParser(description='Meter Reader Infering')
    parser.add_argument(
        '--det_model_dir',
        dest='det_model_dir',
        help='The directory of the detection model',
        type=str)
    parser.add_argument(
        '--seg_model_dir',
        dest='seg_model_dir',
        help='The directory of the segmentation model',
        type=str)
    parser.add_argument(
        '--image_dir',
        dest='image_dir',
        help='The directory of images to be inferred',
        type=str,
        default=None)
    parser.add_argument(
        '--image',
        dest='image',
        help='The image to be inferred',
        type=str,
        default=None)
    parser.add_argument(
        '--use_erode',
        dest='use_erode',
        help='Whether erode the lable map predicted from a segmentation model',
        action='store_true')
    parser.add_argument(
        '--erode_kernel',
        dest='erode_kernel',
        help='Erode kernel size',
        type=int,
        default=4)
    parser.add_argument(
        '--save_dir',
        dest='save_dir',
        help='The directory for saving the predicted results',
        type=str,
        default='./output/result')
    parser.add_argument(
        '--score_threshold',
        dest='score_threshold',
        help="Predicted bounding boxes whose scores are lower than this threshlod are filtered",
        type=float,
        default=0.5)
    parser.add_argument(
        '--seg_batch_size',
        dest='seg_batch_size',
        help="The number of images fed into the segmentation model during one forward propagation",
        type=int,
        default=2)

    return parser.parse_args()

def is_pic(img_name):
    """判断是否是图片

    参数：
        img_name (str): 图片路径

    返回：
        flag (bool): 判断值。
    """
    valid_suffix = ['JPEG', 'jpeg', 'JPG', 'jpg', 'BMP', 'bmp', 'PNG', 'png']
    suffix = img_name.split('.')[-1]
    flag = True
    if suffix not in valid_suffix:
        flag = False
    return flag

class MeterReader:
    """检测表盘的位置，分割各表盘内刻度和指针的位置，并根据分割结果计算出各表盘的读数

    参数：
        det_model_dir (str): 用于定位表盘的检测模型所在路径。
        seg_model_dir (str): 用于分割刻度和指针的分割模型所在路径。

    """

    def __init__(self, det_model_dir, seg_model_dir):
        if not osp.exists(det_model_dir):
            raise Exception("Model path {} does not exist".format(
                det_model_dir))
        if not osp.exists(seg_model_dir):
            raise Exception("Model path {} does not exist".format(
                seg_model_dir))
        self.detector = pdx.load_model(det_model_dir)
        self.segmenter = pdx.load_model(seg_model_dir)

    def decode(self, img_file):
        """图像解码

        参数：
            img_file (str|np.array): 图像路径，或者是已解码的BGR图像数组。

        返回：
            img (np.array): BGR图像数组。
        """

        if isinstance(img_file, str):
            img = cv2.imread(img_file).astype('float32')
        else:
            img = img_file.copy()
        return img

    def filter_bboxes(self, det_results, score_threshold):
        """过滤置信度低于阈值的检测框

        参数：
            det_results (list[dict]): 检测模型预测接口的返回值。
            score_threshold (float)：置信度阈值。

        返回：
            filtered_results (list[dict]): 过滤后的检测狂。

        """
        filtered_results = list()
        for res in det_results:
            if res['score'] > score_threshold:
                filtered_results.append(res)
        return filtered_results

    def roi_crop(self, img, det_results):
        """抠取图像上各检测框的图像区域

        参数：
            img (np.array)：BRG图像数组。
            det_results (list[dict]): 检测模型预测接口的返回值。

        返回：
            sub_imgs (list[np.array]): 各检测框的图像区域。

        """
        sub_imgs = []
        for res in det_results:
            # Crop the bbox area
            xmin, ymin, w, h = res['bbox']
            xmin = max(0, int(xmin))
            ymin = max(0, int(ymin))
            xmax = min(img.shape[1], int(xmin + w - 1))
            ymax = min(img.shape[0], int(ymin + h - 1))
            sub_img = img[ymin:(ymax + 1), xmin:(xmax + 1), :]
            sub_imgs.append(sub_img)
        return sub_imgs

    def resize(self, imgs, target_size, interp=cv2.INTER_LINEAR):
        """图像缩放至固定大小

        参数：
            imgs (list[np.array])：批量BGR图像数组。
            target_size (list|tuple)：缩放后的图像大小，格式为[高, 宽]。
            interp (int)：图像差值方法。默认值为cv2.INTER_LINEAR。

        返回：
            resized_imgs (list[np.array])：缩放后的批量BGR图像数组。

        """

        resized_imgs = list()
        for img in imgs:
            img_shape = img.shape
            scale_x = float(target_size[1]) / float(img_shape[1])
            scale_y = float(target_size[0]) / float(img_shape[0])
            resize_img = cv2.resize(
                img, None, None, fx=scale_x, fy=scale_y, interpolation=interp)
            resized_imgs.append(resize_img)
        return resized_imgs

    def seg_predict(self, segmenter, imgs, batch_size):
        """分割模型完成预测

        参数：
            segmenter (pdx.seg.model)：加载后的分割模型。
            imgs (list[np.array])：待预测的输入BGR图像数组。
            batch_size (int): 分割模型前向预测一次时输入图像的批量大小。

        返回：
            seg_results (list[dict]): 输入图像的预测结果。

        """
        seg_results = list()
        num_imgs = len(imgs)
        for i in range(0, num_imgs, batch_size):
            batch = imgs[i:min(num_imgs, i + batch_size)]
            result = segmenter.predict(batch)
            seg_results.extend(result)
        return seg_results

    def erode(self, seg_results, erode_kernel):
        """对分割模型预测结果中label_map做图像腐蚀操作

        参数：
            seg_results (list[dict])：分割模型的预测结果。
            erode_kernel (int): 图像腐蚀的卷积核的大小。

        返回：
            eroded_results (list[dict])：对label_map进行腐蚀后的分割模型预测结果。

        """
        kernel = np.ones((erode_kernel, erode_kernel), np.uint8)
        eroded_results = seg_results
        for i in range(len(seg_results)):
            eroded_results[i]['label_map'] = cv2.erode(
                seg_results[i]['label_map'].astype(np.uint8), kernel)
        return eroded_results

    def circle_to_rectangle(self, seg_results):
        """将圆形表盘的预测结果label_map转换成矩形

        圆形到矩形的计算方法：
            因本案例中两种表盘的刻度起始值都在左下方，故以圆形的中心点为坐标原点，
            从-y轴开始逆时针计算极坐标到x-y坐标的对应关系：
              x = r + r * cos(theta)
              y = r - r * sin(theta)
            注意：
                1. 因为是从-y轴开始逆时针计算，所以r * sin(theta)前有负号。
                2. 还是因为从-y轴开始逆时针计算，所以矩形从上往下对应圆形从外到内，
                   可以想象把圆形从-y轴切开再往左右拉平时，圆形的外围是上面，内围在下面。

        参数：
            seg_results (list[dict])：分割模型的预测结果。

        返回值：
            rectangle_meters (list[np.array])：矩形表盘的预测结果label_map。

        """
        rectangle_meters = list()
        for i, seg_result in enumerate(seg_results):
            label_map = seg_result['label_map']
            # rectangle_meter的大小已经由预先设置的全局变量RECTANGLE_HEIGHT, RECTANGLE_WIDTH决定
            rectangle_meter = np.zeros(
                (RECTANGLE_HEIGHT, RECTANGLE_WIDTH), dtype=np.uint8)
            for row in range(RECTANGLE_HEIGHT):
                for col in range(RECTANGLE_WIDTH):
                    theta = PI * 2 * (col + 1) / RECTANGLE_WIDTH
                    # 矩形从上往下对应圆形从外到内
                    rho = CIRCLE_RADIUS - row - 1
                    y = int(CIRCLE_CENTER[0] + rho * math.cos(theta) + 0.5)
                    x = int(CIRCLE_CENTER[1] - rho * math.sin(theta) + 0.5)
                    rectangle_meter[row, col] = label_map[y, x]
            rectangle_meters.append(rectangle_meter)
        return rectangle_meters

    def rectangle_to_line(self, rectangle_meters):
        """从矩形表盘的预测结果中提取指针和刻度预测结果并沿高度方向压缩成线状格式。

        参数：
            rectangle_meters (list[np.array])：矩形表盘的预测结果label_map。

        返回：
            line_scales (list[np.array])：刻度的线状预测结果。
            line_pointers (list[np.array])：指针的线状预测结果。

        """
        line_scales = list()
        line_pointers = list()

        for rectangle_meter in rectangle_meters:
            height, width = rectangle_meter.shape[0:2]
            line_scale = np.zeros((width), dtype=np.uint8)
            line_pointer = np.zeros((width), dtype=np.uint8)
            for col in range(width):
                for row in range(height):
                    if rectangle_meter[row, col] == SEG_CNAME2CLSID['pointer']:
                        line_pointer[col] += 1
                    elif rectangle_meter[row, col] == SEG_CNAME2CLSID['scale']:
                        line_scale[col] += 1
            line_scales.append(line_scale)
            line_pointers.append(line_pointer)
        return line_scales, line_pointers

    def mean_binarization(self, data_list):
        """对图像进行均值二值化操作

        参数：
            data_list (list[np.array])：待二值化的批量数组。

        返回：
            binaried_data_list (list[np.array])：二值化后的批量数组。

        """
        batch_size = len(data_list)
        binaried_data_list = data_list
        for i in range(batch_size):
            mean_data = np.mean(data_list[i])
            width = data_list[i].shape[0]
            for col in range(width):
                if data_list[i][col] < mean_data:
                    binaried_data_list[i][col] = 0
                else:
                    binaried_data_list[i][col] = 1
        return binaried_data_list

    def locate_scale(self, line_scales):
        """在线状预测结果中找到每根刻度的中心位置

        参数：
            line_scales (list[np.array])：批量的二值化后的刻度线状预测结果。

        返回：
            scale_locations (list[list])：各图像中每根刻度的中心位置。

        """
        batch_size = len(line_scales)
        scale_locations = list()
        for i in range(batch_size):
            line_scale = line_scales[i]
            width = line_scale.shape[0]
            find_start = False
            one_scale_start = 0
            one_scale_end = 0
            locations = list()
            for j in range(width - 1):
                if line_scale[j] > 0 and line_scale[j + 1] > 0:
                    if find_start == False:
                        one_scale_start = j
                        find_start = True
                if find_start:
                    if line_scale[j] == 0 and line_scale[j + 1] == 0:
                        one_scale_end = j - 1
                        one_scale_location = (
                            one_scale_start + one_scale_end) / 2
                        locations.append(one_scale_location)
                        one_scale_start = 0
                        one_scale_end = 0
                        find_start = False
            scale_locations.append(locations)
        return scale_locations

    def locate_pointer(self, line_pointers):
        """在线状预测结果中找到指针的中心位置

        参数：
            line_scales (list[np.array])：批量的指针线状预测结果。

        返回：
            scale_locations (list[list])：各图像中指针的中心位置。

        """
        batch_size = len(line_pointers)
        pointer_locations = list()
        for i in range(batch_size):
            line_pointer = line_pointers[i]
            find_start = False
            pointer_start = 0
            pointer_end = 0
            location = 0
            width = line_pointer.shape[0]
            for j in range(width - 1):
                if line_pointer[j] > 0 and line_pointer[j + 1] > 0:
                    if find_start == False:
                        pointer_start = j
                        find_start = True
                if find_start:
                    if line_pointer[j] == 0 and line_pointer[j + 1] == 0:
                        pointer_end = j - 1
                        location = (pointer_start + pointer_end) / 2
                        find_start = False
                        break
            pointer_locations.append(location)
        return pointer_locations

    def get_relative_location(self, scale_locations, pointer_locations):
        """找到指针指向了第几根刻度

        参数：
            scale_locations (list[list])：批量的每根刻度的中心点位置。
            pointer_locations (list[list])：批量的指针的中心点位置。

        返回：
            pointed_scales (list[dict])：每个表的结果组成的list。每个表的结果由字典表示，
                字典有两个关键词：'num_scales'、'pointed_scale'，分别表示预测的刻度根数、
                预测的指针指向了第几根刻度。

        """

        pointed_scales = list()
        for scale_location, pointer_location in zip(scale_locations,
                                                    pointer_locations):
            num_scales = len(scale_location)
            pointed_scale = -1
            if num_scales > 0:
                for i in range(num_scales - 1):
                    if scale_location[
                            i] <= pointer_location and pointer_location < scale_location[
                                i + 1]:
                        pointed_scale = i + (
                            pointer_location - scale_location[i]
                        ) / (scale_location[i + 1] - scale_location[i] + 1e-05
                             ) + 1
            result = {'num_scales': num_scales, 'pointed_scale': pointed_scale}
            pointed_scales.append(result)
        return pointed_scales

    def calculate_reading(self, pointed_scales):
        """根据刻度的间隔值和指针指向的刻度根数计算表盘的读数
        """
        readings = list()
        batch_size = len(pointed_scales)
        for i in range(batch_size):
            pointed_scale = pointed_scales[i]
            # 刻度根数大于阈值的为第一种表盘
            if pointed_scale['num_scales'] > TYPE_THRESHOLD:
                reading = pointed_scale['pointed_scale'] * METER_CONFIG[0][
                    'scale_interval_value']
            else:
                reading = pointed_scale['pointed_scale'] * METER_CONFIG[1][
                    'scale_interval_value']
            readings.append(reading)

        return readings

    def get_meter_reading(self, seg_results):
        """对分割结果进行读数后处理得到各表盘的读数

        参数：
            seg_results (list[dict]): 分割模型的预测结果。

        返回：
            meter_readings (list[dcit]): 各表盘的读数。

        """

        rectangle_meters = self.circle_to_rectangle(seg_results)
        line_scales, line_pointers = self.rectangle_to_line(rectangle_meters)
        binaried_scales = self.mean_binarization(line_scales)
        binaried_pointers = self.mean_binarization(line_pointers)
        scale_locations = self.locate_scale(binaried_scales)
        pointer_locations = self.locate_pointer(binaried_pointers)
        pointed_scales = self.get_relative_location(scale_locations,
                                                    pointer_locations)
        meter_readings = self.calculate_reading(pointed_scales)
        return meter_readings

    def print_meter_readings(self, meter_readings):
        """打印各表盘的读数

        参数：
            meter_readings (list[dict])：各表盘的读数
        """
        for i in range(len(meter_readings)):
            print("Meter {}: {}".format(i + 1, meter_readings[i]))

    def visualize(self, img, det_results, meter_readings, save_dir="./"):
        """可视化图像中各表盘的位置和读数

        参数：
            img (str|np.array): 图像路径，或者是已解码的BGR图像数组。
            det_results (dict): 检测模型的预测结果。
            meter_readings (list): 各表盘的读数。
            save_dir (str)：可视化后的图片保存路径。

        """
        vis_results = list()
        for i, res in enumerate(det_results):
            # 将检测结果中的关键词`score`替换成读数，就可以调用pdx.det.visualize画图了
            res['score'] = meter_readings[i]
            vis_results.append(res)
        # 检测结果可视化时会滤除score低于threshold的框，这里读数都是>=-1的，所以设置thresh=-1
        pdx.det.visualize(img, vis_results, threshold=-1, save_dir=save_dir)

    def predict(self,
                img_file,
                save_dir='./',
                use_erode=True,
                erode_kernel=4,
                score_threshold=0.5,
                seg_batch_size=2):
        """检测图像中的表盘，而后分割出各表盘中的指针和刻度，对分割结果进行读数后处理后得到各表盘的读数。

        参数：
            img_file (str)：待预测的图片路径。
            save_dir (str): 可视化结果的保存路径。
            use_erode (bool, optional): 是否对分割预测结果做图像腐蚀。默认值：True。
            erode_kernel (int, optional): 图像腐蚀的卷积核大小。默认值: 4。
            score_threshold (float, optional): 用于滤除检测框的置信度阈值。默认值：0.5。
            seg_batch_size (int, optional)：分割模型前向推理一次时输入表盘图像的批量大小。默认值为：2。
        """

        img = self.decode(img_file)
        det_results = self.detector.predict(img)
        filtered_results = self.filter_bboxes(det_results, score_threshold)
        sub_imgs = self.roi_crop(img, filtered_results)
        sub_imgs = self.resize(sub_imgs, METER_SHAPE)
        seg_results = self.seg_predict(self.segmenter, sub_imgs,
                                       seg_batch_size)
        seg_results = self.erode(seg_results, erode_kernel)
        meter_readings = self.get_meter_reading(seg_results)
        self.print_meter_readings(meter_readings)
        self.visualize(img, filtered_results, meter_readings, save_dir)

def infer(args):
    image_lists = list()
    if args.image is not None:
        if not osp.exists(args.image):
            raise Exception("Image {} does not exist.".format(args.image))
        if not is_pic(args.image):
            raise Exception("{} is not a picture.".format(args.image))
        image_lists.append(args.image)
    elif args.image_dir is not None:
        if not osp.exists(args.image_dir):
            raise Exception("Directory {} does not exist.".format(
                args.image_dir))
        for im_file in os.listdir(args.image_dir):
            if not is_pic(im_file):
                continue
            im_file = osp.join(args.image_dir, im_file)
            image_lists.append(im_file)

    meter_reader = MeterReader(args.det_model_dir, args.seg_model_dir)
    if len(image_lists) > 0:
        for image in image_lists:
            meter_reader.predict(image, args.save_dir, args.use_erode,
                                 args.erode_kernel, args.score_threshold,
                                 args.seg_batch_size)

if __name__ == '__main__':
    args = parse_args()
    infer(args)