import json
import logging
import traceback
import warnings
from datetime import datetime
from collections import OrderedDict
from typing import Dict, Callable, Optional, Union, List, Any, Type, Sequence

from qiskit.providers.backend import BackendV1 as Backend
from qiskit.providers.provider import ProviderV1 as Provider
from qiskit.providers.exceptions import QiskitBackendNotFoundError
from qiskit.providers.providerutils import filter_backends
from qiskit.providers.models import (
    PulseBackendConfiguration,
    QasmBackendConfiguration,
)

from qiskit_ibm_provider.proxies import ProxyConfiguration
from qiskit_ibm_provider.utils.hgp import to_instance_format, from_instance_format
from qiskit_ibm_provider.utils.backend_decoder import configuration_from_server_data
from qiskit_ibm_runtime import ibm_backend

from .utils.utils import validate_job_tags
from .accounts import AccountManager, Account, ChannelType
from .api.clients import AuthClient, VersionClient
from .api.clients.runtime import RuntimeClient
from .api.exceptions import RequestsApiError
from .constants import QISKIT_IBM_RUNTIME_API_URL
from .exceptions import IBMNotAuthorizedError, IBMInputValueError, IBMAccountError
from .exceptions import (
    IBMRuntimeError,
    RuntimeProgramNotFound,
    RuntimeJobNotFound,
)
from .hub_group_project import HubGroupProject  # pylint: disable=cyclic-import
from .utils.result_decoder import ResultDecoder
from .runtime_job import RuntimeJob
from .utils import RuntimeDecoder, to_python_identifier
from .api.client_parameters import ClientParameters
from .runtime_options import RuntimeOptions
from .ibm_backend import IBMBackend

logger = logging.getLogger(__name__)

SERVICE_NAME = "runtime"

class QiskitRuntimeService(Provider):
    """Class for interacting with the Qiskit Runtime service.

    Qiskit Runtime is a new architecture offered by IBM Quantum that
    streamlines computations requiring many iterations. These experiments will
    execute significantly faster within its improved hybrid quantum/classical
    process.

    A sample workflow of using the runtime service::

        from qiskit_ibm_runtime import QiskitRuntimeService, Session, Sampler, Estimator, Options
        from qiskit.test.reference_circuits import ReferenceCircuits
        from qiskit.circuit.library import RealAmplitudes
        from qiskit.quantum_info import SparsePauliOp

        # Initialize account.
        service = QiskitRuntimeService()

        # Set options, which can be overwritten at job level.
        options = Options(optimization_level=1)

        # Prepare inputs.
        bell = ReferenceCircuits.bell()
        psi = RealAmplitudes(num_qubits=2, reps=2)
        H1 = SparsePauliOp.from_list([("II", 1), ("IZ", 2), ("XI", 3)])
        theta = [0, 1, 1, 2, 3, 5]

        with Session(service=service, backend="ibmq_qasm_simulator") as session:
            # Submit a request to the Sampler primitive within the session.
            sampler = Sampler(session=session, options=options)
            job = sampler.run(circuits=bell)
            print(f"Sampler results: {job.result()}")

            # Submit a request to the Estimator primitive within the session.
            estimator = Estimator(session=session, options=options)
            job = estimator.run(
                circuits=[psi], observables=[H1], parameter_values=[theta]
            )
            print(f"Estimator results: {job.result()}")

    The example above uses the dedicated :class:`~qiskit_ibm_runtime.Sampler`
    and :class:`~qiskit_ibm_runtime.Estimator` classes. You can also
    use the :meth:`run` method directly to invoke a Qiskit Runtime program.

    If the program has any interim results, you can use the ``callback``
    parameter of the :meth:`run` method to stream the interim results.
    Alternatively, you can use the :meth:`RuntimeJob.stream_results` method to stream
    the results at a later time, but before the job finishes.

    The :meth:`run` method returns a
    :class:`RuntimeJob` object. You can use its
    methods to perform tasks like checking job status, getting job result, and
    canceling job.
    """

    global_service = None

    def __init__(
        self,
        channel: Optional[ChannelType] = None,
        token: Optional[str] = None,
        url: Optional[str] = None,
        filename: Optional[str] = None,
        name: Optional[str] = None,
        instance: Optional[str] = None,
        proxies: Optional[dict] = None,
        verify: Optional[bool] = None,
        channel_strategy: Optional[str] = None,
    ) -> None:
        """QiskitRuntimeService constructor

        An account is selected in the following order:

            - Account with the input `name`, if specified.
            - Default account for the `channel` type, if `channel` is specified but `token` is not.
            - Account defined by the input `channel` and `token`, if specified.
            - Account defined by the `default_channel` if defined in filename
            - Account defined by the environment variables, if defined.
            - Default account for the ``ibm_cloud`` account, if one is available.
            - Default account for the ``ibm_quantum`` account, if one is available.

        `instance`, `proxies`, and `verify` can be used to overwrite corresponding
        values in the loaded account.

        Args:
            channel: Channel type. ``ibm_cloud`` or ``ibm_quantum``.
            token: IBM Cloud API key or IBM Quantum API token.
            url: The API URL.
                Defaults to https://cloud.ibm.com (ibm_cloud) or
                https://auth.quantum-computing.ibm.com/api (ibm_quantum).
            filename: Full path of the file where the account is created.
                Default: _DEFAULT_ACCOUNT_CONFIG_JSON_FILE
            name: Name of the account to load.
            instance: The service instance to use.
                For ``ibm_cloud`` runtime, this is the Cloud Resource Name (CRN) or the service name.
                For ``ibm_quantum`` runtime, this is the hub/group/project in that format.
            proxies: Proxy configuration. Supported optional keys are
                ``urls`` (a dictionary mapping protocol or protocol and host to the URL of the proxy,
                documented at https://docs.python-requests.org/en/latest/api/#requests.Session.proxies),
                ``username_ntlm``, ``password_ntlm`` (username and password to enable NTLM user
                authentication)
            verify: Whether to verify the server's TLS certificate.
            channel_strategy: Error mitigation strategy.

        Returns:
            An instance of QiskitRuntimeService.

        Raises:
            IBMInputValueError: If an input is invalid.
        """
        super().__init__()

        self._account = self._discover_account(
            token=token,
            url=url,
            instance=instance,
            channel=channel,
            filename=filename,
            name=name,
            proxies=ProxyConfiguration(**proxies) if proxies else None,
            verify=verify,
            channel_strategy=channel_strategy,
        )

        self._client_params = ClientParameters(
            channel=self._account.channel,
            token=self._account.token,
            url=self._account.url,
            instance=self._account.instance,
            proxies=self._account.proxies,
            verify=self._account.verify,
        )

        self._channel_strategy = channel_strategy or self._account.channel_strategy
        self._channel = self._account.channel
        self._backends: Dict[str, "ibm_backend.IBMBackend"] = {}
        self._backend_configs: Dict[str, Any] = {}

        if self._channel == "ibm_cloud":
            self._api_client = RuntimeClient(self._client_params)
            # TODO: We can make the backend discovery lazy
            self._backends = self._discover_cloud_backends()
            QiskitRuntimeService.global_service = self
            self._validate_channel_strategy()
            return
        else:
            auth_client = self._authenticate_ibm_quantum_account(self._client_params)
            # Update client parameters to use authenticated values.
            self._client_params.url = auth_client.current_service_urls()["services"]["runtime"]
            if self._client_params.url == "https://api.de.quantum-computing.ibm.com/runtime":
                warnings.warn(
                    "Features in versions of qiskit-ibm-runtime greater than and including "
                    "0.13.0 may not be supported in this environment"
                )
            self._client_params.token = auth_client.current_access_token()
            self._api_client = RuntimeClient(self._client_params)
            self._hgps = self._initialize_hgps(auth_client)
            for hgp in self._hgps.values():
                for backend_name in hgp.backends:
                    if backend_name not in self._backends:
                        self._backends[backend_name] = None
            self._current_instance = self._account.instance
            if not self._current_instance:
                self._current_instance = self._get_hgp().name
                logger.info("Default instance: %s", self._current_instance)
        QiskitRuntimeService.global_service = self

        # TODO - it'd be nice to allow some kind of autocomplete, but `service.ibmq_foo`
        # just seems wrong since backends are not runtime service instances.
        # self._discover_backends()

    def _discover_account(
        self,
        token: Optional[str] = None,
        url: Optional[str] = None,
        instance: Optional[str] = None,
        channel: Optional[ChannelType] = None,
        filename: Optional[str] = None,
        name: Optional[str] = None,
        proxies: Optional[ProxyConfiguration] = None,
        verify: Optional[bool] = None,
        channel_strategy: Optional[str] = None,
    ) -> Account:
        """Discover account."""
        account = None
        verify_ = verify or True
        if channel_strategy:
            if channel_strategy not in ["q-ctrl", "default"]:
                raise ValueError(f"{channel_strategy} is not a valid channel strategy.")
            if channel and channel != "ibm_cloud":
                raise ValueError(
                    f"The channel strategy {channel_strategy} is "
                    "only supported on the ibm_cloud channel."
                )
        if name:
            if filename:
                if any([channel, token, url]):
                    logger.warning(
                        "Loading account from file %s with name %s. Any input "
                        "'channel', 'token' or 'url' are ignored.",
                        filename,
                        name,
                    )
            else:
                if any([channel, token, url]):
                    logger.warning(
                        "Loading account with name %s. Any input "
                        "'channel', 'token' or 'url' are ignored.",
                        name,
                    )
            account = AccountManager.get(filename=filename, name=name)
        elif channel:
            if channel and channel not in ["ibm_cloud", "ibm_quantum"]:
                raise ValueError("'channel' can only be 'ibm_cloud' or 'ibm_quantum'")
            if token:
                account = Account.create_account(
                    channel=channel,
                    token=token,
                    url=url,
                    instance=instance,
                    proxies=proxies,
                    verify=verify_,
                    channel_strategy=channel_strategy,
                )
            else:
                if url:
                    logger.warning("Loading default %s account. Input 'url' is ignored.", channel)
                account = AccountManager.get(filename=filename, name=name, channel=channel)
        elif any([token, url]):
            # Let's not infer based on these attributes as they may change in the future.
            raise ValueError(
                "'channel' is required if 'token', or 'url' is specified but 'name' is not."
            )

        # channel is not defined yet, get it from the AccountManager
        if account is None:
            account = AccountManager.get(filename=filename)

        if instance:
            account.instance = instance
        if proxies:
            account.proxies = proxies
        if verify is not None:
            account.verify = verify

        # resolve CRN if needed
        self._resolve_crn(account)

        # ensure account is valid, fail early if not
        account.validate()

        return account

    def _validate_channel_strategy(self) -> None:
        """Raise an error if the passed in channel_strategy and
        instance do not match.

        """
        qctrl_enabled = self._api_client.is_qctrl_enabled()
        if self._channel_strategy == "q-ctrl":
            if not qctrl_enabled:
                raise IBMNotAuthorizedError(
                    "The instance passed in is not compatible with Q-CTRL channel strategy. "
                    "Please switch to or create an instance with the Q-CTRL strategy enabled. "
                    "See https://cloud.ibm.com/docs/quantum-computing?"
                    "topic=quantum-computing-get-started for more information"
                )
        else:
            if qctrl_enabled:
                raise IBMNotAuthorizedError(
                    "The instance passed in is only compatible with Q-CTRL performance "
                    "management strategy. "
                    "To use this instance, set channel_strategy='q-ctrl'."
                )

    def _discover_cloud_backends(self) -> Dict[str, "ibm_backend.IBMBackend"]:
        """Return the remote backends available for this service instance.

        Returns:
            A dict of the remote backend instances, keyed by backend name.
        """
        ret = OrderedDict()  # type: ignore[var-annotated]
        backends_list = self._api_client.list_backends(channel_strategy=self._channel_strategy)
        for backend_name in backends_list:
            raw_config = self._api_client.backend_configuration(backend_name=backend_name)
            config = configuration_from_server_data(
                raw_config=raw_config, instance=self._account.instance
            )
            if not config:
                continue
            ret[config.backend_name] = ibm_backend.IBMBackend(
                configuration=config,
                service=self,
                api_client=self._api_client,
            )
        return ret

    def _resolve_crn(self, account: Account) -> None:
        account.resolve_crn()

    def _authenticate_ibm_quantum_account(self, client_params: ClientParameters) -> AuthClient:
        """Authenticate against IBM Quantum and populate the hub/group/projects.

        Args:
            client_params: Parameters used for server connection.

        Raises:
            IBMInputValueError: If the URL specified is not a valid IBM Quantum authentication URL.
            IBMNotAuthorizedError: If the account is not authorized to use runtime.

        Returns:
            Authentication client.
        """
        version_info = self._check_api_version(client_params)
        # Check the URL is a valid authentication URL.
        if not version_info["new_api"] or "api-auth" not in version_info:
            raise IBMInputValueError(
                "The URL specified ({}) is not an IBM Quantum authentication URL. "
                "Valid authentication URL: {}.".format(
                    client_params.url, QISKIT_IBM_RUNTIME_API_URL
                )
            )
        auth_client = AuthClient(client_params)
        service_urls = auth_client.current_service_urls()
        if not service_urls.get("services", {}).get(SERVICE_NAME):
            raise IBMNotAuthorizedError(
                "This account is not authorized to use ``ibm_quantum`` runtime service."
            )
        return auth_client

    def _initialize_hgps(
        self,
        auth_client: AuthClient,
    ) -> Dict:
        """Authenticate against IBM Quantum and populate the hub/group/projects.

        Args:
            auth_client: Authentication data.

        Raises:
            IBMInputValueError: If the URL specified is not a valid IBM Quantum authentication URL.
            IBMAccountError: If no hub/group/project could be found for this account.
            IBMInputValueError: If instance parameter is not found in hgps.

        Returns:
            The hub/group/projects for this account.
        """
        # pylint: disable=unsubscriptable-object
        hgps: OrderedDict[str, HubGroupProject] = OrderedDict()
        service_urls = auth_client.current_service_urls()
        user_hubs = auth_client.user_hubs()
        for hub_info in user_hubs:
            # Build credentials.
            hgp_params = ClientParameters(
                channel=self._account.channel,
                token=auth_client.current_access_token(),
                url=service_urls["services"]["runtime"],
                instance=to_instance_format(
                    hub_info["hub"], hub_info["group"], hub_info["project"]
                ),
                proxies=self._account.proxies,
                verify=self._account.verify,
            )

            # Build the hgp.
            try:
                hgp = HubGroupProject(
                    client_params=hgp_params, instance=hgp_params.instance, service=self
                )
                hgps[hgp.name] = hgp
            except Exception:  # pylint: disable=broad-except
                # Catch-all for errors instantiating the hgp.
                logger.warning(
                    "Unable to instantiate hub/group/project for %s: %s",
                    hub_info,
                    traceback.format_exc(),
                )
        if not hgps:
            raise IBMAccountError(
                "No hub/group/project that supports Qiskit Runtime could "
                "be found for this account."
            )
        # Move open hgp to end of the list
        if len(hgps) > 1:
            open_key, open_val = hgps.popitem(last=False)
            hgps[open_key] = open_val

        default_hgp = self._account.instance
        if default_hgp:
            if default_hgp in hgps:
                # Move user selected hgp to front of the list
                hgps.move_to_end(default_hgp, last=False)
            else:
                raise IBMInputValueError(
                    f"Hub/group/project {default_hgp} could not be found for this account."
                )
        return hgps

    @staticmethod
    def _check_api_version(params: ClientParameters) -> Dict[str, Union[bool, str]]:
        """Check the version of the remote server in a set of client parameters.

        Args:
            params: Parameters used for server connection.

        Returns:
            A dictionary with version information.
        """
        version_finder = VersionClient(url=params.url, **params.connection_parameters())
        return version_finder.version()

    def _get_hgp(
        self,
        instance: Optional[str] = None,
        backend_name: Optional[Any] = None,
    ) -> HubGroupProject:
        """Return an instance of `HubGroupProject`.

        This function also allows to find the `HubGroupProject` that contains a backend
        `backend_name`.

        Args:
            instance: The hub/group/project to use.
            backend_name: Name of the IBM Quantum backend.

        Returns:
            An instance of `HubGroupProject` that matches the specified criteria or the default.

        Raises:
            IBMInputValueError: If no hub/group/project matches the specified criteria,
                or if the input value is in an incorrect format.
            QiskitBackendNotFoundError: If backend cannot be found.
        """
        if instance:
            _ = from_instance_format(instance)  # Verify format
            if instance not in self._hgps:
                raise IBMInputValueError(
                    f"Hub/group/project {instance} " "could not be found for this account."
                )
            if backend_name and not self._hgps[instance].has_backend(backend_name):
                raise QiskitBackendNotFoundError(
                    f"Backend {backend_name} cannot be found in " f"hub/group/project {instance}"
                )
            return self._hgps[instance]

        if not backend_name:
            return list(self._hgps.values())[0]

        for hgp in self._hgps.values():
            if hgp.has_backend(backend_name):
                return hgp

        error_message = (
            f"Backend {backend_name} cannot be found in any " f"hub/group/project for this account."
        )
        if not isinstance(backend_name, str):
            error_message += (
                f" {backend_name} is of type {type(backend_name)} but should "
                f"instead be initialized through the {self}."
            )

        raise QiskitBackendNotFoundError(error_message)

    def _discover_backends(self) -> None:
        """Discovers the remote backends for this account, if not already known."""
        for backend in self._backends.values():
            backend_name = to_python_identifier(backend.name)
            # Append _ if duplicate
            while backend_name in self.__dict__:
                backend_name += "_"
            setattr(self, backend_name, backend)

    # pylint: disable=arguments-differ
    def backends(
        self,
        name: Optional[str] = None,
        min_num_qubits: Optional[int] = None,
        instance: Optional[str] = None,
        filters: Optional[Callable[[List["ibm_backend.IBMBackend"]], bool]] = None,
        **kwargs: Any,
    ) -> List["ibm_backend.IBMBackend"]:
        """Return all backends accessible via this account, subject to optional filtering.

        Args:
            name: Backend name to filter by.
            min_num_qubits: Minimum number of qubits the backend has to have.
            instance: This is only supported for ``ibm_quantum`` runtime and is in the
                hub/group/project format.
            filters: More complex filters, such as lambda functions.
                For example::

                    QiskitRuntimeService.backends(
                        filters=lambda b: b.max_shots > 50000)
                    QiskitRuntimeService.backends(
                        filters=lambda x: ("rz" in x.basis_gates )

            **kwargs: Simple filters that require a specific value for an attribute in
                backend configuration or status.
                Examples::

                    # Get the operational real backends
                    QiskitRuntimeService.backends(simulator=False, operational=True)

                    # Get the backends with at least 127 qubits
                    QiskitRuntimeService.backends(min_num_qubits=127)

                    # Get the backends that support OpenPulse
                    QiskitRuntimeService.backends(open_pulse=True)

                For the full list of backend attributes, see the `IBMBackend` class documentation
                <https://docs.quantum.ibm.com/api/qiskit/providers_models>

        Returns:
            The list of available backends that match the filter.

        Raises:
            IBMInputValueError: If an input is invalid.
            QiskitBackendNotFoundError: If the backend is not in any instance.
        """
        # TODO filter out input_allowed not having runtime
        backends: List[IBMBackend] = []
        instance_filter = instance if instance else self._account.instance
        if self._channel == "ibm_quantum":
            if name:
                if name not in self._backends:
                    raise QiskitBackendNotFoundError("No backend matches the criteria.")
                if not self._backends[name] or instance != self._backends[name]._instance:
                    self._set_backend_config(name)
                    self._backends[name] = self._create_backend_obj(
                        self._backend_configs[name],
                        instance,
                    )
                if self._backends[name]:
                    backends.append(self._backends[name])
            elif instance_filter:
                hgp = self._get_hgp(instance=instance_filter)
                for backend_name in hgp.backends:
                    if (
                        not self._backends[backend_name]
                        or instance_filter != self._backends[backend_name]._instance
                    ):
                        self._set_backend_config(backend_name, instance_filter)
                        self._backends[backend_name] = self._create_backend_obj(
                            self._backend_configs[backend_name], instance_filter
                        )
                    if self._backends[backend_name]:
                        backends.append(self._backends[backend_name])
            else:
                for backend_name, backend_config in self._backends.items():
                    if not backend_config:
                        self._set_backend_config(backend_name)
                        self._backends[backend_name] = self._create_backend_obj(
                            self._backend_configs[backend_name]
                        )
                    if self._backends[backend_name]:
                        backends.append(self._backends[backend_name])

        else:
            if instance:
                raise IBMInputValueError(
                    "The 'instance' keyword is only supported for ``ibm_quantum`` runtime."
                )
            backends = list(self._backends.values())

        if name:
            kwargs["backend_name"] = name
        if min_num_qubits:
            backends = list(
                filter(lambda b: b.configuration().n_qubits >= min_num_qubits, backends)
            )
        return filter_backends(backends, filters=filters, **kwargs)

    def _set_backend_config(self, backend_name: str, instance: Optional[str] = None) -> None:
        """Retrieve backend configuration and add to backend_configs.
        Args:
            backend_name: backend name that will be returned.
            instance: the current h/g/p.
        """
        if backend_name not in self._backend_configs:
            raw_config = self._api_client.backend_configuration(backend_name)
            config = configuration_from_server_data(raw_config=raw_config, instance=instance)
            self._backend_configs[backend_name] = config

    def _create_backend_obj(
        self,
        config: Union[QasmBackendConfiguration, PulseBackendConfiguration],
        instance: Optional[str] = None,
    ) -> IBMBackend:
        """Given a backend configuration return the backend object.
        Args:
            config: backend configuration.
            instance: the current h/g/p.
        Returns:
            A backend object.
        Raises:
            QiskitBackendNotFoundError: if the backend is not in the hgp passed in.
        """
        if config:
            if not instance:
                for hgp in list(self._hgps.values()):
                    if config.backend_name in hgp.backends:
                        instance = to_instance_format(hgp._hub, hgp._group, hgp._project)
                        break

            elif config.backend_name not in self._get_hgp(instance=instance).backends:
                raise QiskitBackendNotFoundError(
                    f"Backend {config.backend_name} is not in "
                    f"{instance}: please try a different hub/group/project."
                )

            return ibm_backend.IBMBackend(
                instance=instance,
                configuration=config,
                service=self,
                api_client=self._api_client,
            )
        return None

    def active_account(self) -> Optional[Dict[str, str]]:
        """Return the IBM Quantum account currently in use for the session.

        Returns:
            A dictionary with information about the account currently in the session.
        """
        return self._account.to_saved_format()

    @staticmethod
    def delete_account(
        filename: Optional[str] = None,
        name: Optional[str] = None,
        channel: Optional[ChannelType] = None,
    ) -> bool:
        """Delete a saved account from disk.

        Args:
            filename: Name of file from which to delete the account.
            name: Name of the saved account to delete.
            channel: Channel type of the default account to delete.
                Ignored if account name is provided.

        Returns:
            True if the account was deleted.
            False if no account was found.
        """
        return AccountManager.delete(filename=filename, name=name, channel=channel)

    @staticmethod
    def save_account(
        token: Optional[str] = None,
        url: Optional[str] = None,
        instance: Optional[str] = None,
        channel: Optional[ChannelType] = None,
        filename: Optional[str] = None,
        name: Optional[str] = None,
        proxies: Optional[dict] = None,
        verify: Optional[bool] = None,
        overwrite: Optional[bool] = False,
        channel_strategy: Optional[str] = None,
        set_as_default: Optional[bool] = None,
    ) -> None:
        """Save the account to disk for future use.

        Args:
            token: IBM Cloud API key or IBM Quantum API token.
            url: The API URL.
                Defaults to https://cloud.ibm.com (ibm_cloud) or
                https://auth.quantum-computing.ibm.com/api (ibm_quantum).
            instance: The CRN (ibm_cloud) or hub/group/project (ibm_quantum).
            channel: Channel type. `ibm_cloud` or `ibm_quantum`.
            filename: Full path of the file where the account is saved.
            name: Name of the account to save.
            proxies: Proxy configuration. Supported optional keys are
                ``urls`` (a dictionary mapping protocol or protocol and host to the URL of the proxy,
                documented at https://docs.python-requests.org/en/latest/api/#requests.Session.proxies),
                ``username_ntlm``, ``password_ntlm`` (username and password to enable NTLM user
                authentication)
            verify: Verify the server's TLS certificate.
            overwrite: ``True`` if the existing account is to be overwritten.
            channel_strategy: Error mitigation strategy.
            set_as_default: If ``True``, the account is saved in filename,
                as the default account.
        """

        AccountManager.save(
            token=token,
            url=url,
            instance=instance,
            channel=channel,
            filename=filename,
            name=name,
            proxies=ProxyConfiguration(**proxies) if proxies else None,
            verify=verify,
            overwrite=overwrite,
            channel_strategy=channel_strategy,
            set_as_default=set_as_default,
        )

    @staticmethod
    def saved_accounts(
        default: Optional[bool] = None,
        channel: Optional[ChannelType] = None,
        filename: Optional[str] = None,
        name: Optional[str] = None,
    ) -> dict:
        """List the accounts saved on disk.

        Args:
            default: If set to True, only default accounts are returned.
            channel: Channel type. `ibm_cloud` or `ibm_quantum`.
            filename: Name of file whose accounts are returned.
            name: If set, only accounts with the given name are returned.

        Returns:
            A dictionary with information about the accounts saved on disk.

        Raises:
            ValueError: If an invalid account is found on disk.
        """
        return dict(
            map(
                lambda kv: (kv[0], Account.to_saved_format(kv[1])),
                AccountManager.list(
                    default=default, channel=channel, filename=filename, name=name
                ).items(),
            ),
        )

    def backend(
        self,
        name: str = None,
        instance: Optional[str] = None,
    ) -> Backend:
        """Return a single backend matching the specified filtering.

        Args:
            name: Name of the backend.
            instance: This is only supported for ``ibm_quantum`` runtime and is in the
                hub/group/project format. If an instance is not given, among the providers
                with access to the backend, a premium provider will be prioritized.
                For users without access to a premium provider, the default open provider will be used.

        Returns:
            Backend: A backend matching the filtering.

        Raises:
            QiskitBackendNotFoundError: if no backend could be found.
        """
        # pylint: disable=arguments-differ, line-too-long
        backends = self.backends(name, instance=instance)
        if not backends:
            cloud_msg_url = ""
            if self._channel == "ibm_cloud":
                cloud_msg_url = (
                    " Learn more about available backends here "
                    "https://cloud.ibm.com/docs/quantum-computing?topic=quantum-computing-choose-backend "
                )
            raise QiskitBackendNotFoundError("No backend matches the criteria." + cloud_msg_url)
        return backends[0]

    def get_backend(self, name: str = None, **kwargs: Any) -> Backend:
        return self.backend(name, **kwargs)

    def run(
        self,
        program_id: str,
        inputs: Dict,
        options: Optional[Union[RuntimeOptions, Dict]] = None,
        callback: Optional[Callable] = None,
        result_decoder: Optional[Union[Type[ResultDecoder], Sequence[Type[ResultDecoder]]]] = None,
        session_id: Optional[str] = None,
        start_session: Optional[bool] = False,
    ) -> RuntimeJob:
        """Execute the runtime program.

        Args:
            program_id: Program ID.
            inputs: Program input parameters. These input values are passed
                to the runtime program.
            options: Runtime options that control the execution environment.
                See :class:`RuntimeOptions` for all available options.

            callback: Callback function to be invoked for any interim results and final result.
                The callback function will receive 2 positional parameters:

                    1. Job ID
                    2. Job result.

            result_decoder: A :class:`ResultDecoder` subclass used to decode job results.
                If more than one decoder is specified, the first is used for interim results and
                the second final results. If not specified, a program-specific decoder or the default
                ``ResultDecoder`` is used.
            session_id: Job ID of the first job in a runtime session.
            start_session: Set to True to explicitly start a runtime session. Defaults to False.

        Returns:
            A ``RuntimeJob`` instance representing the execution.

        Raises:
            IBMInputValueError: If input is invalid.
            RuntimeProgramNotFound: If the program cannot be found.
            IBMRuntimeError: An error occurred running the program.
        """
        qrt_options: RuntimeOptions = options
        if options is None:
            qrt_options = RuntimeOptions()
        elif isinstance(options, Dict):
            qrt_options = RuntimeOptions(**options)

        qrt_options.validate(channel=self.channel)

        hgp_name = None
        if self._channel == "ibm_quantum":
            # Find the right hgp
            hgp = self._get_hgp(instance=qrt_options.instance, backend_name=qrt_options.backend)
            hgp_name = hgp.name
            if hgp_name != self._current_instance:
                self._current_instance = hgp_name
                logger.info("Instance selected: %s", self._current_instance)
        backend = self.backend(name=qrt_options.backend, instance=hgp_name)
        status = backend.status()
        if status.operational is True and status.status_msg != "active":
            warnings.warn(
                f"The backend {backend.name} currently has a status of {status.status_msg}."
            )
        try:
            response = self._api_client.program_run(
                program_id=program_id,
                backend_name=qrt_options.backend,
                params=inputs,
                image=qrt_options.image,
                hgp=hgp_name,
                log_level=qrt_options.log_level,
                session_id=session_id,
                job_tags=qrt_options.job_tags,
                max_execution_time=qrt_options.max_execution_time,
                start_session=start_session,
                session_time=qrt_options.session_time,
                channel_strategy=None
                if self._channel_strategy == "default"
                else self._channel_strategy,
            )
            if self._channel == "ibm_quantum":
                messages = response.get("messages")
                if messages:
                    warning_message = messages[0].get("data")
                    warnings.warn(warning_message)

        except RequestsApiError as ex:
            if ex.status_code == 404:
                raise RuntimeProgramNotFound(f"Program not found: {ex.message}") from None
            raise IBMRuntimeError(f"Failed to run program: {ex}") from None
        backend = (
            self.backend(name=response["backend"], instance=hgp_name)
            if response["backend"]
            else qrt_options.backend
        )

        job = RuntimeJob(
            backend=backend,
            api_client=self._api_client,
            client_params=self._client_params,
            job_id=response["id"],
            program_id=program_id,
            user_callback=callback,
            result_decoder=result_decoder,
            image=qrt_options.image,
            service=self,
        )
        return job

    def job(self, job_id: str) -> RuntimeJob:
        """Retrieve a runtime job.

        Args:
            job_id: Job ID.

        Returns:
            Runtime job retrieved.

        Raises:
            RuntimeJobNotFound: If the job doesn't exist.
            IBMRuntimeError: If the request failed.
        """
        try:
            response = self._api_client.job_get(job_id, exclude_params=True)
        except RequestsApiError as ex:
            if ex.status_code == 404:
                raise RuntimeJobNotFound(f"Job not found: {ex.message}") from None
            raise IBMRuntimeError(f"Failed to delete job: {ex}") from None
        return self._decode_job(response)

    def jobs(
        self,
        limit: Optional[int] = 10,
        skip: int = 0,
        backend_name: Optional[str] = None,
        pending: bool = None,
        program_id: str = None,
        instance: Optional[str] = None,
        job_tags: Optional[List[str]] = None,
        session_id: Optional[str] = None,
        created_after: Optional[datetime] = None,
        created_before: Optional[datetime] = None,
        descending: bool = True,
    ) -> List[RuntimeJob]:
        """Retrieve all runtime jobs, subject to optional filtering.

        Args:
            limit: Number of jobs to retrieve. ``None`` means no limit.
            skip: Starting index for the job retrieval.
            backend_name: Name of the backend to retrieve jobs from.
            pending: Filter by job pending state. If ``True``, 'QUEUED' and 'RUNNING'
                jobs are included. If ``False``, 'DONE', 'CANCELLED' and 'ERROR' jobs
                are included.
            program_id: Filter by Program ID.
            instance: This is only supported for ``ibm_quantum`` runtime and is in the
                hub/group/project format.
            job_tags: Filter by tags assigned to jobs. Matched jobs are associated with all tags.
            session_id: Job ID of the first job in a runtime session.
            created_after: Filter by the given start date, in local time. This is used to
                find jobs whose creation dates are after (greater than or equal to) this
                local date/time.
            created_before: Filter by the given end date, in local time. This is used to
                find jobs whose creation dates are before (less than or equal to) this
                local date/time.
            descending: If ``True``, return the jobs in descending order of the job
                creation date (i.e. newest first) until the limit is reached.

        Returns:
            A list of runtime jobs.

        Raises:
            IBMInputValueError: If an input value is invalid.
        """
        hub = group = project = None
        if instance:
            if self._channel == "ibm_cloud":
                raise IBMInputValueError(
                    "The 'instance' keyword is only supported for ``ibm_quantum`` runtime."
                )
            hub, group, project = from_instance_format(instance)
        if job_tags:
            validate_job_tags(job_tags)

        job_responses = []  # type: List[Dict[str, Any]]
        current_page_limit = limit or 20
        offset = skip

        while True:
            jobs_response = self._api_client.jobs_get(
                limit=current_page_limit,
                skip=offset,
                backend_name=backend_name,
                pending=pending,
                program_id=program_id,
                hub=hub,
                group=group,
                project=project,
                job_tags=job_tags,
                session_id=session_id,
                created_after=created_after,
                created_before=created_before,
                descending=descending,
            )
            job_page = jobs_response["jobs"]
            # count is the total number of jobs that would be returned if
            # there was no limit or skip
            count = jobs_response["count"]

            job_responses += job_page

            if len(job_responses) == count - skip:
                # Stop if there are no more jobs returned by the server.
                break

            if limit:
                if len(job_responses) >= limit:
                    # Stop if we have reached the limit.
                    break
                current_page_limit = limit - len(job_responses)
            else:
                current_page_limit = 20

            offset += len(job_page)

        return [self._decode_job(job) for job in job_responses]

    def delete_job(self, job_id: str) -> None:
        """Delete a runtime job.

        Note that this operation cannot be reversed.

        Args:
            job_id: ID of the job to delete.

        Raises:
            RuntimeJobNotFound: If the job doesn't exist.
            IBMRuntimeError: If the request failed.
        """
        try:
            self._api_client.job_delete(job_id)
        except RequestsApiError as ex:
            if ex.status_code == 404:
                raise RuntimeJobNotFound(f"Job not found: {ex.message}") from None
            raise IBMRuntimeError(f"Failed to delete job: {ex}") from None

    def _decode_job(self, raw_data: Dict) -> RuntimeJob:
        """Decode job data received from the server.

        Args:
            raw_data: Raw job data received from the server.

        Returns:
            Decoded job data.
        """
        instance = None
        if self._channel == "ibm_quantum":
            hub = raw_data.get("hub")
            group = raw_data.get("group")
            project = raw_data.get("project")
            if all([hub, group, project]):
                instance = to_instance_format(hub, group, project)
        # Try to find the right backend
        try:
            if "backend" in raw_data:
                backend = self.backend(raw_data["backend"], instance=instance)
            else:
                backend = None
        except QiskitBackendNotFoundError:
            backend = ibm_backend.IBMRetiredBackend.from_name(
                backend_name=raw_data["backend"],
                api=None,
            )

        params = raw_data.get("params", {})
        if isinstance(params, list):
            if len(params) > 0:
                params = params[0]
            else:
                params = {}
        if not isinstance(params, str):
            params = json.dumps(params)

        decoded = json.loads(params, cls=RuntimeDecoder)
        return RuntimeJob(
            backend=backend,
            api_client=self._api_client,
            client_params=self._client_params,
            service=self,
            job_id=raw_data["id"],
            program_id=raw_data.get("program", {}).get("id", ""),
            params=decoded,
            creation_date=raw_data.get("created", None),
            session_id=raw_data.get("session_id"),
            tags=raw_data.get("tags"),
        )

    def least_busy(
        self,
        min_num_qubits: Optional[int] = None,
        instance: Optional[str] = None,
        filters: Optional[Callable[[List["ibm_backend.IBMBackend"]], bool]] = None,
        **kwargs: Any,
    ) -> ibm_backend.IBMBackend:
        """Return the least busy available backend.

        Args:
            min_num_qubits: Minimum number of qubits the backend has to have.
            instance: This is only supported for ``ibm_quantum`` runtime and is in the
                hub/group/project format.
            filters: Filters can be defined as for the :meth:`backends` method.
                An example to get the operational backends with 5 qubits::

                    QiskitRuntimeService.least_busy(n_qubits=5, operational=True)

        Returns:
            The backend with the fewest number of pending jobs.

        Raises:
            QiskitBackendNotFoundError: If no backend matches the criteria.
        """
        backends = self.backends(
            min_num_qubits=min_num_qubits, instance=instance, filters=filters, **kwargs
        )
        candidates = []
        for back in backends:
            backend_status = back.status()
            if not backend_status.operational or backend_status.status_msg != "active":
                continue
            candidates.append(back)
        if not candidates:
            raise QiskitBackendNotFoundError("No backend matches the criteria.")
        return min(candidates, key=lambda b: b.status().pending_jobs)

    def instances(self) -> List[str]:
        """Return the IBM Quantum instances list currently in use for the session.

        Returns:
            A list with instances currently in the session.
        """
        if self._channel == "ibm_quantum":
            return list(self._hgps.keys())
        return []

    @property
    def channel(self) -> str:
        """Return the channel type used.

        Returns:
            The channel type used.
        """
        return self._channel

    @property
    def runtime(self):  # type:ignore
        """Return self for compatibility with IBMQ provider.

        Returns:
            self
        """
        return self

    def __repr__(self) -> str:
        return "<{}>".format(self.__class__.__name__)


import discord
from utils.auth import AuthManager

class Account(discord.Cog):
    @discord.slash_command(name="register", description="Register using your discord username")
    async def register(self, ctx):
        AuthManager.registerGuild(ctx.author)
        AuthManager.registerUser(ctx.author)
        await ctx.response.send_message(content=AuthManager.signUp(ctx.author), ephemeral=True)

def setup(bot):  # this is called by Pycord to setup the cog
    bot.add_cog(Account(bot))  # add the cog to the bot


from PyQt5.QtWidgets import *
from PyQt5.QtCore import Qt
from QLed import QLed

class Box(QGroupBox):

  instances = []

  def __init__(self, name, opcID='opcID', horizontal_spacing=10, width=100):
    #self.setTitle(name)
    super().__init__(name)
    self.instances.append(self)
    self.opcName=name
    mainLayout = QFormLayout()
    self.state = False

    self.setEnabled(self.state)

    self.led1=QLed(onColour=QLed.Green, shape=QLed.Circle)
    self.led2=QLed(onColour=QLed.Green, shape=QLed.Circle)
    self.led3=QLed(onColour=QLed.Green, shape=QLed.Circle)

    self.radioBtn1=QRadioButton('Hand')
    self.radioBtn2=QRadioButton('AUS')
    #self.radioBtn2.setChecked(True)
    #self.led2.value = True
    self.radioBtn3=QRadioButton('AUTO')

    self.opcID=opcID
    self.radioBtn1.clicked.connect(self.write1)
    self.radioBtn2.clicked.connect(self.write2)
    self.radioBtn3.clicked.connect(self.write3)

    mainLayout.addRow(self.radioBtn1,self.led1)
    mainLayout.addRow(self.radioBtn2,self.led2)
    mainLayout.addRow(self.radioBtn3,self.led3)

    #Settings:
    mainLayout.setVerticalSpacing(8)
    mainLayout.setFormAlignment(Qt.AlignLeft)
    mainLayout.setHorizontalSpacing(horizontal_spacing)
    self.setFixedHeight(120)
    self.setFixedWidth(width)

    self.setLayout(mainLayout)

  @classmethod
  def set_all_states(cls, state):
        for instance in cls.instances:
            instance.state = state
            instance.setEnabled(state)

  def write1(self):
    if self.led1.value==False:
        print(self.opcID+': '+ self.radioBtn1.text())
    self.led1.setValue(True)
    self.led2.setValue(False)  # Add this line
    self.led3.setValue(False)  # Add this line

  def write2(self):

    if self.led2.value==False:
      print(self.opcID+': '+ self.radioBtn2.text())

    self.led2.setValue(True)
    self.led1.setValue(False)
    self.led3.setValue(False)

  def write3(self):

    if self.led3.value==False:
      print(self.opcID+': '+ self.radioBtn3.text())

    self.led2.setValue(False)
    self.led1.setValue(False)
    self.led3.setValue(True)

  def update(self,val):

    # self.led1.value=val[self.opcName+'.Hand']
    # self.led2.value=val[self.opcName+'.AUS']
    # self.led3.value=val[self.opcName+'.AUTO']
    if (val[self.opcName+'.Hand']):
      self.radioBtn2.setChecked(False)
      self.radioBtn3.setChecked(False)
      self.radioBtn1.setChecked(True)

      self.led2.setValue(False)
      self.led3.setValue(False)
      self.led1.setValue(True)

      print("Led1 is true")

    elif (val[self.opcName+'.AUS']):
      self.radioBtn1.setChecked(False)
      self.radioBtn2.setChecked(True)
      self.radioBtn3.setChecked(False)
      self.led1.setValue(False)
      self.led2.setValue(True)
      self.led3.setValue(False)
      print("Led2 is true")

    elif (val[self.opcName+'.AUTO']):
      self.radioBtn1.setChecked(False)
      self.radioBtn2.setChecked(False)
      self.radioBtn3.setChecked(True)
      self.led1.setValue(False)
      self.led2.setValue(False)
      self.led3.setValue(True)
      print("Led3 is true")

    #print(val[self.opcName+'.Hand'])


"""
Привет! ID успешной посылки: 54853357
_____________________________________
Задача:
Гоша реализовал структуру данных Дек, максимальный размер которого определяется заданным числом. Методы push_back(x),
push_front(x), pop_back(), pop_front() работали корректно. Но, если в деке было много элементов, программа работала
очень долго. Дело в том, что не все операции выполнялись за O(1). Помогите Гоше! Напишите эффективную реализацию.

Внимание: при реализации нельзя использовать связный список.
____________________________________________________________
Так как связный список использовать нельзя, я подумал и пришел к тому, что самое оптимальное решение это использовать
два массива, НО потом мне подсказали и я подумал еще получше - циклический буфер будет лучшим выбором!
Будем использовать для метода push_back и push_front для вставки значений в конец и начало буфера.
Также мы заведем в классе Deque поле size, в котором будем хранить максимальный размер Дека, задаваемый во входных
данных. Поля tail и head для хранения индексов хвоста буфера и начала. Поле count - хранение количества элементов в
буфере. Ну и соответственно проинициализируем саму очередь на заданный размер.
Идея алгоритма простая, мы считываем данные, упаковываем их в массив, далее через оператов if - elif на нужные команды
вызываем нужные методы.

Для реализации алгоритма нам нужен класс, в котором будет реализованы следующие методы:
 * push_back(value) - добавляет элемент в конец буфера
 * push_front(value) - добавляем элемент в начало буфера
 * pop_back() - удаляет элемент из конца буфера
 * pop_front() - удаляет элемент из начала буфера
Два дополнительный метода is_full() и is_empty() позволят нам отлавливать моменты, когда дека заполнена или пуста, и
выкидывать в методах добавления и удаления элементом исключения, которые мы будем обрабатывать снаружи.
При добавление в конец проверяем, что буфер не заполнен, далее проверяем, что элементы в буфере уже есть, проверяем
если tail + 1 == size, то обнуляем tail, в противном случае увеличиваем tail на 1, для того, чтобы не перезатереть
значение, которое уже лежит в буфере. Если буфер пустой, то tail и  head обнуляем и записываем по индексу tail значение
value. Увеличиваем счетчик элементов буфере на 1.
Аналогичная ситуация для добавления в начало. Только здесь необходимо следить за индексом в head для того, чтобы не
перезатереть значение, которое уже записано в буфер. Добавление происходит по индексу head, и увеличение счетчика на 1.
Далее методы удаления элементов.
Удаление с конца. Проверяем буфер на пустоту. Сохраняем текущий индекс в idx из tail во временную переменную именно по
этому индексу мы и извлечем значение. Далее нам нужно переопределить индексы в tail и head, чтобы они указывали на
правильные позиции буфера после удаления элемента. Уменьшаем счетчик на 1. Берем элемент по индексу idx из буфера, а на
его место записываем None. Удалять элементы нельзя, чтобы не изменился размер буфера. По идее элементы можно не заменять
на None, а просто сдвигать tail и head на нужные новые позиции и уменьшать счетчик. Но в задании указано удалить и мы
его удаляем.
Удаление с начала аналогичное. В idx сохраняем индекс из head, далее переопределяем tail и head для новых позиций,
уменьшаем счетчик на 1 и возвращаем элемент.
------------------------------------------------------------
Про сложность.
Алгоритм выполняется за линейное время O(n), где n - количество команд.
Сами операции выполняются за O(1).
Мы тратим на работу алгоритма O(n) памяти, потому что длина буфера не превосходят 0 <= n <= 50_000,
где n это маскимальный размер Дека.
------------------------------------------------------------
Данные посылки:
0.56s 19.71Mb

"""

from typing import List, Tuple, NoReturn

class Deque:

    def __init__(self, n: int):
        self.queue = [None] * n
        self.head = 0
        self.tail = 0
        self.size = n
        self.count = 0

    def is_full(self):
        return self.count == self.size

    def is_empty(self):
        return self.count == 0

    def push_back(self, value):
        if self.is_full():
            raise IndexError()
        if self.count:
            if self.tail + 1 == self.size:
                self.tail = 0
            else:
                self.tail += 1
        else:
            self.tail = self.head = 0

        self.queue[self.tail] = value
        self.count += 1

    def push_front(self, value: int):
        if self.is_full():
            raise IndexError()
        if self.count:
            if self.head - 1 < 0:
                self.head = self.size - 1
            else:
                self.head -= 1
        else:
            self.tail = self.head = 0

        self.queue[self.head] = value
        self.count += 1

    def pop_back(self):
        if self.is_empty():
            raise IndexError()
        idx = self.tail
        if self.count == 1:
            self.tail = self.head = -1
        else:
            if self.tail - 1 < 0:
                self.tail = self.size - 1
            else:
                self.tail -= 1
        self.count -= 1
        item = self.queue[idx]
        self.queue[idx] = None
        return item

    def pop_front(self):
        if self.is_empty():
            raise IndexError()
        idx = self.head
        if self.count == 1:
            self.tail = self.head = -1
        else:
            if self.head + 1 == self.size:
                self.head = 0
            else:
                self.head += 1
        self.count -= 1
        item = self.queue[idx]
        self.queue[idx] = None
        return item

def input_data() -> Tuple[int, List[Tuple[str, ...]]]:
    n = int(input().strip())
    m = int(input().strip())
    command_list = []
    while n:
        command = tuple(input().strip().split())
        command_list.append(command)
        n -= 1
    return m, command_list

def solution(deque_length: int, command_list: List[Tuple[str, ...]]) -> NoReturn:
    deque = Deque(deque_length)
    for command in command_list:
        if command[0] == 'push_back':
            try:
                deque.push_back(int(command[1]))
            except IndexError:
                print('error')
        elif command[0] == 'push_front':
            try:
                deque.push_front(int(command[1]))
            except IndexError:
                print('error')
        elif command[0] == 'pop_back':
            try:
                print(deque.pop_back())
            except IndexError:
                print('error')
        elif command[0] == 'pop_front':
            try:
                print(deque.pop_front())
            except IndexError:
                print('error')

if __name__ == '__main__':
    solution(*input_data())


# Definition for a binary tree node.
# class TreeNode:
#     def __init__(self, val=0, left=None, right=None):
#         self.val = val
#         self.left = left
#         self.right = right
class Solution:
    def generateTrees(self, n: int) -> List[TreeNode]:
        def dfs(start: int,end: int) -> List[TreeNode]:
            if start > end:
                return [None]
            i = start
            result = []
            while i <= end:
                lefts = dfs(start,i-1)
                rights = dfs(i+1,end)
                for left in lefts:
                    for right in rights:
                        root = TreeNode(i)
                        root.left = left
                        root.right = right
                        result.append(root)
                i = i + 1
            return result

        reslult = dfs(1,n)
        return reslult


import setuptools

with open("README.md", "r") as fh:
    long_description = fh.read()

setuptools.setup(
    name="webull",
    version="0.6.1",
    author="ted chou",
    description="The unofficial python interface for the WeBull API",
    license='MIT',
    author_email="ted.chou12@gmail.com",
    long_description=long_description,
    long_description_content_type="text/markdown",
    url="https://github.com/tedchou12/webull.git",
    packages=setuptools.find_packages(),
    install_requires=[
        "certifi>=2020.4.5.1",
        "chardet>=3.0.4",
        "idna>=2.9",
        "numpy>=1.18.4",
        "pandas>=0.25.3",
        "python-dateutil>=2.8.1",
        "pytz>=2020.1",
        "requests>=2.23.0",
        "six>=1.14.0",
        "urllib3>=1.25.9",
        "email-validator>=1.1.0",
        "paho-mqtt>=1.6.0"
    ],
    classifiers=[
        "Programming Language :: Python :: 3",
        "License :: OSI Approved :: MIT License",
        "Operating System :: OS Independent",
    ],
    python_requires='>=3.6',
)


#!/usr/bin/env python3
import fire
import logging
import os, sys, traceback
from IsoNet.util.dict2attr import Arg,check_parse,idx2list
from fire import core
from IsoNet.util.metadata import MetaData,Label,Item

class ISONET:
    """
    ISONET: Train on tomograms and restore missing-wedge\n
    for detail description, run one of the following commands:

    isonet.py prepare_star -h
    isonet.py prepare_subtomo_star -h
    isonet.py deconv -h
    isonet.py make_mask -h
    isonet.py extract -h
    isonet.py refine -h
    isonet.py predict -h
    isonet.py resize -h
    isonet.py gui -h
    """
    #log_file = "log.txt"

    def prepare_star(self,folder_name, output_star='tomograms.star',pixel_size = 10.0, defocus = 0.0, number_subtomos = 100):
        """
        \nThis command generates a tomograms.star file from a folder containing only tomogram files (.mrc or .rec).\n
        isonet.py prepare_star folder_name [--output_star] [--pixel_size] [--defocus] [--number_subtomos]
        :param folder_name: (None) directory containing tomogram(s). Usually 1-5 tomograms are sufficient.
        :param output_star: (tomograms.star) star file similar to that from "relion". You can modify this file manually or with gui.
        :param pixel_size: (10) pixel size in angstroms. Usually you want to bin your tomograms to about 10A pixel size.
        Too large or too small pixel sizes are not recommended, since the target resolution on Z-axis of corrected tomograms should be about 30A.
        :param defocus: (0.0) defocus in Angstrom. Only need for ctf deconvolution. For phase plate data, you can leave defocus 0.
        If you have multiple tomograms with different defocus, please modify them in star file or with gui.
        :param number_subtomos: (100) Number of subtomograms to be extracted in later processes.
        If you want to extract different number of subtomograms in different tomograms, you can modify them in the star file generated with this command or with gui.

        """
        md = MetaData()
        md.addLabels('rlnIndex','rlnMicrographName','rlnPixelSize','rlnDefocus','rlnNumberSubtomo','rlnMaskBoundary')
        tomo_list = sorted(os.listdir(folder_name))
        i = 0
        for tomo in tomo_list:
            if tomo[-4:] == '.rec' or tomo[-4:] == '.mrc':
                i+=1
                it = Item()
                md.addItem(it)
                md._setItemValue(it,Label('rlnIndex'),str(i))
                md._setItemValue(it,Label('rlnMicrographName'),os.path.join(folder_name,tomo))
                md._setItemValue(it,Label('rlnPixelSize'),pixel_size)
                md._setItemValue(it,Label('rlnDefocus'),defocus)
                md._setItemValue(it,Label('rlnNumberSubtomo'),number_subtomos)
                md._setItemValue(it,Label('rlnMaskBoundary'),None)
        md.write(output_star)

    def prepare_subtomo_star(self, folder_name, output_star='subtomo.star', pixel_size: float=10.0, cube_size = None):
        """
        \nThis command generates a subtomo star file from a folder containing only subtomogram files (.mrc).
        This command is usually not necessary in the traditional workflow, because "isonet.py extract" will generate this subtomo.star for you.\n
        isonet.py prepare_subtomo_star folder_name [--output_star] [--cube_size]
        :param folder_name: (None) directory containing subtomogram(s).
        :param output_star: (subtomo.star) output star file for subtomograms, will be used as input in refinement.
        :param pixel_size: (10) The pixel size in angstrom of your subtomograms.
        :param cube_size: (None) This is the size of the cubic volumes used for training. This values should be smaller than the size of subtomogram.
        And the cube_size should be divisible by 8. If this value isn't set, cube_size is automatically determined as int(subtomo_size / 1.5 + 1)//16 * 16
        """
        #TODO check folder valid, logging
        if not os.path.isdir(folder_name):
            print("the folder does not exist")
        import mrcfile
        md = MetaData()
        md.addLabels('rlnSubtomoIndex','rlnImageName','rlnCubeSize','rlnCropSize','rlnPixelSize')
        subtomo_list = sorted(os.listdir(folder_name))
        for i,subtomo in enumerate(subtomo_list):
            subtomo_name = os.path.join(folder_name,subtomo)
            try:
                with mrcfile.open(subtomo_name, mode='r', permissive=True) as s:
                    crop_size = s.header.nx
            except:
                print("Warning: Can not process the subtomogram: {}!".format(subtomo_name))
                continue
            if cube_size is not None:
                cube_size = int(cube_size)
                if cube_size >= crop_size:
                    cube_size = int(crop_size / 1.5 + 1)//16 * 16
                    print("Warning: Cube size should be smaller than the size of subtomogram volume! Using cube size {}!".format(cube_size))
            else:
                cube_size = int(crop_size / 1.5 + 1)//16 * 16
            it = Item()
            md.addItem(it)
            md._setItemValue(it,Label('rlnSubtomoIndex'),str(i+1))
            md._setItemValue(it,Label('rlnImageName'),subtomo_name)
            md._setItemValue(it,Label('rlnCubeSize'),cube_size)
            md._setItemValue(it,Label('rlnCropSize'),crop_size)
            md._setItemValue(it,Label('rlnPixelSize'),pixel_size)

            # f.write(str(i+1)+' ' + os.path.join(folder_name,tomo) + '\n')
        md.write(output_star)

    def deconv(self, star_file: str,
        deconv_folder:str="./deconv",
        voltage: float=300.0,
        cs: float=2.7,
        snrfalloff: float=None,
        deconvstrength: float=None,
        highpassnyquist: float=0.02,
        chunk_size: int=None,
        overlap_rate: float= 0.25,
        ncpu:int=4,
        tomo_idx: str=None):
        """
        \nCTF deconvolution for the tomograms.\n
        isonet.py deconv star_file [--deconv_folder] [--snrfalloff] [--deconvstrength] [--highpassnyquist] [--overlap_rate] [--ncpu] [--tomo_idx]
        This step is recommended because it enhances low resolution information for a better contrast. No need to do deconvolution for phase plate data.
        :param deconv_folder: (./deconv) Folder created to save deconvoluted tomograms.
        :param star_file: (None) Star file for tomograms.
        :param voltage: (300.0) Acceleration voltage in kV.
        :param cs: (2.7) Spherical aberration in mm.
        :param snrfalloff: (1.0) SNR fall rate with the frequency. High values means losing more high frequency.
        If this value is not set, the program will look for the parameter in the star file.
        If this value is not set and not found in star file, the default value 1.0 will be used.
        :param deconvstrength: (1.0) Strength of the deconvolution.
        If this value is not set, the program will look for the parameter in the star file.
        If this value is not set and not found in star file, the default value 1.0 will be used.
        :param highpassnyquist: (0.02) Highpass filter for at very low frequency. We suggest to keep this default value.
        :param chunk_size: (None) When your computer has enough memory, please keep the chunk_size as the default value: None . Otherwise, you can let the program crop the tomogram into multiple chunks for multiprocessing and assembly them into one. The chunk_size defines the size of individual chunk. This option may induce artifacts along edges of chunks. When that happen, you may use larger overlap_rate.
        :param overlap_rate: (None) The overlapping rate for adjecent chunks.
        :param ncpu: (4) Number of cpus to use.
        :param tomo_idx: (None) If this value is set, process only the tomograms listed in this index. e.g. 1,2,4 or 5-10,15,16
        """
        from IsoNet.util.deconvolution import deconv_one

        logging.basicConfig(format='%(asctime)s, %(levelname)-8s %(message)s',
        datefmt="%m-%d %H:%M:%S",level=logging.INFO,handlers=[logging.StreamHandler(sys.stdout)])
        logging.info('\n######Isonet starts ctf deconvolve######\n')

        try:
            md = MetaData()
            md.read(star_file)
            if not 'rlnSnrFalloff' in md.getLabels():
                md.addLabels('rlnSnrFalloff','rlnDeconvStrength','rlnDeconvTomoName')
                for it in md:
                    md._setItemValue(it,Label('rlnSnrFalloff'),1.0)
                    md._setItemValue(it,Label('rlnDeconvStrength'),1.0)
                    md._setItemValue(it,Label('rlnDeconvTomoName'),None)

            if not os.path.isdir(deconv_folder):
                os.mkdir(deconv_folder)

            tomo_idx = idx2list(tomo_idx)
            for it in md:
                if tomo_idx is None or str(it.rlnIndex) in tomo_idx:
                    if snrfalloff is not None:
                        md._setItemValue(it,Label('rlnSnrFalloff'), snrfalloff)
                    if deconvstrength is not None:
                        md._setItemValue(it,Label('rlnDeconvStrength'),deconvstrength)

                    tomo_file = it.rlnMicrographName
                    base_name = os.path.basename(tomo_file)
                    deconv_tomo_name = '{}/{}'.format(deconv_folder,base_name)

                    deconv_one(it.rlnMicrographName,deconv_tomo_name,voltage=voltage,cs=cs,defocus=it.rlnDefocus/10000.0, pixel_size=it.rlnPixelSize,snrfalloff=it.rlnSnrFalloff, deconvstrength=it.rlnDeconvStrength,highpassnyquist=highpassnyquist,chunk_size=chunk_size,overlap_rate=overlap_rate,ncpu=ncpu)
                    md._setItemValue(it,Label('rlnDeconvTomoName'),deconv_tomo_name)
                md.write(star_file)
            logging.info('\n######Isonet done ctf deconvolve######\n')

        except Exception:
            error_text = traceback.format_exc()
            f =open('log.txt','a+')
            f.write(error_text)
            f.close()
            logging.error(error_text)

    def make_mask(self,star_file,
                mask_folder: str = 'mask',
                patch_size: int=4,
                mask_boundary: str=None,
                density_percentage: int=None,
                std_percentage: int=None,
                use_deconv_tomo:bool=True,
                z_crop:float=None,
                tomo_idx=None):
        """
        \ngenerate a mask that include sample area and exclude "empty" area of the tomogram. The masks do not need to be precise. In general, the number of subtomograms (a value in star file) should be lesser if you masked out larger area. \n
        isonet.py make_mask star_file [--mask_folder] [--patch_size] [--density_percentage] [--std_percentage] [--use_deconv_tomo] [--tomo_idx]
        :param star_file: path to the tomogram or tomogram folder
        :param mask_folder: path and name of the mask to save as
        :param patch_size: (4) The size of the box from which the max-filter and std-filter are calculated.
        :param density_percentage: (50) The approximate percentage of pixels to keep based on their local pixel density.
        If this value is not set, the program will look for the parameter in the star file.
        If this value is not set and not found in star file, the default value 50 will be used.
        :param std_percentage: (50) The approximate percentage of pixels to keep based on their local standard deviation.
        If this value is not set, the program will look for the parameter in the star file.
        If this value is not set and not found in star file, the default value 50 will be used.
        :param use_deconv_tomo: (True) If CTF deconvolved tomogram is found in tomogram.star, use that tomogram instead.
        :param z_crop: If exclude the top and bottom regions of tomograms along z axis. For example, "--z_crop 0.2" will mask out the top 20% and bottom 20% region along z axis.
        :param tomo_idx: (None) If this value is set, process only the tomograms listed in this index. e.g. 1,2,4 or 5-10,15,16
        """
        from IsoNet.bin.make_mask import make_mask
        logging.basicConfig(format='%(asctime)s, %(levelname)-8s %(message)s',
        datefmt="%m-%d %H:%M:%S",level=logging.INFO,handlers=[logging.StreamHandler(sys.stdout)])
        logging.info('\n######Isonet starts making mask######\n')
        try:
            if not os.path.isdir(mask_folder):
                os.mkdir(mask_folder)
            # write star percentile threshold
            md = MetaData()
            md.read(star_file)
            if not 'rlnMaskDensityPercentage' in md.getLabels():
                md.addLabels('rlnMaskDensityPercentage','rlnMaskStdPercentage','rlnMaskName')
                for it in md:
                    md._setItemValue(it,Label('rlnMaskDensityPercentage'),50)
                    md._setItemValue(it,Label('rlnMaskStdPercentage'),50)
                    md._setItemValue(it,Label('rlnMaskName'),None)

            tomo_idx = idx2list(tomo_idx)
            for it in md:
                if tomo_idx is None or str(it.rlnIndex) in tomo_idx:
                    if density_percentage is not None:
                        md._setItemValue(it,Label('rlnMaskDensityPercentage'),density_percentage)
                    if std_percentage is not None:
                        md._setItemValue(it,Label('rlnMaskStdPercentage'),std_percentage)
                    if use_deconv_tomo and "rlnDeconvTomoName" in md.getLabels() and it.rlnDeconvTomoName not in [None,'None']:
                        tomo_file = it.rlnDeconvTomoName
                    else:
                        tomo_file = it.rlnMicrographName
                    tomo_root_name = os.path.splitext(os.path.basename(tomo_file))[0]

                    if os.path.isfile(tomo_file):
                        logging.info('make_mask: {}| dir_to_save: {}| percentage: {}| window_scale: {}'.format(tomo_file,
                        mask_folder, it.rlnMaskDensityPercentage, patch_size))

                        #if mask_boundary is None:
                        if "rlnMaskBoundary" in md.getLabels() and it.rlnMaskBoundary not in [None, "None"]:
                            mask_boundary = it.rlnMaskBoundary
                        else:
                            mask_boundary = None

                        mask_out_name = '{}/{}_mask.mrc'.format(mask_folder,tomo_root_name)
                        make_mask(tomo_file,
                                mask_out_name,
                                mask_boundary=mask_boundary,
                                side=patch_size,
                                density_percentage=it.rlnMaskDensityPercentage,
                                std_percentage=it.rlnMaskStdPercentage,
                                surface = z_crop)

                    md._setItemValue(it,Label('rlnMaskName'),mask_out_name)
                md.write(star_file)
            logging.info('\n######Isonet done making mask######\n')
        except Exception:
            error_text = traceback.format_exc()
            f =open('log.txt','a+')
            f.write(error_text)
            f.close()
            logging.error(error_text)

    def extract(self,
        star_file: str,
        use_deconv_tomo: bool = True,
        subtomo_folder: str = "subtomo",
        subtomo_star: str = "subtomo.star",
        cube_size: int = 64,
        crop_size: int = None,
        log_level: str="info",
        tomo_idx = None
        ):

        """
        \nExtract subtomograms\n
        isonet.py extract star_file [--subtomo_folder] [--subtomo_star] [--cube_size] [--use_deconv_tomo] [--tomo_idx]
        :param star_file: tomogram star file
        :param subtomo_folder: (subtomo) folder for output subtomograms.
        :param subtomo_star: (subtomo.star) star file for output subtomograms.
        :param cube_size: (64) Size of cubes for training, should be divisible by 8, eg. 32, 64. The actual sizes of extracted subtomograms are this value adds 16.
        :param crop_size: (None) The size of subtomogram, should be larger then the cube_size The default value is 16+cube_size.
        :param log_level: ("info") level of the output, either "info" or "debug"
        :param use_deconv_tomo: (True) If CTF deconvolved tomogram is found in tomogram.star, use that tomogram instead.
        """
        d = locals()
        d_args = Arg(d)

        if d_args.log_level == "debug":
            logging.basicConfig(format='%(asctime)s, %(levelname)-8s [%(filename)s:%(lineno)d] %(message)s'
            ,datefmt="%H:%M:%S",level=logging.DEBUG,handlers=[logging.StreamHandler(sys.stdout)])
        else:
            logging.basicConfig(format='%(asctime)s, %(levelname)-8s [%(filename)s:%(lineno)d] %(message)s'
            ,datefmt="%m-%d %H:%M:%S",level=logging.INFO,handlers=[logging.StreamHandler(sys.stdout)])

        logging.info("\n######Isonet starts extracting subtomograms######\n")

        try:
            if os.path.isdir(subtomo_folder):
                logging.warning("subtomo directory exists, the current directory will be overwritten")
                import shutil
                shutil.rmtree(subtomo_folder)
            os.mkdir(subtomo_folder)

            from IsoNet.preprocessing.prepare import extract_subtomos
            if crop_size is None:
                d_args.crop_size = cube_size + 16
            else:
                d_args.crop_size = crop_size
            d_args.subtomo_dir = subtomo_folder
            d_args.tomo_idx = idx2list(tomo_idx)
            extract_subtomos(d_args)
            logging.info("\n######Isonet done extracting subtomograms######\n")
        except Exception:
            error_text = traceback.format_exc()
            f =open('log.txt','a+')
            f.write(error_text)
            f.close()
            logging.error(error_text)

    def refine(self,
        subtomo_star: str,
        gpuID: str = None,
        iterations: int = None,
        data_dir: str = None,
        pretrained_model: str = None,
        log_level: str = None,
        result_dir: str='results',
        remove_intermediate: bool =False,
        select_subtomo_number: int = None,
        preprocessing_ncpus: int = 16,
        continue_from: str=None,
        epochs: int = 10,
        batch_size: int = None,
        steps_per_epoch: int = None,

        noise_level:  tuple=(0.05,0.10,0.15,0.20),
        noise_start_iter: tuple=(11,16,21,26),
        noise_mode: str = None,
        noise_dir: str = None,
        learning_rate: float = None,
        drop_out: float = 0.3,
        convs_per_depth: int = 3,
        kernel: tuple = (3,3,3),
        pool: tuple = None,
        unet_depth: int = 3,
        filter_base: int = None,
        batch_normalization: bool = True,
        normalize_percentile: bool = True,

    ):
        """
        \ntrain neural network to correct missing wedge\n
        isonet.py refine subtomo_star [--iterations] [--gpuID] [--preprocessing_ncpus] [--batch_size] [--steps_per_epoch] [--noise_start_iter] [--noise_level]...
        :param subtomo_star: (None) star file containing subtomogram(s).
        :param gpuID: (0,1,2,3) The ID of gpu to be used during the training. e.g 0,1,2,3.
        :param pretrained_model: (None) A trained neural network model in ".h5" format to start with.
        :param iterations: (30) Number of training iterations.
        :param data_dir: (data) Temporary folder to save the generated data used for training.
        :param log_level: (info) debug level, could be 'info' or 'debug'
        :param continue_from: (None) A Json file to continue from. That json file is generated at each iteration of refine.
        :param result_dir: ('results') The name of directory to save refined neural network models and subtomograms
        :param preprocessing_ncpus: (16) Number of cpu for preprocessing.

        ************************Training settings************************

        :param epochs: (10) Number of epoch for each iteraction.
        :param batch_size: (None) Size of the minibatch.If None, batch_size will be the max(2 * number_of_gpu,4). batch_size should be divisible by the number of gpu.
        :param steps_per_epoch: (None) Step per epoch. If not defined, the default value will be min(num_of_subtomograms * 6 / batch_size , 200)

        ************************Denoise settings************************

        :param noise_level: (0.05,0.1,0.15,0.2) Level of noise STD(added noise)/STD(data) after the iteration defined in noise_start_iter.
        :param noise_start_iter: (11,16,21,26) Iteration that start to add noise of corresponding noise level.
        :param noise_mode: (None) Filter names when generating noise volumes, can be 'ramp', 'hamming' and 'noFilter'
        :param noise_dir: (None) Directory for generated noise volumes. If set to None, the Noise volumes should appear in results/training_noise

        ************************Network settings************************

        :param drop_out: (0.3) Drop out rate to reduce overfitting.
        :param learning_rate: (0.0004) learning rate for network training.
        :param convs_per_depth: (3) Number of convolution layer for each depth.
        :param kernel: (3,3,3) Kernel for convolution
        :param unet_depth: (3) Depth of UNet.
        :param filter_base: (64) The base number of channels after convolution.
        :param batch_normalization: (True) Use Batch Normalization layer
        :param pool: (False) Use pooling layer instead of stride convolution layer.
        :param normalize_percentile: (True) Normalize the 5 percent and 95 percent pixel intensity to 0 and 1 respectively. If this is set to False, normalize the input to 0 mean and 1 standard dievation.
        """
        from IsoNet.bin.refine import run
        d = locals()
        d_args = Arg(d)
        with open('log.txt','a+') as f:
            f.write(' '.join(sys.argv[0:]) + '\n')
        run(d_args)

    def predict(self, star_file: str, model: str, output_dir: str='./corrected_tomos', gpuID: str = None, cube_size:int=64,
    crop_size:int=96,use_deconv_tomo=True, batch_size:int=None,normalize_percentile: bool=True,log_level: str="info", tomo_idx=None):
        """
        \nPredict tomograms using trained model\n
        isonet.py predict star_file model [--gpuID] [--output_dir] [--cube_size] [--crop_size] [--batch_size] [--tomo_idx]
        :param star_file: star for tomograms.
        :param output_dir: file_name of output predicted tomograms
        :param model: path to trained network model .h5
        :param gpuID: (0,1,2,3) The gpuID to used during the training. e.g 0,1,2,3.
        :param cube_size: (64) The tomogram is divided into cubes to predict due to the memory limitation of GPUs.
        :param crop_size: (96) The side-length of cubes cropping from tomogram in an overlapping patch strategy, make this value larger if you see the patchy artifacts
        :param batch_size: The batch size of the cubes grouped into for network predicting, the default parameter is four times number of gpu
        :param normalize_percentile: (True) if normalize the tomograms by percentile. Should be the same with that in refine parameter.
        :param log_level: ("debug") level of message to be displayed, could be 'info' or 'debug'
        :param tomo_idx: (None) If this value is set, process only the tomograms listed in this index. e.g. 1,2,4 or 5-10,15,16
        :param use_deconv_tomo: (True) If CTF deconvolved tomogram is found in tomogram.star, use that tomogram instead.
        :raises: AttributeError, KeyError
        """
        d = locals()
        d_args = Arg(d)
        from IsoNet.bin.predict import predict

        if d_args.log_level == "debug":
            logging.basicConfig(format='%(asctime)s, %(levelname)-8s %(message)s',
            datefmt="%m-%d %H:%M:%S",level=logging.DEBUG,handlers=[logging.StreamHandler(sys.stdout)])
        else:
            logging.basicConfig(format='%(asctime)s, %(levelname)-8s %(message)s',
            datefmt="%m-%d %H:%M:%S",level=logging.INFO,handlers=[logging.StreamHandler(sys.stdout)])
        try:
            predict(d_args)
        except:
            error_text = traceback.format_exc()
            f =open('log.txt','a+')
            f.write(error_text)
            f.close()
            logging.error(error_text)

    def resize(self, star_file:str, apix: float=15, out_folder="tomograms_resized"):
        '''
        This function rescale the tomograms to a given pixelsize
        '''
        md = MetaData()
        md.read(star_file)
        #print(md._data[0].rlnPixelSize)
        from scipy.ndimage import zoom
        #from skimage.transform import rescale
        #import numpy as np
        import mrcfile
        if not os.path.isdir(out_folder):
            os.makedirs(out_folder)
        for item in md._data:
            ori_apix = item.rlnPixelSize
            tomo_name = item.rlnMicrographName
            zoom_factor = float(ori_apix)/apix
            new_tomo_name = "{}/{}".format(out_folder,os.path.basename(tomo_name))
            with mrcfile.open(tomo_name) as mrc:
                data = mrc.data
            print("scaling: {}".format(tomo_name))
            new_data = zoom(data, zoom_factor,order=3, prefilter=False)
            #new_data = rescale(data, zoom_factor,order=3, anti_aliasing = True)
            #new_data = new_data.astype(np.float32)

            with mrcfile.new(new_tomo_name,overwrite=True) as mrc:
                mrc.set_data(new_data)
                mrc.voxel_size = apix

            item.rlnPixelSize = apix
            print(new_tomo_name)
            item.rlnMicrographName = new_tomo_name
            print(item.rlnMicrographName)
        md.write(os.path.splitext(star_file)[0] + "_resized.star")
        print("scale_finished")

    def check(self):
        from IsoNet.bin.predict import predict
        from IsoNet.bin.refine import run
        import skimage
        import PyQt5
        import tqdm
        print('IsoNet --version 0.2 installed')

    def gui(self):
        """
        \nGraphic User Interface\n
        """
        import IsoNet.gui.Isonet_star_app as app
        app.main()

def Display(lines, out):
    text = "\n".join(lines) + "\n"
    out.write(text)

def pool_process(p_func,chunks_list,ncpu):
    from multiprocessing import Pool
    with Pool(ncpu,maxtasksperchild=1000) as p:
        # results = p.map(partial_func,chunks_gpu_num_list,chunksize=1)
        results = list(p.map(p_func,chunks_list))
    # return results

if __name__ == "__main__":
    core.Display = Display
    # logging.basicConfig(format='%(asctime)s, %(levelname)-8s %(message)s',datefmt="%m-%d %H:%M:%S",level=logging.INFO)
    if len(sys.argv) > 1:
        check_parse(sys.argv[1:])
    fire.Fire(ISONET)


import numpy as np
import pandas as pd

from nltk.tokenize import RegexpTokenizer
from nltk.corpus import stopwords

from PyQt5 import QtGui
from PyQt5.QtWidgets import *

import sys

from PIL import Image

from wordCloud.WC import Ui_MainWindow

from wordcloud import WordCloud
from wordcloud import ImageColorGenerator
from collections import Counter
from konlpy.tag import Hannanum

import matplotlib.pyplot as plt
from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas

class Main(QMainWindow, Ui_MainWindow):
    def __init__(self):
        super().__init__()
        self.setupUi(self)

        self.lines = ""  # 연설문
        self.nowlang = self.lang.currentText()
        self.eg_wordlist = []
        self.kr_wordlist = []

        self.mask = None
        self.canvas = None

        self.textbutton.clicked.connect(self.choseText)
        self.imgbutton.clicked.connect(self.choseImage)
        self.langbutton.clicked.connect(self.choselang)

    def clearAll(self):
        self.lines = ""
        self.plainTextEdit.clear()
        for i in reversed(range(self.verticalLayout.count())):
            self.verticalLayout.itemAt(i).widget().setParent(None) # layout비우는 방법
        for i in reversed(range(self.verticalLayout_2.count())):
            self.verticalLayout_2.itemAt(i).widget().setParent(None)

    def choselang(self):
        self.nowlang = self.lang.currentText()

    def choseImage(self):
        for i in reversed(range(self.verticalLayout_2.count())):
            self.verticalLayout_2.itemAt(i).widget().setParent(None)

        fileName, _ = QFileDialog.getOpenFileName(self, '불러올 img file을 선택하세요.', '', 'img Files(*.png)')
        if fileName:
            self.mask = np.array(Image.open(fileName))

            if self.nowlang == "영어":
                self.makeImgWordCloud(self.eg_wordlist)
            else:
                self.makeImgWordCloud(self.kr_wordlist)

            self.label_2.setPixmap(QtGui.QPixmap(fileName).scaled(400, 300))

    def choseText(self):
        self.clearAll()
        fileName, _ = QFileDialog.getOpenFileName(self, '불러올 txt file을 선택하세요.', '', 'txt Files(*.txt)')

        self.label.setText(fileName.split("/")[-1].split(".")[0] + " WordCloud")
        if fileName:
            f = open(fileName, "r", encoding="cp949")

            if self.nowlang == "영어":
                self.lines = f.readlines()[0]
                f.close()
                self.makeEgWordList()
            else:
                self.lines = f.readlines()
                f.close()
                self.makeKrWordList()

    def makeEgWordList(self):
        tokenizer = RegexpTokenizer("[\w]+")  # word 단위로 구분하라
        stop_words = stopwords.words("english")  # 단어는 자주 등장하지만 실제 의미 분석에는 의미 없는단어

        words = self.lines.lower()
        tokens = tokenizer.tokenize(words)
        stopped_tokens = [i for i in list(tokens) if not i in stop_words]
        self.eg_wordlist = [i for i in stopped_tokens if len(i) > 1]
        self.makeTop20Word(self.eg_wordlist)
        self.makeWordCloud(self.eg_wordlist)

    def flatten(self, l):
        flatList = []
        for elem in l:
            if type(elem) == list:
                for e in elem:
                    flatList.append(e)
            else:
                flatList.append(elem)
        return flatList

    def makeKrWordList(self):

        hannanum = Hannanum()
        temp = []
        for i in range(len(self.lines)):
            temp.append(hannanum.nouns(self.lines[i]))

        word_list = self.flatten(temp)

        self.kr_wordlist = pd.Series([x for x in word_list if len(x) > 1])
        self.makeTop20Word(self.kr_wordlist)
        self.makeWordCloud(self.kr_wordlist)

    def makeTop20Word(self, wordlist):
        keys = pd.Series(wordlist).value_counts().head(20).keys()
        values = pd.Series(wordlist).value_counts().head(20).values

        for i in range(len(keys)):
            self.plainTextEdit.appendPlainText("{} : {}개".format(keys[i], values[i]))

    def makeWordCloud(self, wordlist):

        font_path = '/Library/Fonts/AppleGothic.ttf'

        wordcloud = WordCloud(font_path=font_path, width=800, height=800, background_color="white")

        count = Counter(wordlist)
        wordcloud = wordcloud.generate_from_frequencies(count)

        def __array__(self):
            return self.to_array()

        def to_array(self):
            return np.array(self.to_image())

        array = wordcloud.to_array()

        fig = plt.figure()
        plt.imshow(array, interpolation="bilinear")
        self.canvas = FigureCanvas(fig)
        self.canvas.draw()
        self.verticalLayout.addWidget(self.canvas)
        self.canvas.show()

    def makeImgWordCloud(self, wordlist):
        font_path = '/Library/Fonts/AppleGothic.ttf'

        count = Counter(wordlist)

        wc = WordCloud(font_path=font_path, mask=self.mask, background_color="white")

        wc = wc.generate_from_frequencies(count)

        image_color = ImageColorGenerator(self.mask)

        fig = plt.figure(figsize=(8, 8))
        plt.imshow(wc.recolor(color_func=image_color), interpolation="bilinear")
        plt.axis("off")
        self.canvas = FigureCanvas(fig)
        self.canvas.draw()
        self.verticalLayout_2.addWidget(self.canvas)
        self.canvas.show()

if __name__ == '__main__':
    app = QApplication(sys.argv)
    you_viewer_main = Main()
    you_viewer_main.show()
    app.exec_()


from django.contrib import admin
from django.urls import path
from form1 import views

urlpatterns = [
    path('form1', views.index,name='index'),
    path('form2', views.form2, name='Supervisor'),
    path('', views.login_view, name='home'),
    path('signup', views.signup_view, name='signup'),
    path('menu', views.menu_view, name='menu'),
    path('login', views.login_view, name='login'),
    path('logout', views.logout_view, name='logout'),
    path('movetohistory/<int:case_id>', views.move_to_history, name='MoveToHistory'),
    path('add_frv', views.create_frv ,name='AddFrv'),
    path('driver', views.driver, name='driver'),
    #path('drivermap', views.drivermap, name='drivermap'),
    path('location/case/get', views.get_case_location, name='GetCaseLocation'),
    path('location/case/set', views.set_case_location, name='SaveCaseLocation'),
    path('assignfrv', views.assign_frv, name='AssignFRV' ),
    #path('location/frv/get', views.get_frv_location, name='GetFRVocation'),
    #path('location/frv/gset', views.set_frv_location, name='SetFRVLocation'),

]


import os
import time
import json
import torch
import random
import warnings
import torchvision
import numpy as np
import pandas as pd
import pathlib

from utils import *
from data import HumanDataset
from data import process_df
from data import process_submission_leakdata_full
from data import process_loss_weight
from data import process_together_labels
from tqdm import tqdm
from config import config
from datetime import datetime
from models.model import *
from torch import nn, optim
from collections import OrderedDict
from torch.autograd import Variable
from torch.utils.data.sampler import WeightedRandomSampler
from torch.utils.data import DataLoader
from torch.optim import lr_scheduler
from sklearn.model_selection import train_test_split
from timeit import default_timer as timer
from sklearn.metrics import f1_score
from PIL import Image
import matplotlib.pyplot as plt

# 1. set random seed
random.seed(2050)
np.random.seed(2050)
torch.manual_seed(2050)
torch.cuda.manual_seed_all(2050)
os.environ["CUDA_VISIBLE_DEVICES"] = "0"
torch.backends.cudnn.benchmark = True
warnings.filterwarnings('ignore')

index_class_dict = {
    0: "Nucleoplasm",
    1: "Nuclear membrane",
    2: "Nucleoli",
    3: "Nucleoli fibrillar center",
    4: "Nuclear speckles",
    5: "Nuclear bodies",
    6: "Endoplasmic reticulum",
    7: "Golgi apparatus",
    8: "Peroxisomes",
    9: "Endosomes",
    10: "Lysosomes",
    11: "Intermediate filaments",
    12: "Actin filaments",
    13: "Focal adhesion sites",
    14: "Microtubules",
    15: "Microtubule ends",
    16: "Cytokinetic bridge",
    17: "Mitotic spindle",
    18: "Microtubule organizing center",
    19: "Centrosome",
    20: "Lipid droplets",
    21: "Plasma membrane",
    22: "Cell junctions",
    23: "Mitochondria",
    24: "Aggresome",
    25: "Cytosol",
    26: "Cytoplasmic bodies",
    27: "Rods & rings"
}

def check(check_loader, model, folds, val_data_list):
    model.cuda()
    model.eval()
    count = 0
    wrong_id = []
    wrong_class = []
    true_target = []
    wrong_target = []
    true_name = []
    wrong_name = []
    pred = []
    for i, (image, target) in enumerate(tqdm(check_loader)):
        with torch.no_grad():
            image = image.cuda(non_blocking=True)
            y_pred = model(image)
            label = y_pred.sigmoid().cpu().data.numpy()
            label_orig = label.copy().reshape((-1))
            ll = label.copy().reshape((-1))
            ll = -ll
            ll.sort()
            ll = -ll
            threshold = config.threshold
            # if threshold < ll[3]:
            #     threshold = ll[3]
            label = label >= threshold
            label = label.reshape(-1)
            target = np.array(target)
            target = target.reshape(-1)
            flag = True
            for j in range(len(label)):
                if label[j] != target[j]:
                    flag = False
                    break
            if not flag or flag:
                count += 1
                name = val_data_list.iloc[i].Id
                wrong_img_path = os.path.join(config.train_data, name)
                target1 = ' '.join(list([str(k) for k in np.nonzero(target)]))
                label1 = ' '.join(list([str(k) for k in np.nonzero(label)]))
                label1_name = '-&-'.join(list([index_class_dict[k] for k in np.nonzero(label)]))
                label_orig = ' '.join(list(str('%1.2f' % k) for k in label_orig))
                wrong_id.append(str(name))
                wrong_class.append(str(flag))
                true_target.append(target1)
                wrong_target.append(label1)
                pred.append(label_orig)

                images = np.zeros(shape=(512, 512, 3), dtype=np.float)
                r = Image.open(wrong_img_path + "_red.png")
                g = Image.open(wrong_img_path + "_green.png")
                b = Image.open(wrong_img_path + "_blue.png")
                y = Image.open(wrong_img_path + "_yellow.png")
                images[:, :, 0] = np.array(r) / 2 + np.array(y) / 2
                images[:, :, 1] = g
                images[:, :, 2] = b
                images = images.astype(np.uint8)
                f0 = plt.figure(0, figsize=(20, 25))
                f0.suptitle('%s    True:%s     Pred:%s     Pred_name%s' % (str(flag), target1, label1, label1_name))
                ax1 = plt.subplot2grid((5, 4), (0, 0), fig=f0)
                ax2 = plt.subplot2grid((5, 4), (0, 1))
                ax3 = plt.subplot2grid((5, 4), (0, 2))
                ax4 = plt.subplot2grid((5, 4), (0, 3))
                ax5 = plt.subplot2grid((5, 4), (1, 0), rowspan=4, colspan=4)
                ax1.imshow(np.array(r), cmap="Reds")
                ax1.set_title("true:")
                ax2.imshow(np.array(g), cmap="Greens")
                ax2.set_title("pred:")
                ax3.imshow(np.array(b), cmap="Blues")
                ax4.imshow(np.array(y), cmap="Oranges")
                ax5.imshow(images)
                plt.waitforbuttonpress(0)
                plt.close(f0)
    if wrong_id is not []:
        df = pd.DataFrame({
            'Id': wrong_id,
            'True': wrong_class,
            'True_Target': true_target,
            'Pred_Target': wrong_target,
            'pred': pred
        })
        df.to_csv('wrong_classification.csv')

def main():
    fold = config.fold
    model = get_net()
    model.cuda()
    best_model = torch.load(
        "%s/%s_fold_%s_model_best_%s.pth.tar" % (config.best_models, config.model_name, str(fold), config.best))
    model.load_state_dict(best_model["state_dict"])

    train_files = pd.read_csv(config.train_csv)
    external_files = pd.read_csv(config.external_csv)
    test_files = pd.read_csv(config.test_csv)
    all_files, test_files, weight_log = process_df(train_files, external_files, test_files)
    train_data_list, val_data_list = train_test_split(all_files, test_size=0.13, random_state=2050)
    val_data_list = val_data_list[val_data_list['is_external'] == 0]

    check_gen = HumanDataset(val_data_list, augument=False, mode="train")
    check_loader = DataLoader(check_gen, 1, shuffle=False, pin_memory=True, num_workers=6)

    check(check_loader, model, fold, val_data_list)

if __name__ == "__main__":
    main()