## started with a copy from https://github.com/keatontaylor/alexa-actions VERSION 0.8.2

# UPDATE THESE VARIABLES WITH YOUR CONFIG
HOME_ASSISTANT_URL                = 'https://yourhainstall.com'       # REPLACE WITH THE URL FOR YOUR HA FRONTEND
VERIFY_SSL                        = True                              # SET TO FALSE IF YOU DO NOT HAVE VALID CERTS
TOKEN                             = ''                                # ADD YOUR LONG LIVED TOKEN IF NEEDED OTHERWISE LEAVE BLANK

### NO NEED TO EDIT ANYTHING UNDER THE LINE ###
import logging
import urllib3
import json
import isodate
import prompts
from datetime import datetime

import ask_sdk_core.utils as ask_utils
from ask_sdk_core.skill_builder import SkillBuilder
from ask_sdk_core.dispatch_components import AbstractRequestHandler
from ask_sdk_core.dispatch_components import AbstractExceptionHandler
from ask_sdk_core.dispatch_components import AbstractRequestInterceptor
from ask_sdk_core.handler_input import HandlerInput
from ask_sdk_model import SessionEndedReason
from ask_sdk_model.slu.entityresolution import StatusCode
from ask_sdk_model import Response

logger = logging.getLogger(__name__)
logger.setLevel(logging.INFO)

INPUT_TEXT_ENTITY = "input_text.alexa_ha_report"

class Borg:
    """Borg MonoState Class for State Persistence."""
    _shared_state = {}
    def __init__(self):
        self.__dict__ = self._shared_state

class HomeAssistant(Borg):
    """HomeAssistant Wrapper Class."""
    def __init__(self, handler_input=None):
        Borg.__init__(self)
        if handler_input:
            self.handler_input = handler_input

        self.token = self._fetch_token() if TOKEN == "" else TOKEN

        if not hasattr(self, 'ha_state') or self.ha_state is None:
            self.get_ha_state()

    def _clear_state(self):
        self.ha_state = None

    def _fetch_token(self):
        return ask_utils.get_account_linking_access_token(self.handler_input)

    def _check_response_errors(self, response):
        data = self.handler_input.attributes_manager.request_attributes["_"]
        if response.status == 401:
            logger.error("401 Error", response.data)
            speak_output = "Error 401 " + data[prompts.ERROR_401]
            return speak_output
        elif response.status == 404:
            logger.error("404 Error", response.data)
            speak_output = "Error 404 " + data[prompts.ERROR_404]
            return speak_output
        elif response.status >= 400:
            logger.error("{response.status} Error", response.data)
            speak_output = "Error {response.status} " + data[prompts.ERROR_400]
            return speak_output

        return None

    def get_ha_state(self):
        """Get State from HA."""

        http = urllib3.PoolManager(
            cert_reqs='CERT_REQUIRED' if VERIFY_SSL else 'CERT_NONE',
            timeout=urllib3.Timeout(connect=10.0, read=10.0)
        )

        response = http.request(
            'GET',
            '{}/api/states/{}'.format(HOME_ASSISTANT_URL, INPUT_TEXT_ENTITY),
            headers={
                'Authorization': 'Bearer {}'.format(self.token),
                'Content-Type': 'application/json'
            },
        )

        errors = self._check_response_errors(response)
        if not errors:
            self.ha_state = {
                "error": True,
                "text": errors
            }

        decoded_response = json.loads(response.data.decode('utf-8'))['state']

        self.ha_state = {
            "error": False,
            "text": json.loads(decoded_response)['text']
        }

class LaunchRequestHandler(AbstractRequestHandler):
    """Handler for Skill Launch."""
    def can_handle(self, handler_input):
        return (ask_utils.is_request_type("LaunchRequest")(handler_input) or
                ask_utils.is_intent_name("ReportIntent")(handler_input))

    def handle(self, handler_input):
        home_assistant_object = HomeAssistant(handler_input)
        speak_output = home_assistant_object.ha_state['text']

        return (
            handler_input.response_builder
            .speak(speak_output)
            .response
        )

class CancelOrStopIntentHandler(AbstractRequestHandler):
    """Single handler for Cancel and Stop Intent."""
    def can_handle(self, handler_input):
        return (ask_utils.is_intent_name("AMAZON.CancelIntent")(handler_input) or
                ask_utils.is_intent_name("AMAZON.StopIntent")(handler_input))

    def handle(self, handler_input):
        print("CancelOrStopIntentHandler")
        data = handler_input.attributes_manager.request_attributes["_"]
        speak_output = data[prompts.STOP_MESSAGE]

        return (
            handler_input.response_builder
                .speak(speak_output)
                .response
        )

class SessionEndedRequestHandler(AbstractRequestHandler):
    """Handler for Session End."""
    def can_handle(self, handler_input):
        return ask_utils.is_request_type("SessionEndedRequest")(handler_input)

    def handle(self, handler_input):
        home_assistant_object = HomeAssistant(handler_input)
        if handler_input.request_envelope.request.reason == SessionEndedReason.EXCEEDED_MAX_REPROMPTS:
            home_assistant_object.post_ha_event(RESPONSE_NONE, RESPONSE_NONE)

        return handler_input.response_builder.response

class CatchAllExceptionHandler(AbstractExceptionHandler):
    """Generic error handling to capture any syntax or routing errors. If you receive an error
    stating the request handler chain is not found, you have not implemented a handler for
    the intent being invoked or included it in the skill builder below.
    """
    def can_handle(self, handler_input, exception):
        return True

    def handle(self, handler_input, exception):
        print("CatchAllExceptionHandler")
        logger.error(exception, exc_info=True)
        home_assistant_object = HomeAssistant()

        data = handler_input.attributes_manager.request_attributes["_"]
        if hasattr(home_assistant_object, 'ha_state') and home_assistant_object.ha_state != None and 'text' in home_assistant_object.ha_state:
            speak_output = data[prompts.ERROR_ACOUSTIC].format(home_assistant_object.ha_state['text'])
            return (
                handler_input.response_builder
                    .speak(speak_output)
                    .ask('')
                    .response
            )
        else:
            speak_output = data[prompts.ERROR_CONFIG].format(home_assistant_object.ha_state['text'])
            return (
                handler_input.response_builder
                    .speak(speak_output)
                    .response
            )

class LocalizationInterceptor(AbstractRequestInterceptor):
    """Add function to request attributes, that can load locale specific data."""

    def process(self, handler_input):
        locale = handler_input.request_envelope.request.locale
        logger.info("Locale is {}".format(locale[:2]))

        # localized strings stored in language_strings.json
        with open("language_strings.json") as language_prompts:
            language_data = json.load(language_prompts)
        # set default translation data to broader translation
        data = language_data[locale[:2]]
        # if a more specialized translation exists, then select it instead
        # example: "fr-CA" will pick "fr" translations first, but if "fr-CA" translation exists,
    #          then pick that instead
        if locale in language_data:
            data.update(language_data[locale])
        handler_input.attributes_manager.request_attributes["_"] = data

# The SkillBuilder object acts as the entry point for your skill, routing all request and response
# payloads to the handlers above. Make sure any new handlers or interceptors you've
# defined are included below. The order matters - they're processed top to bottom.

sb = SkillBuilder()

# register request / intent handlers
sb.add_request_handler(LaunchRequestHandler())

# register exception handlers
sb.add_exception_handler(CatchAllExceptionHandler())

# register response interceptors
sb.add_global_request_interceptor(LocalizationInterceptor())

lambda_handler = sb.lambda_handler()


from multiprocessing.pool import ThreadPool
from bs4 import BeautifulSoup

import requests
from time import sleep

import pandas as pd
from xml.etree import ElementTree

import epo_ops
import yaml
from tqdm import tqdm

# Create an EPO patent based upon the search results as parameters.
# Return a dictionary of EPO Patents
def create_epo_patent(client, search_responses_document_ids, search_responses_document_countries,
                      search_responses_document_kinds):
    epo_patents = {}
    patent_inventors = []
    patent_applicants = []

    # Attempt to attain published data on a specific patent
    # Due to API restrictions, it can result in an HTTP error.
    # If so, delay and reattempt.
    try:
        response = client.published_data(  # Retrieve bibliography data
            reference_type='publication',  # publication, application, priority
            input=epo_ops.models.Docdb(search_responses_document_ids, search_responses_document_countries,
                                       search_responses_document_kinds),  # original, docdb, epodoc
            endpoint='biblio',  # optional, defaults to biblio in case of published_data
            constituents=[]  # optional, list of constituents
        )
    except requests.exceptions.HTTPError as e:
        print("Attempt to retrieve bibliography data failed. \nReason: {}.\nReattempting.".format(e))
        sleep(0.1)
        response = client.published_data(  # Retrieve bibliography data
            reference_type='publication',  # publication, application, priority
            input=epo_ops.models.Docdb(search_responses_document_ids, search_responses_document_countries,
                                       search_responses_document_kinds),  # original, docdb, epodoc
            endpoint='biblio',  # optional, defaults to biblio in case of published_data
            constituents=[]  # optional, list of constituents
        )
    contents = response.text

    '''
        Using BeautifulSoup, parse the resulting xml response from OPS for the key information
        required for each patent, mainly the patent applicants, inventors, title and abstract text.
    '''
    soup = BeautifulSoup(contents, "lxml")

    applicants = soup.findAll("applicants", {"data-format": "original"})
    for applicant in applicants:
        patent_applicants.append(applicant.get_text())

    inventors = soup.findAll("inventor", {"data-format": "original"})

    for inventor in inventors:
        patent_inventors.append(inventor.get_text())

    title = soup.find('invention-title')
    patent_title = title.get_text()

    abstract = soup.find('abstract')
    if abstract is None:
        patent_abstract = ""
    else:
        patent_abstract = abstract.get_text()

    epo_patents['Applicants'] = patent_applicants
    epo_patents['Inventor Names'] = patent_inventors
    epo_patents['Patent Title'] = patent_title
    epo_patents['Patent Abstract'] = patent_abstract

    return epo_patents

# Get patent IDs for specific search term.
def get_epo_patent_ids_for_search_term(client, term, range_begin, range_end):
    search_responses_document_ids = []
    search_responses_document_countries = []
    search_responses_document_kinds = []
    try:
        response = client.published_data_search(cql='ti=' + term, range_begin=range_begin, range_end=range_end,
                                                constituents=None)
    except requests.exceptions.HTTPError as e:
        print("Attempt to retrieve published data failed.\nReason: {}.\nReattempting.".format(e))
        sleep(0.1)
        response = client.published_data_search(cql='ti=' + term, range_begin=range_begin, range_end=range_end,
                                                constituents=None)
    tree = ElementTree.fromstring(response.content)

    '''
        Parse resulting xml into specific lists, i.e. document-number, country of origin and the type of document.
    '''

    for doc_id in tree.iter('{http://www.epo.org/exchange}doc-number'):
        search_responses_document_ids.append(doc_id.text)

    for country in tree.iter('{http://www.epo.org/exchange}country'):
        search_responses_document_countries.append(country.text)

    for kind in tree.iter('{http://www.epo.org/exchange}kind'):
        search_responses_document_kinds.append(kind.text)

    return search_responses_document_ids, search_responses_document_countries, search_responses_document_kinds

# For each search term, retrieve the top N patents for each term where N is range max.
# NOTE: Due to restrictions from OPS, range_max cannot exceed 2000
# OPS Key and Secret can be generated from developers.epo.org
def get_epo_patents_for_search_term(search_term):
    client = epo_ops.Client(key='XXXXX', secret='XXXXX')  # Instantiate client

    search_term_epo_patent_ids = []
    search_term_epo_patent_country_origin = []
    search_term_epo_patent_type = []
    search_results = {}

    range_begin = 1
    range_max = 500

    while range_begin <= range_max:
        epo_patent_ids, epo_patent_countries, epo_patent_types = get_epo_patent_ids_for_search_term(client, search_term,
                                                                                range_begin,
                                                                                (range_begin + 99))
        if not epo_patent_ids or not epo_patent_countries or not epo_patent_types:
            continue
        else:
            search_term_epo_patent_ids.extend(epo_patent_ids)
            search_term_epo_patent_country_origin.extend(epo_patent_countries)
            search_term_epo_patent_type.extend(epo_patent_types)
        range_begin += 100

    # Create search text as dict. Applicants (Companies typically), inventor names, Title, abstract

    for i in range(0, len(search_term_epo_patent_ids)):
        if search_term_epo_patent_ids[i] is None or search_term_epo_patent_country_origin[i] is None or \
                search_term_epo_patent_type[i] is None:
            continue
        else:
            search_results[search_term_epo_patent_ids[i]] = create_epo_patent(client, search_term_epo_patent_ids[i],
                                                                              search_term_epo_patent_country_origin[i],
                                                                              search_term_epo_patent_type[i])
    return search_results

def imap_unordered_bar(func, args, n_processes=4):
    p = ThreadPool(n_processes)
    res_list = []
    with tqdm(total=len(args)) as pbar:
        for i, res in tqdm(enumerate(p.imap_unordered(func, args))):
            pbar.update()
            res_list.append(res)
    pbar.close()
    p.close()
    p.join()
    return res_list

if __name__ == "__main__":
    with open("../EPO/Config/cfg_search_terms.yml", 'r') as ymlfile:
        cfg_search_terms = yaml.load(ymlfile)

    search_terms = cfg_search_terms["search_terms"]["categories"]
    search_terms_epo_patents = imap_unordered_bar(get_epo_patents_for_search_term, search_terms)

    df = pd.DataFrame(search_terms_epo_patents)
    df.to_csv("../EPO/EPOPatents.csv")


from unittest import skipUnless, skipIf

from django import VERSION
from django.contrib.auth.models import User
from django.core.exceptions import MultipleObjectsReturned
from django.db.models.manager import Manager
from django.test.testcases import TestCase

from moderation.managers import ModerationObjectsManager
from moderation.models import ModeratedObject
from moderation.moderator import GenericModerator
from tests.models import ModelWithSlugField2, ModelWithVisibilityField, UserProfile
from tests.utils import setup_moderation, teardown_moderation

class ModerationObjectsManagerTestCase(TestCase):
    fixtures = ['test_users.json', 'test_moderation.json']

    def setUp(self):

        self.user = User.objects.get(username='moderator')
        self.profile = UserProfile.objects.get(user__username='moderator')

        class UserProfileModerator(GenericModerator):
            visibility_column = 'is_public'

        self.moderation = setup_moderation(
            [UserProfile, (ModelWithVisibilityField, UserProfileModerator)]
        )

    def tearDown(self):
        teardown_moderation()

    def test_moderation_objects_manager(self):
        ManagerClass = ModerationObjectsManager()(Manager)

        self.assertEqual(
            str(ManagerClass.__bases__),
            "(<class 'moderation.managers.ModerationObjectsManager'>"
            ", <class 'django.db.models.manager.Manager'>)",
        )

    def test_filter_moderated_objects_returns_empty_queryset(self):
        """Test filter_moderated_objects returns empty queryset
        for object that has moderated object"""

        ManagerClass = ModerationObjectsManager()(Manager)
        manager = ManagerClass()
        manager.model = UserProfile

        query_set = UserProfile._default_manager.all()
        moderated_object = ModeratedObject(content_object=self.profile)
        moderated_object.save()

        self.assertEqual(str(list(manager.filter_moderated_objects(query_set))), "[]")

    def test_filter_moderated_objects_returns_object(self):
        """Test if filter_moderated_objects return object when object
        doesn't have moderated object or deserialized object is <> object"""
        moderated_object = ModeratedObject(content_object=self.profile)
        moderated_object.save()
        moderated_object.approve()

        self.profile.description = "New"
        self.profile.save()

        self.assertEqual(
            str(list(UserProfile.objects.all())),
            '[<UserProfile: moderator - http://www.google.com>]',
        )

    def test_exclude_objs_by_visibility_col(self):
        ManagerClass = ModerationObjectsManager()(Manager)
        manager = ManagerClass()
        manager.model = ModelWithVisibilityField

        ModelWithVisibilityField(test='test 1').save()
        ModelWithVisibilityField(test='test 2', is_public=True).save()

        query_set = ModelWithVisibilityField.objects.all()

        query_set = manager.exclude_objs_by_visibility_col(query_set)

        self.assertEqual(
            str(list(query_set)),
            "[<ModelWithVisibilityField: test 2 - is public True>]",
        )

class ModeratedObjectManagerTestCase(TestCase):
    fixtures = ['test_users.json']

    def setUp(self):
        self.moderation = setup_moderation([UserProfile, ModelWithSlugField2])

        self.user = User.objects.get(username='admin')

    def tearDown(self):
        teardown_moderation()

    def test_objects_with_same_object_id_django(self):
        model1 = ModelWithSlugField2(slug='test')
        model1.save()

        model2 = UserProfile(
            description='Profile for new user',
            url='http://www.yahoo.com',
            user=User.objects.get(username='user1'),
        )

        model2.save()

        self.assertRaises(
            MultipleObjectsReturned, ModeratedObject.objects.get, object_pk=model2.pk
        )

        moderated_obj1 = ModeratedObject.objects.get_for_instance(model1)
        moderated_obj2 = ModeratedObject.objects.get_for_instance(model2)

        self.assertEqual(
            repr(moderated_obj2), '<ModeratedObject: user1 - http://www.yahoo.com>'
        )
        if VERSION < (2, 0):
            self.assertEqual(
                repr(moderated_obj1), "<ModeratedObject: ModelWithSlugField2 object>"
            )
        else:
            self.assertEqual(
                repr(moderated_obj1),
                "<ModeratedObject: ModelWithSlugField2 object (1)>",
            )

    def test_instance_with_many_moderations(self):
        # Register a moderator that keeps the history
        # un-register UserProfile
        from moderation import moderation

        class KeepHistoryModerator(GenericModerator):
            keep_history = True

        moderation.unregister(UserProfile)
        moderation.register(UserProfile, KeepHistoryModerator)

        profile = UserProfile(
            description='Profile for new user',
            url='http://www.yahoo.com',
            user=self.user,
        )
        profile.save()

        # There should only be one ModeratedObject
        self.assertEqual(1, ModeratedObject.objects.count())

        profile.url = 'http://www.google.com'
        profile.save()

        # Should now be two
        self.assertEqual(2, ModeratedObject.objects.count())

        # Getting for instance should return the one with the google url
        moderated_object = ModeratedObject.objects.get_for_instance(profile)
        self.assertEqual(profile.url, moderated_object.changed_object.url)

        # Get the first object, and does it have the yahoo address
        moderated_object_pk1 = ModeratedObject.objects.get(pk=1)
        self.assertEqual(
            'http://www.yahoo.com', moderated_object_pk1.changed_object.url
        )


import json
import uuid

from nifiapi.properties import PropertyDescriptor
from nifiapi.properties import StandardValidators
from nifiapi.properties import ExpressionLanguageScope
from nifiapi.flowfiletransform import FlowFileTransform, FlowFileTransformResult

import pinecone

class PutPineconeVectorEmbedding(FlowFileTransform):
    class Java:
        implements = ['org.apache.nifi.python.processor.FlowFileTransform']
    class ProcessorDetails:
        dependencies = ['langchain', 'openai', 'pinecone-client','tiktoken']
        version = '0.0.1-SNAPSHOT'
        description = 'Upserts vector text embeddings into Pinecone with the configured index. Expected format is a list of json elements with text, embeddign and metadata'
        tags = [ 'Pinecone', 'AI, ''OpenAI',  'Vector Database', 'Embeddings' ]

    def __init__(self, **kwargs):
        # Build Property Descriptors

        self.pinecone_api_key = PropertyDescriptor(
            name="Pinecone API Key",
            description="The API key to connect to the Pinecone",
            required = True,
            sensitive = True
        )
        self.pinecone_environment_name = PropertyDescriptor(
            name="Pinecone Env Name",
            description="The environment that the Pinecone index is located.",
            required = True,
            sensitive = True
        )

        self.pinecone_index_name = PropertyDescriptor(
            name="Index Name",
            description="The Pinecone index to store the embeddings.",
            required = True,
        )

        self.pinecone_namespace = PropertyDescriptor(
            name="Namepace",
            description="The  namespace within the index to store the vector embeddings.",
            required = True,
        )

        self.batch_size = PropertyDescriptor(
            name="Batch Size",
            default_value = "100",
            description="The number of text docs to batch up before upserting into Pinecone",
            required=True,
        )

        self.descriptors = [self.pinecone_api_key, self.pinecone_environment_name, self.pinecone_index_name, self.pinecone_namespace, self.batch_size]
        # self.descriptors = [self.vector_embedding_model_service]

    def onScheduled(self, context):
        self.logger.info("Initializing Pinecone Index")

        # INitialize Pinecone and get the indecx we will be upserting into. Assume the index already exists
        pinecone_api_key =  context.getProperty(self.pinecone_api_key.name).getValue()
        pinecone_environment = context.getProperty(self.pinecone_environment_name.name).getValue()
        pinecone_index = context.getProperty(self.pinecone_index_name.name).getValue()

        pinecone.init(
            api_key=pinecone_api_key,
            environment=pinecone_environment
        )
        self.pinecone_index = pinecone.Index(index_name=pinecone_index)

    def transform(self, context, flowFile):
        self.logger.info("Inside transform of PutPineconeVectorEmbedding..")

        # Convert the json embedding string into list of text embedding documents that contain text, embedding and metadata for each text chunk
        chunked_docs_string = flowFile.getContentsAsBytes().decode('utf-8')
        chunk_docs_json_list_deserialized = json.loads(chunked_docs_string)

        # Store the text, embedding and metadata in seperate lists which we will use to batch up and insert into pinecone
        texts = []
        embeddings = []
        metadatas = []
        for doc_dict in chunk_docs_json_list_deserialized:
            texts.append(doc_dict["text"])
            embeddings.append(doc_dict['embedding'])
            metadatas.append(doc_dict['metadata'])

        batch_size = context.getProperty(self.batch_size.name).asInteger()
        namespace = context.getProperty(self.pinecone_namespace.name).getValue()

        # Create the List of tuples to insert into pinecone where tuple is string Id, tuple 2 is list of floats, and tuple 3 is metadata dictionary
        vector_ids = []
        for i in range(0, len(texts), batch_size):
            # set end position of batch
            i_end = min(i + batch_size, len(texts))
            # get batch of texts and ids
            lines_batch = texts[i:i_end]
            # create ids
            ids_batch = [str(uuid.uuid4()) for n in range(i, i_end)]
            vector_ids.extend(ids_batch)
            # get batch of embeddings
            embeddings_batch = embeddings[i:i_end]
            # prep metadata and upsert batch
            metadata_batch = metadatas[i:i_end]

            for j, line in enumerate(lines_batch):
                metadata_batch[j]["text"] = line
            to_upsert = zip(ids_batch, embeddings_batch, metadata_batch)

            # upsert to Pinecone
            self.pinecone_index.upsert(vectors=list(to_upsert), namespace=namespace)

        # Return a list of Ids for each embedding inserted
        vector_ids_json_string = json.dumps(vector_ids)
        self.logger.info(str(len(vector_ids)) + " vectors were inserted wtih the following ids: " + vector_ids_json_string)

        return FlowFileTransformResult(relationship="success", contents=vector_ids_json_string)

    def getPropertyDescriptors(self):
        return self.descriptors


from __future__ import print_function
import numpy as np
import os
import logging
import sys
import utm
import collections
import datetime
import math
import xml.etree.ElementTree as ET
import traceback
from ast import literal_eval
from PyQt5 import QtGui,QtCore,uic

import camproject

from . import gpx
from . import image
from . import PATHS
from . import upload
from . import transform

np.set_printoptions(suppress=True)

logger = logging.getLogger(__name__)

epsilon=1e-6

def par(meta,treepath=[],default=None):
    n = len(treepath)
    try:
        chain = "meta"
        for i in treepath:
            chain += ".child('"+i+"')"
        chain += ".value()"
        #print (chain)
        return eval(chain)
    except:
        #logger.error("par failed",exc_info=True)
        return default

class PoiModel(QtCore.QObject):
    sigPois =  QtCore.pyqtSignal(list)
    changed = QtCore.pyqtSignal()

    def __init__(self):
        super().__init__()
        self.Cam = camproject.Camera()
        self.CamR = camproject.Camera()
        self.Ext = camproject.Extrinsics()
        self.ExtR = camproject.Extrinsics()
        self.dem = None
        self.boresight_roll, self.boresight_pitch, self.boresight_yaw,self.boresight_order = 0,0,0,"ZYX"
        self.im_width,self.im_height,self.im_fx,self.im_cx,self.im_cy = 0,0,1000,0,0

    def onImage(self,point):
        pt = point.ravel()
        if  0 < pt[0] < self.im_width:
            if 0 < pt[1] < self.im_height:
                return True
        else:
            return False

    def setAttitude(self,mat=None):
        if mat== None:
            self.Cam.attitudeMat(self.Ext.transform())
        else:
            self.Cam.attitudeMat(mat)

    def setPose(self,imgheader):
        #logger.warning("SET POSE " + imgheader["file"]["name"])
        self.imgheader = imgheader
        try:
            uavyaw = imgheader["uav"].get("yaw",0)
            uavX = round(imgheader["gps"].get("UTM_X",0),3)
            uavY = round(imgheader["gps"].get("UTM_Y",0),3)
            uavZ = imgheader["gps"].get("rel_altitude",0)
            uavorder = imgheader["uav"].get("euler_order","ZYX")
            roll = imgheader["camera"].get("roll",0)

            pitch= imgheader["camera"].get("pitch",0)

            yaw = imgheader["camera"].get("yaw",0)
            gimbalorder = imgheader["camera"].get("euler_order","ZXY")
            #logger.info(self.imgheader)
            self.UTM_Y,self.UTM_X,self.ZoneNumber,self.ZoneLetter = utm.from_latlon(self.imgheader["gps"]["latitude"],self.imgheader["gps"]["longitude"])

            self.Ext.setPose(X=uavX,Y=uavY,Z=uavZ,order=uavorder)
            self.Ext.setGimbal(roll=roll,pitch=pitch,yaw=yaw,order=gimbalorder)
            #self.Ext.setUAVBoresight(dx=0.2)
        except:
            logger.warning("pois load data failed",exc_info=True)
        self.setAttitude()
       # print(self.Ext.getParams())
       # print(self.ExtR.getParams()["pose"])

    def loadMeta(self,meta):
        self.im_width = par(meta, ["general","images","width"],0) #meta.child("general").child("images").child("width").value() #["general"]["images"]["width"]
        self.im_height = par(meta, ["general","images","height"],0) #meta.child("general").child("images").child("height").value()#["general"]["images"]["height"]
        self.im_cx = par(meta, ["calibration","geometric","cx"],2) #meta.child("calibration").child("geometric").child("cx").value()#["calibration"]["geometric"]["cx"]
        self.im_cy = par(meta, ["calibration","geometric","cy"],2) #meta.child("calibration").child("geometric").child("cy").value()#["calibration"]["geometric"]["cy"]
        self.im_fx = par(meta, ["calibration","geometric","fx"],999) #meta.child("calibration").child("geometric").child("fx").value()#["calibration"]["geometric"]["fx"]

        ###########################
        # there was a fixed Value in camera2 !!!!
        ###########################

        #self.im_cx = 320
        #self.im_cy = 256
        #self.im_fx = 1115

        self.boresight_pitch = par(meta, ["calibration","boresight","cam_pitch"],0) #meta.child("calibration").child("boresight").child("cam_pitch_offset").value()#["calibration"]["boresight"]["cam_pitch_offset"]
        self.boresight_roll = par(meta, ["calibration","boresight","cam_roll"],0) #meta.child("calibration").child("boresight").child("cam_roll_offset").value()#["calibration"]["boresight"]["cam_roll_offset"]
        self.boresight_yaw = par(meta, ["calibration","boresight","cam_yaw"],0) #meta.child("calibration").child("boresight").child("cam_yaw_offset").value()#["calibration"]["boresight"]["cam_yaw_offset"]
        self.boresight_order = par(meta, ["calibration","boresight","cam_euler_order"],"ZYX") #meta.child("calibration").child("boresight").child("cam_yaw_offset").value()#["calibration"]["boresight"]["cam_yaw_offset"]
        self.pois = []
        try:
            for L in meta.child("pois").children():
                for poi in L.child("data").children():
                    for view in poi.children():
                        val = literal_eval(view.value())
                       # print ("VAL",val,view.opts)
                        try:
                            self.pois.append({"name":poi.name(),"x":val[0],"y":val[1],"layer":L.name(),
                                "filename":view.name(),
                                "lat":float(view.opts.get("latitude",0)),
                                "lon":float(view.opts.get("longitude",0)),
                                "ele":float(view.opts.get("elevation",0)),
                                "uav_lat":float(view.opts["uav_lat"]),
                                "uav_lon":float(view.opts["uav_lon"]),
                                "uav_ele":float(view.opts["uav_ele"]),
                                #"uav_yaw":float(view.opts["uav_yaw"]),
                                "cam_yaw":float(view.opts["cam_yaw"]),
                                "cam_pitch":float(view.opts["cam_pitch"]),
                                "cam_roll":float(view.opts["cam_roll"]),
                                "cam_euler_order":view.opts["cam_euler_order"],
                                "boresight_pitch":float(view.opts["boresight_pitch"]),
                                "borsight_roll":float(view.opts["boresight_roll"]),
                                "boresight_yaw":float(view.opts["boresight_yaw"]),
                                "boresight_euler_order":view.opts["boresight_euler_order"],
                                "found_time":view.opts["found_time"]})
                        except:
                            self.pois.append({"name":poi.name(),
                                "x":val[0],"y":val[1],
                                "layer":L.name(),
                                "filename":view.name()})
        except:
            logger.error("load Calib failed",exc_info=True)

        self.Ext.setCameraBoresight(droll=self.boresight_roll, dpitch= self.boresight_pitch, dyaw=self.boresight_yaw, order=self.boresight_order)
        self.setIntrinsics(self.im_width,self.im_height,self.im_fx,self.im_cx,self.im_cy)
        #print("loadMeta",self.im_width,self.im_height,self.im_fx,self.im_cx,self.im_cy)
        self.changed.emit()

    def getPois(self,filename): # get pois in this image
        Pois = []
        Reprojected = []
        #print("========",filename,"============")
        for i in self.pois:
            if i["filename"]==filename:
                #print("from file")
                Pois.append(i)
            else:
                try:
                    attitude = self.prepareReproject(i)
                    #print("reprojected ",i,self.im_fx)
                    j = self.backreproject(i, attitude)
                 #   print(j)
                    if not j.all() == None:
                        rep = i.copy()
                        rep["x"]= j.ravel()[0]
                        rep["y"]= j.ravel()[1]
                        rep["reprojected"]=True
                        Reprojected.append(rep)
                except:
                    logger.warning("getPois Reproject failed",exc_info=True)
        Pois.extend(Reprojected)
       # print("REPROJECTED:")
       # print (Reprojected)
        self.sigPois.emit(Pois)

    def prepareReproject(self,poi):
    #    print("ExtR:")
        UTM_Y,UTM_X,ZoneNumber,ZoneLetter = utm.from_latlon(poi["uav_lat"],poi["uav_lon"])
        #print("UTM",round(UTM_X,3),round(UTM_Y,3),poi["uav_ele"])
        self.ExtR.setPose(round(UTM_X,3),round(UTM_Y,3),poi["uav_ele"]) #yaw=0.54,X=UTM_X,Y=UTM_Y,Z=48.57)
        #self.ExtR.setUAVBoresight(dx=poi["cam_dx"],dy=poi["cam_dy"],dz=poi["cam_dz"])#dx=0.20)
        #print("T_UAV", self.ExtR.T_uav)
        self.ExtR.setCameraBoresight(dyaw= self.boresight_yaw,
            dpitch= self.boresight_pitch,
            droll= self.boresight_roll,
            order= self.boresight_order) #dpitch=-1.5,dyaw=1.0
        self.ExtR.setGimbal(roll=poi["cam_roll"],pitch= poi["cam_pitch"],yaw=poi["cam_yaw"],order=poi["cam_euler_order"])#pitch=-90,roll=-0.23,yaw=0,order="ZXY")
        #self.ExtR.setGimbal(pitch=-90,roll=0,yaw=0,order="ZXY")
        #print ("RBoresight:",self.ExtR.R_cam_boresight)
        #print ("TBoresight:",self.ExtR.T_cam_boresight)
        #print("RGimbal",self.ExtR.R_gimbal)
        #print("uavboresight",self.ExtR.R_uav_boresight)
        #print("uavboresight",self.ExtR.T_uav_boresight)
        #print("uav",self.ExtR.T_uav)#T_uav

       # print("cosyUAV2Cam",self.ExtR.cosyUAVToCamera,
       #  "rcambire",self.ExtR.R_cam_boresight,
       #  "tcambore",self.ExtR.T_cam_boresight,
       #  "rgim",self.ExtR.R_gimbal,
       #  "r_uav_bore",self.ExtR.R_uav_boresight,
       #  "Tuavbore",self.ExtR.T_uav_boresight,
       #  "R_uav",self.ExtR.R_uav,
       #  "cosyW2UAV",self.ExtR.cosyWorldToUAV.T,
       #  "T_uav",self.ExtR.T_uav)

        return self.ExtR.transform()

    def setIntrinsics(self,width,height,fx,cx,cy,ar=1.0,skew=0.0):
        self.Cam.intrinsics(width,height,fx,cx,cy,ar,skew)
        self.CamR.intrinsics(width,height,fx,cx,cy,ar,skew)

    def setDEM(self,dem):
        self.dem = dem

    def raymarching(self,pos,vec,ele=0):
        if not self.dem:
            poi = self.ray_intersect_plane(pos[0:3],vec[0:3],np.array([0,0,1,ele]),True)
        return poi #3D Vector

    def ray_intersect_plane(self,raypos, raydir, plane, front_only=True):
        p = plane[:3] * plane[3]
        n = plane[:3]

        rd_n = np.dot(raydir.ravel()[:3], n)
        if abs(rd_n) < epsilon:
            logger.warning("length of raydir is 0")
            return np.array([[None],[None],[None]])
        if front_only == True:
            if rd_n >= epsilon:
                logger.warning("raydir points away")
                return np.array([[None],[None],[None]])
        pd = np.dot(p, n)
        p0_n = np.dot(raypos.ravel(), n)
        t = (pd - p0_n) / rd_n
        return raypos.ravel() + (raydir.ravel()[:3] * t)

    def reproject_poi(self,x,y):
        pos = self.reproject(np.array([x,y]))
        lat, lon = utm.to_latlon(pos[1],pos[0],self.ZoneNumber,self.ZoneLetter)
        found_time = datetime.datetime.now().isoformat()
        return {"name":self.imgheader["file"]["name"],"value":(x,y),"type":"str", "paramtyp":"view",
            "latitude":lat, "longitude":lon,"elevation":pos[2],
            "uav_lat":float(self.imgheader["gps"]["latitude"]),
            "uav_lon":float(self.imgheader["gps"]["longitude"]),
            "uav_ele":float(self.imgheader["gps"]["rel_altitude"]),
            "cam_yaw":float(self.imgheader["camera"]["yaw"]),
            "cam_pitch":float(self.imgheader["camera"]["pitch"]),
            "cam_roll":float(self.imgheader["camera"]["roll"]),
            "cam_euler_order":self.imgheader["camera"]["euler_order"],
            "boresight_pitch":self.boresight_pitch,
            "boresight_roll":self.boresight_roll,
            "boresight_yaw":self.boresight_yaw,
            "boresight_euler_order":self.boresight_order,
            "found_time":found_time,
            "readonly":False,"removable":True,"renamable":False,"enabled":False}

    def reproject(self,x):
        repro = self.Cam.reproject(x)
        campos = self.Cam.position()
        rd = repro.T - campos.ravel()
        pos = self.raymarching(campos, rd.reshape(4,1))
        return pos

   # def frontofcamera(self,pt,camplane):
   #     print ("frontofcam",pt.ravel(),camplane.ravel())
   #     rd_n = np.dot(pt.ravel()[:3], camplane.ravel()[:3])
   #     print ("infrontofcam", rd_n)
   #     if rd_n>0: return True
   #     else: return False

    def backreproject(self,x,attitudeMat):
       # print(x,attitudeMat)
        self.CamR.attitudeMat(attitudeMat)
        pixel = np.array([int(float(x["x"])),int(float(x["y"]))]) #.reshape(1,2)
        repro = self.CamR.reproject(pixel)
        campos = self.CamR.position()
        rd = repro.T - campos.ravel()
        pos = self.raymarching(campos,rd).reshape(1,3)
      #  if not self.frontofcamera(pos,self.Cam.reproject(np.array([[320,256]]))):
      #      print("BACKSIDE!")
      #      return np.array([[None,None]])

        x_br = self.Cam.project(pos)
       # print("BACK",x_br)
        if self.onImage(x_br.ravel()):
            return x_br
        else:
            return np.array([[None,None]])

if __name__ == "__main__":
    test = 4
    np.set_printoptions(suppress=True)
    import utm

    poi = PoiModel()
    poi.setIntrinsics(640,512,1115,320,256)#640,512,1500,320,256)

    if test ==1:
        print("\nExample1")
        poi.Ext.setPose(yaw=0,pitch=0,X=20,Y=1,Z=100)
        poi.Ext.setGimbal(pitch=-80)
        poi.setAttitude()
        Xc = np.array([[320],[256]]).ravel()
    elif test==2:
        print("\nExample2")
        UTM_Y,UTM_X,ZoneNumber,ZoneLetter = utm.from_latlon(48.1405597,11.016507)
        poi.Ext.setPose(X=round(UTM_X,3),Y=round(UTM_Y,3),Z=48.57)
        poi.Ext.setGimbal(pitch=-90)
        poi.setAttitude()
        Xc = np.array([[513],[267]]).ravel()
    elif test==3:
#        backreproject(x) {'name': 'xxx_3', 'x': '513.0', 'y': '267.0', 'layer': '0', 'filename': '04051446_0076.ARA', 'lat': 48.
#140543, 'lon': 11.016634, 'ele': 0.0, 'uav_lat': 48.1405597, 'uav_lon': 11.016507, 'uav_ele': 48.57, 'cam_yaw': 0.54, 'c
#am_pitch': -90.0, 'cam_roll': 0.0, 'cam_dx': 0.2, 'cam_dy': 0.0, 'cam_dz': 0.0, 'euler_dir': 'ZXY', 'pitch_offset': 1.5,
# 'roll_offset': 0.0, 'yaw_offset': 1.0, 'found_time': '2017-04-05T14:43:52'}
#[[      -0.01884844        0.99982235        0.          -549358.97277609]
# [       0.99982235        0.01884844       -0.         -5345193.61324658]
# [       0.                0.                1.              -48.57      ]
# [       0.                0.                0.                1.        ]]
        print("\nExample3")
        UTM_Y,UTM_X,ZoneNumber,ZoneLetter = utm.from_latlon(48.1405597,11.016507)
        poi.Ext.setPose(yaw=0.54,X=round(UTM_X,3),Y=round(UTM_Y,3),Z=48.57)
        poi.Ext.setGimbal(pitch=-90)
        poi.setAttitude()
        Xc = np.array([[513],[267]]).ravel()

    if test <4:
        Xrepro = poi.reproject(Xc)
        print("S",poi.Cam.S)
        print("Xc",Xc)
        print("rel",Xrepro-poi.Cam.position()[0:3].ravel())
        print("Xrepro", Xrepro)

    if test==4:
        print("\nExample4, genauso ist es in camera2 implementiert!") #'filename': '04051446_0076.ARA'
        UTM_Y,UTM_X,ZoneNumber,ZoneLetter = utm.from_latlon(48.1405597,11.016507)
        pUTM_Y,pUTM_X,pZoneNumber,pZoneLetter = utm.from_latlon(48.140543,11.016634)
        #poi.Ext.setPose(roll=-0.23,yaw=-0.54,X=UTM_X,Y=UTM_Y,Z=48.57)
        #poi.Ext.setCameraBoresight(dpitch=-1.5,dyaw=1.0, dx=0.20)
        #poi.Ext.setGimbal(pitch=-90)
        poi.Ext.setPose(yaw=0.54,X=round(UTM_X,3),Y=round(UTM_Y,3),Z=48.57)
        poi.Ext.setUAVBoresight(dx=0.20)
        poi.Ext.setCameraBoresight(dpitch=-1.5,dyaw=1.0)
        poi.Ext.setGimbal(pitch=-90,roll=-0.23,yaw=0,order="ZXY")
        poi.setAttitude()
        Xc = np.array([[513],[267]]).ravel()
        print("Xc",Xc)
        Xrepro = poi.reproject(Xc)
        print("Xrepro", Xrepro)

        print("UTM",UTM_Y,UTM_X,ZoneNumber)
        print("pUTM",round(pUTM_X,3),round(pUTM_Y,3),pZoneNumber)
        print("rel from UTM y:",pUTM_Y-UTM_Y,"x:",pUTM_X-UTM_X)
        print("S",poi.Cam.S)
        print("rel",Xrepro-poi.Cam.position()[0:3].ravel())
        xrp = Xrepro.reshape(1,3)
        #mat4d = np.vstack((mat3d,[0,0,0]))
        px = poi.Cam.project(np.hstack((xrp,[[1]])))
        print("px",px)
        pl = poi.Cam.project(np.array([[pUTM_X],[pUTM_Y],[0],[1]]))
        print("frompoilist",pl)
    # import os
    # import sys
    # import logging
    # import numpy as np
    # from PyQt5 import QtCore,QtGui,uic, QtWidgets,QtTest
    # from PyQt5.QtWidgets import QApplication,QWidget,QMainWindow, QLineEdit,QToolButton,QAction,QMessageBox,QPushButton,QVBoxLayout,QProgressDialog
    # import pyqtgraph as pg
    # from pyqtgraph.parametertree import Parameter, ParameterTree, ParameterItem, registerParameterType
    # from . import image
    # from . import flightjson

    # app = QApplication(sys.argv)
    # path = "D:/WILDRETTER-DATEN/2017_DLR/2017-04-05_14-56_Hausen"

    # poi = PoiModel()

    # win = flightjson.FlightWidget()
    # root = QLineEdit()
    # root.insert(path)
    # ldbut = QPushButton()
    # ldbut.setText("Load")
    # win.toolBar.addWidget(root)
    # win.toolBar.addWidget(ldbut)
    # fm = flightjson.Flight()
    # fm.load(path)
    # win.setMeta(fm)

    # fm.loadfinished.connect(lambda: poi.loadMeta(fm.p))
    # img = image.Image.factory(os.path.join(path,"04051456_0229.ARA"))

    # #poi.setPose(img.header)
    # print("POSE,yaw:",img.header["uav"]["yaw"])
    # print("UTM:",img.header["gps"].get("UTM_X",0),img.header["gps"].get("UTM_Y",0),img.header["gps"].get("rel_altitude",0))
    # print("Gimbal:",img.header["camera"]["roll"],img.header["camera"]["pitch"],img.header["camera"]["yaw"])

    # def prepare():
        # #c = poi.Cam.project(np.array([0,0,0,1]))
        # c = np.array([320,256])#[264.0,396.0])
        # #poi.setPose(img.header)
        # poi.Ext.setPose(yaw=0,X=50000,Y=6400,Z=50)
        # poi.Ext.setGimbal(roll=0,pitch=-90)
        # poi.setAttitude()
        # #print("POSalt",poi.Cam.S[0:3,[3]].ravel())
        # #print("POSneu",poi.Cam.position())

        # #print (c)
        # rp = poi.reproject(c)
        # print("RP",rp)

    # vbox = QVBoxLayout()
    # win.horizontalLayout.addLayout(vbox)
    # but = QPushButton()
    # but.setText("project")
    # vbox.addWidget(but)
    # but.clicked.connect(prepare)
    # win.show()

    # win.escAction = QAction('escape', win)
    # win.escAction.setShortcut(QtGui.QKeySequence(QtCore.Qt.Key_Escape))
    # win.addAction(win.escAction)
    # win.escAction.triggered.connect(win.close)
    # sys.exit(app.exec_())


import json
import os
import sys
import argparse
import glob
from collections import defaultdict
import shutil
import pickle
import pandas as pd
import matplotlib.pyplot as plt
import utils
import numpy as np
import matplotlib.cm as cm
import traceback
import seaborn as sns
import scipy as sp
import math
import multiprocessing as mp   # multithread processing
from functools import partial  # partial evaluation of a function
from matplotlib.cm import ScalarMappable
# plt.rcParams['text.usetex'] = True
import matplotlib as mpl
import pdb

class ForkedPdb(pdb.Pdb):
    """A Pdb subclass that may be used
    from a forked multiprocessing child

    """
    def interaction(self, *args, **kwargs):
        _stdin = sys.stdin
        try:
            sys.stdin = open('/dev/stdin')
            pdb.Pdb.interaction(self, *args, **kwargs)
        finally:
            sys.stdin = _stdin

# mpl.use("pgf")
mpl.rcParams.update(
    {
        # Adjust to your LaTex-Engine
        "pgf.texsystem": "pdflatex",
        "font.family": "serif",
        "text.usetex": True,
        "pgf.rcfonts": False,
        "axes.unicode_minus": False,
        "lines.linewidth":   5,
        "lines.markersize": 14,
        "savefig.bbox":"tight",
    }
)
# optional font setting
font = {'family' : 'serif',
        'weight' : 'normal',
        'size'   : 24}
mpl.rc('font', **font)
mpl.rcParams['figure.figsize'] = (6, 6)  # Use the values from \printsizes

np.seterr(divide="ignore")  # ignore the log of -1

def set_default(obj):
    if isinstance(obj, set):
        return list(obj)
    raise TypeError

def main(args):
    if args.diff:
        diff(args)
    elif args.compute:
        compute2(args)
    elif args.compare is not None or args.plot or args.contour:
        plot(args)
    else:
        export(args)

def diff(args):

    lst_file = []
    exclude = ["name", "dataset_fname", "config", "time"]
    for dr in args.dirs:

        # lst_file.extend(glob.glob(os.path.join(dr, "**", "config.json"), recursive=True))  # all config files)
        lst_file = glob.glob(os.path.join(dr, "**", "config.json"), recursive=True)  # all config files)

        if len(lst_file) < 2:  # do not process if less than 2 different configurations
            continue

        root = os.path.commonpath(lst_file)
        diff_dict = cmpt_dict(lst_file, exclude)# for k,v in merge_dct.items():
            # if len(v) == 1:
                # merge_dct.pop(k)

        with open(os.path.join(root, "filter.json"), 'w') as fp:
            json.dump(diff_dict, fp, indent=0, sort_keys=True, default=set_default)

    return

def cmpt_dict(lst_file, exclude=["name", "dataset_fname", "config"]):

    merge_dct = defaultdict(set)
    for f in lst_file:

        with open(f) as fconf:
            data = json.load(fconf)
            if not merge_dct:  # first time
                for k,v in data.items():
                    if isinstance(v, (list, dict)):
                        continue
                    merge_dct[k] |= set([v])   # convert the values to set
            else:
                for k,v in data.items():
                    if isinstance(v, (list, dict)):
                        continue
                    if k not in merge_dct.keys():
                        print("Warning! ", k, " not in the keys")
                    merge_dct[k] |= set([v])

    diff_dct = {k :v for k,v in merge_dct.items() if len(v) > 1 and not k in exclude}
    return diff_dct

def export(args):

    with open(args.filter) as fconf:
        conf = json.load(fconf)
    dname, name = os.path.split(os.path.splitext(args.filter)[0])

    lst_file = (glob.glob(os.path.join(dname, "**", "config.json"), recursive=True))  # all config files)

    outroot = os.path.join(os.path.dirname(args.filter), "merge", "export")
    pnames = ["loss", "ranks"]
    outdir = os.path.join(outroot, name)
    if args.reset:
        shutil.rmtree(outdir, ignore_errors=True)
    for f in lst_file:

        with open(f) as fconf:
            data = json.load(fconf)
        if not all([data[k] in conf[k] for k in conf.keys()]):
            continue
        root = os.path.dirname(f)
        namestr = to_string(sorted(conf.keys()), data)
        for pname in pnames:
            try:
                src = os.path.join(root, "plot", f"{pname}.pdf")
                # outdir = os.path.join(outroot, name, pname)
                dst = os.path.join(outdir, pname, f"{namestr}.pdf")
                os.makedirs(os.path.dirname(dst), exist_ok=True)
                shutil.copyfile(src, dst)
            except FileNotFoundError:
                traceback.print_exc()

def compute(args):

    EPS = 4
    dname, name = os.path.split(os.path.splitext(args.filter)[0])
    lst_file = (glob.glob(os.path.join(dname, "**", "config.json"), recursive=True))  # all config files)

    with open(args.filter) as fconf:
        conf = json.load(fconf)

    outdir = os.path.join(dname, "export", "compute")
    os.makedirs(outdir, exist_ok=True)

    for fname in lst_file:

        dfname = os.path.join(os.path.dirname(fname), "dfs.pkl")
        try:
            with open(dfname, "rb") as _f:
                dfs = pickle.load(_f)
            with open(fname) as fconf:
                fargs = json.load(fconf)
        except:
            print("can't open the dataframe at ", fname)
            continue

        key = tuple([fargs[k] for k in conf.keys()])
        outname = df_to_string(key, conf.keys())
        # outfold = os.path.join(outdir, outname)
        os.makedirs(outdir, exist_ok=True)
        df = dfs["quant"].dropna(axis=1, how='all')
        lr = fargs["learning_rate"]
        if not 'time' in df.columns:
            df["time"] = df.index * lr   # assume a constant learning rate

        diff = df["diff loss to OPT"]
        logdiff = np.log10(diff)
        wass = df["wass. dist to OPT"]  # criterion for convergence
        if not (wass < EPS).any():
            print("theshold not crossed in ", fname)
            continue  # leave the slope at NaN, not converged
        # df.loc[:, "slope"] = float('nan')
        logwass = np.log10(wass)  # the log10 of wass
        s = int(0.01 / lr + 0.5)  # sample in one per centisecond
        logwassewm = logwass.ewm(span=s).mean()[wass.index[::s]]  # subsample the signal after smoothing it
        dlogwass = (logwassewm.shift(-1) - logwassewm.shift(1)) /2

        dabs = dlogwass.abs()

        d2abs = (dabs.shift(-1) - dabs.shift(1)) / 2
        d2abs0 = d2abs.copy()
        d2abs0[d2abs.abs() < d2abs.abs().max()/10] = 0

        posabs = d2abs0 > 0
        negabs = d2abs0 < 0
        nullabs = d2abs0 == 0

        begidx = np.where(nullabs & posabs.shift(-1))[0]
        endidx = np.where(negabs & nullabs.shift(-1))[0]

        begpts = d2abs.index[begidx]
        endpts = d2abs.index[endidx]

        th = dabs.max() * 2/3
        mask = dabs > th
        components = np.where(mask)[0]
        brkpts = np.where(np.diff(components) != 1)[0]  # the breakpoints at which the connected components end
        aas = [components[0]] + components[brkpts + 1].tolist()
        bbs = components[brkpts].tolist() + [components[-1]]  # add the last point
        # idx = components[0]  # first index where the threshold is reached
        # intervals = []  # list of possible intervals for the linear regression

        # ptidx = components[brkpts]  # the breakpoints in the mask frame
        # frameidx = dabs.index[ptidx]  # the breakpoints in the original frame
        aaframe = dabs.index[aas]
        bbframe = dabs.index[bbs]
        aaarr = np.array(aaframe)  # numpy conversion
        bbarr = np.array(bbframe)
        begarr = np.array(begpts)
        endarr = np.array(endpts)
        # test all the different lengths
        testbeg = (begarr.reshape(1, -1, 1) - aaarr.reshape(-1, 1, 1)) >= 0
        testend = (bbarr.reshape(-1, 1, 1) - endarr.reshape(1, 1, -1)) >= 0
        testBE = ((endarr.reshape(1, 1, -1) - begarr.reshape(1, -1, 1) > 0)  |
                  (bbarr.reshape(-1, 1, 1) - begarr.reshape(1, -1, 1) > 0) |
                  (endarr.reshape(1, 1, -1) - aaarr.reshape(-1, 1, 1) > 0))

        # test

        allowed = np.where((testbeg  | testend ) & (testBE))  # requires the correct interval orientation

        if len(allowed[0]) == 0:  # we did not find
            print("can't find a slope in ", fname)
            continue

        maxidx_pts = allowed[0].max()  # the index of the last correct interval in allowed frame
        maxidx_beg = allowed[1].max()
        maxidx_end = allowed[2].max()

        # inteval_idx = (allowed[1][maxidx], allowed[2][maxidx])

        interval = max(aaarr[maxidx_pts], begarr[maxidx_beg]), bbarr[maxidx_pts]

        if interval[0] >= interval[1]:
            print("Wrong interval value in ", fname)
            continue

        x = logdiff.index[interval[0]:interval[1]]
        y = logdiff[interval[0]:interval[1]]
        mask = np.isfinite(y)  # only consider finite data
        regress = sp.stats.linregress(x[mask]*lr, y[mask]) # slope in s^-1

        plt.figure()
        ax = logdiff.plot()
        ax.plot(x, regress.intercept + regress.slope * x, 'r')
        outstat = os.path.join(outdir, "regress")
        os.makedirs(outstat, exist_ok=True)
        plt.savefig(os.path.join(outstat, f"{outname}.pdf"))
        plt.close("all")

        computed = {'regress': regress, 'interval': interval}
        savefname = os.path.join(os.path.dirname(fname), "regress.pkl")
        with open(savefname, 'wb') as fp:
            pickle.dump(computed, fp)

def check_params(conf, fname):
    """Checks if a json file has allowed parameter compared to a config file"""

    with open(fname, 'r') as fp:

        fconf = json.load(fp)

    return all([fconf[k] in conf[k] for k in conf.keys()])

def compute2(args):

    EPS = 1e-4
    if args.filter is not None:
        dname, name = os.path.split(os.path.splitext(args.filter)[0])
        lst_file = (glob.glob(os.path.join(dname, "**", "config.json"), recursive=True))  # all config files)
    # lst_regress = glob.glob(os.path.join(dname, "**", "regress.pkl"), recursive=True)

        with open(args.filter) as fconf:
            conf = json.load(fconf)

        lst_file = [l for l in lst_file if check_params(conf, l)]

    else:
        conf = None
        lst_file = []
        lst_lst = [glob.glob(os.path.join(dir_, "**", "config.json"), recursive=True) for dir_ in args.dirs]
        for sublst in lst_lst:
            lst_file.extend(sublst)

    if args.filter is not None:
        dname, name = os.path.split(os.path.splitext(args.filter)[0])
        outdir = os.path.join(dname, "merge", name, "compute") if name != "filter" else os.path.join(dname, "merge", "compute")
    else:
        if len(lst_file) > 1:  # more than one file to process
            dname = os.path.commonpath(lst_file)
            outdir = os.path.join(dname, "merge", "compute")
            conf = cmpt_dict(lst_file)
        else:
            dname = os.path.dirname(lst_file[0])
            outdir = os.path.join(dname, "compute")

    os.makedirs(outdir, exist_ok=True)
    failed_lst = []  # list for all the failed processings
    failed_fname = os.path.join(outdir, "failed.txt")
    partial_cpt = partial(compute_fname, conf=conf, outdir=outdir)
    # if os.path.isfile(f):
        # os.remove(FAILED)
    with mp.Pool(processes=args.nprocs) as pool:
        pool.map(partial_cpt, lst_file)

    # processes = [Process(target=compute_fname, args=(fname,conf,outdir)) for fname in lst_file]

    # for prc in processes:
        # prc.start()

    # for prc in processes:
        # prc.join()

# failed_lst.sort()
# with open(failed_fname, 'w') as _f:
    # _f.writelines("\n".join(failed_lst))

def compute_fname(fname, conf, outdir):

    dfname = os.path.join(os.path.dirname(fname), "dfs.pkl")
    try:
        with open(dfname, "rb") as _f:
            dfs = pickle.load(_f)
        with open(fname) as fconf:
            fargs = json.load(fconf)
    except:
        print("can't open the dataframe at ", fname)
        # failed_lst.append(fname)
        # continue
        return

    savefname = os.path.join(os.path.dirname(fname), "regress.pkl")
    if os.path.isfile(savefname) and not args.force:
        # continue
        return
    if conf is not None:
        key = tuple([fargs[k] for k in conf.keys()])
        outname = df_to_string(key, conf.keys())
    else:
        outname = "debug"
    # outfold = os.path.join(outdir, outname)
    # os.makedirs(outdir, exist_ok=True)
    df = dfs["quant"].dropna(axis=1, how='all')
    lr = fargs["learning_rate"]
    if not 'time' in df.columns:
        df["time"] = df.index * lr   # assume a constant learning rate

    WIN_SIZE = int(0.1  / lr + 0.5)  # window size over which we compute the linear regression in samples
    diff = df["diff loss to OPT"]
    logdiff = np.log10(diff)
    wass = df["wass. dist to OPT"]  # criterion for convergence
    # if not (wass < EPS).any():
        # print("theshold not crossed in ", fname)
        # continue  # leave the slope at NaN, not converged
    # df.loc[:, "slope"] = float('nan')
    logwass = np.log10(wass)  # the log10 of wass
    logwassfin = logwass[np.isfinite(logwass)]
    logdifffin = logdiff[np.isfinite(logdiff)]
    s = int(1e-4 / lr + 0.5)  # sample in 1 per millisecond
    logwassewm = logwassfin.ewm(span=s).mean()[logwassfin.index[::s]]  # subsample the signal after smoothing it
    logdiffewm = logdifffin.ewm(span=s).mean()[logdifffin.index[::s]]  # subsample the signal after smoothing it
    diffewm = diff.ewm(span=s).mean()[diff.index[::s]]

    mask = (logdiffewm < -2) & (logdiffewm > -3.5)  #
    # mask = (diffewm < 1e-2) & (diffewm > 1e-4 )  #
    # mask = (logdiffewm > -3.5) #

    # brkpts = np.where(np.diff(np.where(mask)[0]) != 1)[0]
    windows_idx = np.where(np.diff(mask))[0]
    if mask.iloc[0]:
        windows_idx = np.concatenate(([0], windows_idx), axis=0)
    if mask.iloc[-1]:
        windows_idx = np.concatenate((windows_idx, [len(mask)-1]), axis=0)
    window_idx = None

    if len(windows_idx) == 0 or len(windows_idx)==1:
        print("Signal not found at ", fname)
        # failed_lst.append(fname)
        # with open(FAILED, 'a') as _f:
            # _f.write(fname, '\n')
        # continue
        return

    windows_size = np.diff(windows_idx)[::2]  # every two are the lengths of the True regions
    win_max = 2*np.argmax(windows_size)
    window_idx = ( windows_idx[win_max],  windows_idx[win_max+1] )

    # interval = mask.index[window_idx[0]], mask.index[min(len(mask)-1, window_idx[1]+1)]
    interval = max(0, mask.index[window_idx[1]] - WIN_SIZE), mask.index[window_idx[1]]
    # assert all(mask.loc[interval[0]:interval[1]])

    x = logdiff.index[interval[0]:interval[1]]
    y = logdiff[interval[0]:interval[1]]
    # x = diff.index[interval[0]:interval[1]]
    # y = diff[interval[0]:interval[1]]
    mask = np.isfinite(y)
    regress = sp.stats.linregress(x[mask]*lr, y[mask]) # slope in s^-1
    # regress = sp.stats.linregress(x*lr, y) # slope in s^-1
    lograte = np.log(diff[interval[1]-1] / diff[interval[0]]) / (lr*(diff.index[interval[1]] - diff.index[interval[0]]))

    plt.figure()
    ax = logdiff.plot()
    logdiffewm.plot(ax=ax)
    ax.plot(x, regress.intercept + regress.slope*lr * x, 'r')
    outstat = os.path.join(outdir, "regress")
    os.makedirs(outstat, exist_ok=True)
    plt.savefig(os.path.join(outstat, f"{outname}.pdf"))
    plt.close("all")

    computed = {'regress': regress, 'interval': interval, 'lograte': lograte}
    with open(savefname, 'wb') as fp:
        pickle.dump(computed, fp)

def append_df(df_lst, keyval, args, xaxis, yaxis, orderkey):
    # f = fnames[key]
    key, f = keyval
    dfname = os.path.join(os.path.dirname(f), "dfs.pkl")
    try:
        with open(dfname, "rb") as _f:
            dfs = pickle.load(_f)
        with open(f, 'r') as _f:  # the configuration of the run
            fargs = json.load(_f)

    except:
        return

    # ForkedPdb().set_trace()
    if yaxis in ("slope", "lograte") or args.contour in ("slope", "r", "lograte"):
        regressfname = os.path.join(os.path.dirname(f), "regress.pkl")
        try:
            with open(regressfname, "rb") as _f:
                data = pickle.load(_f)
                regress = data["regress"]
                # lograte = data["lograte"]
        except:
            return

    df = dfs["quant"].dropna(axis=1, how='all')
    # if we want to compute additional quantities
    # compute the slope if it as converged
    if xaxis != df.index.name:  #means the xaxis is a hyperparameter
        df = df.tail(1)  # only keep the last one,
    if yaxis in "slope" or args.contour in ("slope", "r"):
        df["slope"] = regress.slope
        df["r"] = r2 = regress.rvalue**2
        df["Pred_C"] = dfs["vals"]["Prec_C"]
    # elif yaxis == "lograte" or args.contour in ("lograte"):  # lograte not
    # working
        # df["lograte"] = lograte
    if yaxis == "smin":
        try:
            vals = dfs["vals"]
            if all([k in vals.keys() for k in ["smin", "smin*", "smin**"]]):
                smin, sminstar, smin2star = vals["smin"], vals["smin*"], vals["smin**"]
            else:  # compute the value of smin based on the trainset
                ds =fargs["dataset_fname"]
                sets, _ = utils.load_dataset(fname=ds)  #
                if sets:
                    trainset = sets["train"]
                    # args.load_dataset = fname_ds
                    # STD = trainset.std
                    # Sigma0 = STD.mm(STD.t())  # covariance of the target distribution
                    dmin = min(fargs["xdim"], fargs["zdim"], fargs["width"])
                    r = min(fargs["xdim"], fargs["zdim"])
                    lambdas = trainset.Lambda
                    smin2star = math.sqrt(lambdas[dmin-1])
                    sminstar = math.sqrt(lambdas[r-1])
                    smin = math.sqrt(lambdas[-1])
                else:
                    raise IOError("Can't open the trainset for read")
            df["smin**"] = smin2star
            df["smin*"] = sminstar
            df["smin"] = smin
        except:
            traceback.print_exc()

    # index = index.union(df.index)
    # index.name = "niter"
    if args.contour and yaxis == "slope":
        tpls = [(*key, regress.slope, s) for s in df.columns]
    # elif args.contour == "slope" and yaxis == "smin":
        # tpls = [(*key, s) for s in df.columns]
    else:
        tpls = [(*key, s) for s in df.columns]
    columns = pd.MultiIndex.from_tuples(tpls,names=orderkey + ["stat"])
    df.columns = columns
    # columns = columns.union(df.columns)
    # df_merge = df_merge.reindex(index)
    df_lst.append(df)
    # if df_merge.empty:  # first time in the loop
        # df_merge = df
    # else:
        # df_merge = pd.concat([df_merge, df], axis=1)# .loc[:, key] = df

def plot(args):  # assume the compare key is not None

    with open(args.filter) as fconf:
        conf = json.load(fconf)
    dname, name = os.path.split(os.path.splitext(args.filter)[0])

    yaxis = args.y  # it's a list, make it a loop?

    if args.y in ("slope", "lograte") or args.contour in ("slope", "r"):
        # regressfname = os.path.join(os.path.dirname(f), "regress.pkl")
        lst_file_regress = (glob.glob(os.path.join(dname, "**", "regress.pkl"), recursive=True))  # all config files)
        lst_file = [f.replace("regress.pkl", "config.json") for f in lst_file_regress]
    else:
        lst_file = (glob.glob(os.path.join(dname, "**", "config.json"), recursive=True))  # all config files)

    outroot = os.path.join(os.path.dirname(args.filter), "merge")
    pnames = ["loss", "ranks"]
    if name == "filter":
        name = ''  # remove the "filter" part if not changed
    outdir = os.path.join(outroot, name)

    compkey = args.compare
    xaxis = args.x
    keys = []
    noncomp = [k for k in sorted(conf.keys()) if k!=compkey and k!=xaxis and  k !=yaxis and len(conf[k]) > 1]
    if compkey:
        orderkey =[ compkey] +  noncomp
    else:
        orderkey = noncomp

    if args.contour:
        # yaxis = args.y[0]
        # if yaxis in conf.keys():
        orderkey = orderkey + [xaxis, yaxis]  # keep the contour the most enclosed
        # else:
            # orderkey = orderkey + [xaxis]  # will use the computed value as axis
    # if args.contour or xaxis != "niter":
    elif xaxis != "niter":
        orderkey = [xaxis] + orderkey

    fnames = dict()
    shared_conf = None
    for  f in lst_file:  # all possible experiemnets, will filter them based on conf

        with open(f) as fconf:
            data = json.load(fconf)
        try:
            if not all([data[k] in conf[k] for k in conf.keys()]):
                # if we don't have all the same parameters as the conf filter
                continue
        except:
            # print("missing key {}".format(k))
            continue

        key = tuple([data[k] for k in orderkey if k in data.keys() and (not k in conf.keys() or len(conf[k]) > 1)])
        if key in keys:
            print(f"Key {key} already in keys!")
        keys.append(key)
        fnames[key] = f
        shared_conf = {k:v for k,v in shared_conf.items() if k in data.keys() and v == data[k]} if shared_conf is not None else data

    # columns = pd.MultiIndex.from_tuples(keys, names = orderkey)
    columns = pd.Index([])  # empyt at first
    # index
    df_merge = pd.DataFrame(index=pd.Index([], name=xaxis), columns=columns)
    index = df_merge.index
    columns = df_merge.columns
    if args.contour:
        plotdir = os.path.join(outdir, "contour", f"{xaxis}-{yaxis}")
    elif compkey is not None:
        plotdir = os.path.join(outdir, "plot", xaxis, compkey)
    # elif args.heatmap:
        # plotdir = os.path.join(outdir, "heatmap", f"{xaxis}-{yaxis}")
    else:
        plotdir = os.path.join(outdir, "plot", f"{xaxis}")
    os.makedirs(plotdir, exist_ok=True)

    with open(os.path.join(plotdir, "shared_conf.json"), 'w') as fp:
        json.dump(shared_conf, fp, indent=0, sort_keys=True, default=set_default)

    if args.reset:
        shutil.rmtree(plotdir, ignore_errors=True)

    # for stat in args.y:  # each quantity after the other
    df_lst = []

    # for key in fnames.keys():  # for all the configurations, need to merge the dataframe

    partial_fn = partial(append_df, args=args, xaxis=xaxis, yaxis=yaxis, orderkey=orderkey)

    pool = mp.Pool(processes=args.nprocs)
    manager = mp.Manager()
    L = manager.list()
    [pool.apply_async(partial_fn, args=[L, (key,f)]) for (key,f) in fnames.items()]
    pool.close()
    pool.join()

    # ForkedPdb().set_trace()
    df_merge = pd.concat(L, axis=1)
    if noncomp:  # if there are other keys
        dfu = df_merge.unstack().unstack(level=noncomp)  # group the different results with
    else:
        # we don't need to compare a key
        dfu = df_merge.unstack().dropna(how='all').droplevel("niter").unstack()  # problem if there are no other keys! todo
        # remove the niter level, dangerous? not test on many cases
        # but otherwise if multiple niter for different experiments leads to
        # many NaNs,
        # Before, the niter are the columns? and along the x is the compare key

    dfu = dfu.dropna(how='all', axis=0)  # test ?
    totstats = df_merge.columns.get_level_values("stat").unique()
    userdef = args.contour if args.contour else args.y
    stats =  totstats if userdef is None else  [userdef]
    # if args.contour:

    for stat in stats:  # for all the stats we want to plot
        # dfu = df_merge.unstack()
        if "stat" in dfu.index.names:
            df_filt = dfu.loc[dfu.index.get_level_values("stat") == stat,: ].droplevel("stat")
        else:  # in the collumns, only the case when noncomp is False?
            df_filt = dfu.loc[:, dfu.columns.get_level_values("stat") == stat]
            df_filt = df_filt.stack().droplevel("stat")

        def process_contour(df_filt, dfu, stat, args=args):

            iterkeys = noncomp or df_filt.isna().any().any()  #  iterate over the keys
            if iterkeys:  # if we have some missing values
                keys = df_filt.keys()  # we take all remaining keys
            else:
                keys = ['']

            fill = stat == "r"
            for key in keys:  # each of the configurations
                keyname = ', '.join(dfu.keys().names)
                string = df_to_string(key, dfu.keys().names) if iterkeys else str(key) # the name of the file
                df_plot = df_filt[key] if iterkeys else df_filt
                df_plot = df_plot.dropna()
                if "niter" in df_plot.index.names:  # if we have several niter per configuration
                    all_xy = df_plot.index.droplevel("niter")
                    if len(all_xy) == len(all_xy.unique()):
                        df_plot = df_plot.droplevel('niter')
                    else:
                        pass # todo: take the last niter for each configuration

                if df_plot.empty:
                    continue

                if iterkeys:
                    df_plot = df_plot.unstack(level=xaxis)
                plotfolder = os.path.join(plotdir, string)
                # niterstr = key if df_filt.columns.name == 'niter' else ''
                if isinstance(df_plot, pd.DataFrame) and df_plot.columns.name == 'niter':
                    niter = df_plot.columns[-1]  # take the last iteration for plotting
                    df_plot = df_plot[niter]
                    df_plot = df_plot.unstack(0)  # put it in a 2d array
                elif isinstance(df_plot, pd.Series):
                    df_plot = df_plot.unstack(level=xaxis)  # put it in a 2darray
                    # niterstr = f"iter-{niter}"
                # plt.contourf(df_plot.reindex(df_plot.inde
                # ax = sns.contour(df_plot.reindex(df_plot.index.sort_values(ascending=False)))  # flip the y values
                xx = df_plot.columns
                yy = df_plot.index
                X, Y = np.meshgrid(xx, yy)
                # vmin = int(df_plot.min().min() + 0.5)
                vmin = df_plot.min().min()
                # vmax = max(vmin+1, int(df_plot.max().max() + 0.5))
                vmax = df_plot.max().max()
                if vmin == vmax:
                    continue
                levels = 40
                level_boundaries = np.linspace(vmin, vmax, levels+1)
                fig, ax = plt.subplots(figsize=(7, 6))
                if fill:
                    ctr = ax.contourf(X, Y, df_plot, level_boundaries, vmin=vmin, vmax=vmax)  # flip the y values
                else:
                    ctr = ax.contour(X, Y, df_plot, level_boundaries, vmin=vmin, vmax=vmax)  # flip the y values
                os.makedirs(plotfolder, exist_ok=True)
                plotnamepgf = f"{stat}.pgf"
                plotname = f"{stat}.pdf"
                plotnametex = f"{stat}.tex"
                # plt.title(stat + niterstr )

                keyval = str(key) if not isinstance(key, tuple) else ', '.join([str(k) for k in key])
                ctr.axes.set_xlabel(xx.name)
                ctr.axes.set_ylabel("$\lambda_{\\mathrm{min}}(\Sigma_0^{1/2})$")
                ctr.axes.figure.colorbar(ScalarMappable(norm=ctr.norm, cmap=ctr.cmap),
                                         ticks = np.linspace(vmin, vmax, num=5),
                                         boundaries=level_boundaries,
                                         values= (level_boundaries[:-1] + level_boundaries[1:])/2,
                                         ax=ctr.axes
                                         )
                # ctr.axes.figure.suptitle(f"{stat} for {keyname} = {keyval}")
                plt.savefig(os.path.join(plotfolder,  plotname), bbox_inches="tight")
                plt.savefig(os.path.join(plotfolder,  plotnamepgf))
                plt.close("all")

        def process_keys(noncomp, df_filt, dfu, stat):
            if noncomp or not noncomp and compkey is None:  # we have keys
                keys = df_filt.keys()
            elif compkey is not None:  # only compkey to vary on
                keys = [compkey]

            for key in keys:
                string = df_to_string(key, dfu.keys().names) if noncomp else str(key) # the name of the file
                df_plot = df_filt[key].dropna(how='all', axis=0) if noncomp else df_filt

                niter = None
                several_niter = False
                if xaxis != "niter":
                    if "niter" in df_plot.index.names:
                        all_keys =  df_plot.index.droplevel("niter")
                        if len(all_keys) == len(all_keys.unique()):  # one iter per configuration

                            niter = df_plot.index.get_level_values("niter")  # keep for reference?
                            df_plot = df_plot.droplevel("niter")
                        # else:
                            # several_niter = True

                        else:
                            # niter = niter[-1]
                            print("Several niter found for ", key)
                            # if compkey is not None and compkey != "niter":
                            if compkey is not None and compkey != "niter":
                                niter = niter[-1]  # take the last niter available
                                df_plot = df_plot.loc[df_plot.index.get_level_values("niter") == niter, :]
                            else:
                                several_niter = True
                            # else:
                                # compkey = "niter"
                                # df_plot = df_plot.unstack(level="niter")

                        # niterstr = ", niter = " + str(niter)
                    else:
                        niter = key
                    if not isinstance(niter, pd.Index):
                        niterstr = ", niter = " + str(niter)
                    else:
                        niterstr = ""

                else:
                    niterstr = ""
                if compkey is not None:
                    df_plot = df_plot.unstack(level=compkey)
                if compkey is None and several_niter:  # we comp on the niter
                    df_plot = df_plot.unstack(level="niter")
                # df_piv = df_filt.unstack().reset_index()#.dropna(axis=0, how="any")
                # dfu = df_piv.pivot(index=xaxis, columns=compkey, values=0)
            # for key in dfu.keys():  # for each configuration
                # df_plot = dfu.loc[:, key].to_frame(name=stat)
                if df_plot.empty:
                    continue
                kwargs = {}
                if stat == "rank W":
                    kwargs["alpha"] = 0.3
                if stat in ("slope", "lograte"):
                    compute_regress = True
                    kwargs["style"] = '+'
                    kwargs["zorder"] = 2  # in front of the regression line

                # if stat in ("rank W", "norm grad"):
                    # kwargs["style"] = "."
                # ax = df_plot.unstack(level=compkey).plot(y=stat, **kwargs)
                if not args.contour:
                    ax = df_plot.plot(**kwargs)
                    if stat == args.y and stat in ("slope", "lograte"):  # plot the slope
                        # ax.set_xlabel(None)
                        # leg = ax.get_legend()
                        # leg_labels = leg.get_texts()
                        handles, labels = ax.get_legend_handles_labels()
                        if isinstance(df_plot, pd.DataFrame) and len(df_plot.columns) > 1:
                            for idx, ckey in enumerate(df_plot.columns):  # the total compare keys available
                                reg = sp.stats.linregress(df_plot[ckey].index, df_plot[ckey])
                                linplot = ax.plot(df_plot[ckey].index, reg.slope * df_plot[ckey].index + reg.intercept, color=ax.lines[idx].get_color(), linestyle='dashed')
                                r2 = reg.rvalue**2
                                labels.append(f"$a={reg.slope:.2f}$, $r^2$={r2:.3f}")
                                handles.append(linplot[0])

                        else:  # only one linear regression to perform
                            if isinstance(df_plot, pd.DataFrame):
                                reg = sp.stats.linregress(df_plot.index, df_plot.values[:, 0])
                            else:
                                reg = sp.stats.linregress(df_plot.index, df_plot.values)
                            linplot = ax.plot(df_plot.index, reg.slope * df_plot.index + reg.intercept, color='grey', linestyle='dashed', alpha=0.9, zorder=0)
                            r2 = reg.rvalue**2
                            labels.append(f"$a={reg.slope:.2f}$, $r^2$={r2:.3f}")
                            handles.append(linplot[0])

                        p = len(handles) // 2
                        ax.legend(handles[p:], labels[p:], ncol=1)  # only plot the regression legend

                    # if stat == "slope":
                        # ax.set_ylabel("Rate of convergence")
                    # else:
                    ax.set_ylabel(stat)

                else:  # should never be the case?
                    ax = sns.contour(df_plot, **kwargs)

                if xaxis == "niter" and args.with_elevel and stat.find("loss A") != -1:
                    # we have to compute the target singularvalues for the
                    # betroffene files
                    # take a tuple of the current key
                    tkey = (key if isinstance(key, tuple) else (key, )) if noncomp else ()
                    namekeys = [tuple([k, *tkey]) for k in df_plot.dropna(axis=1,how="all").columns]
                    fs = [fnames[k] for k in namekeys]
                    # key  is missing the compkey
                    fargss = []
                    for f in fs:
                        with open(f) as fconf:
                            fargss.append(json.load(fconf))
                    dss = set( frgs["dataset_fname"] for frgs in fargss )
                    xdims = set( frgs["xdim"] for frgs in fargss )
                    if len(dss) == len(xdims) == 1:  # same dataset loaded
                        ds = dss.pop()
                        xdim = xdims.pop()
                        sets, _ = utils.load_dataset(fname=ds)  #
                        if sets:
                            trainset = sets["train"]
                            # args.load_dataset = fname_ds
                            STD = trainset.std
                            Sigma0 = STD.mm(STD.t())  # covariance of the target distribution
                            lambdas, U = np.linalg.eigh(Sigma0)
                            Es  = lambdas.cumsum()[::-1]  # the different energy levels, lambdas are <
                            x = np.linspace(0, 1, len(Es))
                            grbow = cm.get_cmap(plt.get_cmap("gist_rainbow"))(x)
                            [ax.axhline(E, color=grbow[i, :], alpha=0.5, linestyle='--', zorder=0) for i, E in enumerate(Es)]
                            xmax = ax.get_xlim()[1]
                            [ax.figure.text(xmax, E, i, transform=ax.transData, fontsize='x-small') for i, E in enumerate(Es,1) if i == 1 or i%5 == 0]
                    if stat == "loss A":
                        stat += " elevel"

                    # df_plot.plot(x="niter", y=stat)
                keyname = ', '.join(dfu.keys().names)
                keyval = str(key) if not isinstance(key, tuple) else ', '.join([str(k) for k in key])
                plotfolder = os.path.join(plotdir, stat)
                os.makedirs(plotfolder, exist_ok=True)
                # plt.suptitle(f"{keyname} = {keyval}")
                # plt.title(stat + niterstr)
                plt.savefig(os.path.join(plotfolder,  f"{string}.pgf"), bbox_inches="tight")
                plt.savefig(os.path.join(plotfolder,  f"{string}.pdf"))
                plt.close("all")
        # end of process keys
        # df_filt = df_merge.loc[:, df_merge.columns.get_level_values("stat") == stat]
        # the keys are either the different configurations or the niter
        # df_filt = dfu
        # df_filt.droplevel("stat")
        if args.contour:
            process_contour(df_filt, dfu, stat)
        else:
            process_keys(noncomp, df_filt, dfu, stat)

        # else:  # if there are no other keys as the comparison key
            # # dfu should have the iterations as columns and the compkey as
            # # xaxis
            # process_keys([compkey], df_filt, dfu, stat)

    return

def df_to_string(key, names):

    lst = []
    if not isinstance(key, tuple):  # if there is ony one key
        key = (key,)

    for k, name in zip(key, names):
        n = name[0]
        if isinstance(k, bool):
            lst.append(f'{n}' if k else f'no-{n}')
        else:
            val = str(k)
            if isinstance(k, (int, float)):
                lst.append(f'{n}{val}')
            # elif isinstance(arg, dict):
                # dirname.append('-'.join([f'{k}-{v}' for k,v in arg.items()]))
            else:
                lst.append(f'{n}-{val}')
    # string = os.path.join(name, '-'.join(name))
    string = '-'.join(lst)
    return string

def to_string(keys, dct):

    name = []
    for  key in keys:
        arg = dct[key]
        k = key[0]
        if isinstance(arg, bool):
            name.append( f'{k}' if arg else f'no-{k}')
        else:
            val = str(arg)
            if isinstance(arg, (int, float)):
                name.append(f'{k}{val}')
            # elif isinstance(arg, dict):
                # dirname.append('-'.join([f'{k}-{v}' for k,v in arg.items()]))
            else:
                name.append(f'{k}-{val}')
    # string = os.path.join(name, '-'.join(name))
    string = '-'.join(name)
    return string

if __name__ == "__main__":

    parser = argparse.ArgumentParser()

    parser.add_argument("-o", "--output", type=str, help="output the config diff between the different experiments")

    parser.add_argument("--nprocs","-n", type=int, default=1, help="the number of threads to span")
    parser.add_argument("--filter", help="json file use to filter")
    parser.add_argument("--diff", action="store_true", help="diff mode, write to file")
    parser.add_argument("--compare", type=str, help="the key for the pot")
    parser.add_argument("--plot", action="store_true", help="simply plots the data again")
    parser.add_argument("--reset", action="store_true", help="set to reset the output")
    parser.add_argument("--with-elevel", action="store_true", help="energy level plotting")
    parser.add_argument("-c", "--compute", action="store_true", help="compute additional properties on the data")
    parser.add_argument("--force", action="store_true", help="force new computations")
    parser.add_argument("-x", default="niter",  help="the x axis")
    parser.add_argument("-y", help="the quantities to plot as a function of x")
    parser.add_argument("-ctr", "--contour", choices=("slope", "r", "lograte"), help="plot a heat map of the value with x/y coordinates")
    parser.add_argument("dirs", nargs="*", help="the different directories to look for")

    args = parser.parse_args()
    main(args)


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

import sys, os, time
from myzmq.zmq_comm import *
from myzmq.zmq_cfg import *

import rospy
from sensor_msgs.msg import Image
from cv_bridge import CvBridge
import cv2
from PIL import Image as plimg
import numpy as np

DISPLAY_COLOR_SEG = 1
ID_CHANGE = {'0':255, '1':255, '2':255, '3':255, '4':255, '5':255, '6':255, '7':0, '8':1, '9':255, '10':255, '11':2, '12':3, '13':4, '14':255,'15':255,'16':255, '17':5, '18':255, '19':6, '20':7, '21':8, '22':9, '23':10, '24':11, '25':12, '26':13, '27':14, '28':15, '29':255, '30':255, '31':16, '32':17, '33':18, '-1':-1}
colors = [[128,64,128], [244,35,232], [70,70,70], [102,102,156], [190,153,153], [153,153,153], [250,170,30], [220,220,0], [107,142,35], [152,251,152], [255,0,0], [220,20,60], [70,130,180], [0,0,142], [0,0,70], [0,60,100], [0,80,100], [0,0,230], [119,11,32], [0,0,0]]
ALL_TYPES = {'0':'road', '1':'sidewalk', '2':'building', '3':'wall', '4':'fence', '5':'pole', '6':'trafficc light', '7':'traffic sign', '8':'vegetation', '9':'terrain', '10':'sky', '11':'person', '12':'rider', '13':'car', '14':'truck', '15':'bus', '16':'train', '17':'motorcycle', '18':'bike', '19':'unknown',}

rospy.init_node('seg_trans', anonymous=True)
segcli = zmq_comm_cli_c(name=name_semanticseg, ip=ip_semanticseg, port=port_semanticseg)
res_pub = rospy.Publisher('/seg_img', Image, queue_size=1)
bridge = CvBridge()

def color_the_typeid(type_id):
    colorimg = []
    maxid = []
    countover19 = 0
    for i in range(type_id.shape[0]):
        for j in range(type_id.shape[1]):
            if(type_id[i][j] < 34):
                realid = ID_CHANGE[str(type_id[i][j])]
            if(realid >= 19):
                countover19 += 1
                realid = 19
            colorimg.append(colors[realid])
    colorimg = np.array(colorimg,dtype='uint8')
    colorimg = colorimg.reshape((480,640,3))
    colorimg = cv2.resize(colorimg, (640,480))
    return colorimg

def inputimg_cb(data):
    rospy.loginfo('start')
    img = bridge.imgmsg_to_cv2(data, "bgr8")
    img = plimg.fromarray(cv2.cvtColor(img,cv2.COLOR_BGR2RGB))
    data_jpeg=img_rgb_to_jpeg(img)
    rospy.loginfo('1')
    res = segcli.execute(data_jpeg)
    rospy.loginfo('2')
    if(DISPLAY_COLOR_SEG):
        colorimg = color_the_typeid(res)
        cv2.imshow('segcolor', colorimg)
        cv2.waitKey(1)
    res = bridge.cv2_to_imgmsg(res, encoding="8UC1")
    res_pub.publish(res)
    rospy.loginfo('end')

if __name__ == '__main__':
    rospy.Subscriber('/camera/image_raw', Image, inputimg_cb)
    rospy.loginfo('[seg_trans]init done')
    rospy.spin()


import numpy as np
import wave
import scipy.signal as signal
import matplotlib.pyplot as plt
import lpc

def enframe(signal, nw, inc, winfunc):
    '''将音频信号转化为帧。
    参数含义：
    signal:原始音频型号
    nw:每一帧的长度(这里指采样点的长度，即采样频率乘以时间间隔)
    inc:相邻帧的间隔（同上定义）
    '''
    signal_length=len(signal) #信号总长度
    if signal_length<=nw: #若信号长度小于一个帧的长度，则帧数定义为1
        nf=1
    else: #否则，计算帧的总长度
        nf=int(np.ceil((1.0*signal_length-nw+inc)/inc))
    pad_length=int((nf-1)*inc+nw) #所有帧加起来总的铺平后的长度
    zeros=np.zeros((pad_length-signal_length,)) #不够的长度使用0填补，类似于FFT中的扩充数组操作
    pad_signal=np.concatenate((signal,zeros)) #填补后的信号记为pad_signal
    indices=np.tile(np.arange(0,nw),(nf,1))+np.tile(np.arange(0,nf*inc,inc),(nw,1)).T  #相当于对所有帧的时间点进行抽取，得到nf*nw长度的矩阵[0-160],[40-200]...
    indices=np.array(indices,dtype=np.int32) #将indices转化为矩阵
    frames=pad_signal[indices] #得到帧信号
    win=np.tile(winfunc,(nf,1))  #window窗函数，这里默认取1
    return frames*win,nf   #返回帧信号矩阵

def wavread(filename):

    f = wave.open(filename, 'rb')
    params = f.getparams()
    nchannels, sampwidth, framerate, nframes = params[:4]
    strData = f.readframes(nframes)  # 读取音频，字符串格式
    waveData = np.fromstring(strData, dtype=np.int16)  # 将字符串转化为int
    f.close()
    waveData = waveData * 1.0 / (max(abs(waveData)))  # wave幅值归一化
    #画图
    time = np.arange(0, nframes) * (1.0 / framerate)
    plt.figure()
    plt.xlabel("Time(s)")
    plt.ylabel("Amplitude")
    plt.title("Single channel wavedata")
    plt.plot(time, waveData)
    plt.show()
    waveData = np.reshape(waveData, [nframes, nchannels]).T

    return waveData

def calEnergy(Frame,nf,nw):
    '''
    计算每一帧的能量
    :param Frame: 要计算的帧数组
    :param nf: 数组行，多少个帧
    :param nw: 数组列，每个帧多少个采样点
    :return: 返回所有帧的能量，一个列表
    '''
    result=[]
    if nf==1:
        sum = 0
        for j in range(nw):
            sum = sum + Frame[j] * Frame[j]
        result.append(sum)
    else:
        for i in range(nf):
            sum=0
            for j in range(nw):
                sum=sum+Frame[i][j]*Frame[i][j]
            result.append(sum)
    return result

def calzhuo(Energy,nf):
    '''
    计算出浊音的索引
    :param Energy: 能量数组
    :param nf: 多少帧
    :return: 返回每一个浊音段对应的帧的索引
    '''
    result=[]
    cache=[]
    for i in range(nf):
        if Energy[i]>0.1:
            cache.append(i)
            if Energy[i+1]<0.1 and Energy[i+2]<0.1:
                result.append(cache)
                cache=[]
    return result

def calxiangguan(Frame,i,nw):
    '''
    计算一帧自相关
    :param Frame: 加窗后的帧
    :param i: 对应帧的索引
    :param nw: 该帧有多少
    :return: 返回该帧的基因频率
    '''
    sum=0
    result=[]
    result2=[]
    for k in range(0,100):
        for j in range(nw-k):
            sum=sum+Frame[i][j]*Frame[i][j+k]
        result.append(sum)
    #归一化
    for l in range(len(result)):
        result2.append(result[l]/result[0])
    return 8000/result2.index(max(result2[-80:]))

filename = '01234.wav'
data = wavread(filename)
nw = 160  #窗大小
inc = 40 #窗偏移
winfunc = signal.hamming(nw) #汉明窗
Frame,nf = enframe(data[0], nw, inc, winfunc) #分帧加窗
Energy=calEnergy(Frame,nf,nw) #计算每一帧的能量

#画出每一帧的能量图
x = np.arange(1,len(Energy)+1)
plt.figure()
plt.title(u'每一帧的能量')
plt.xlabel(u'帧数')
plt.ylabel(u'能量')
plt.plot(x,Energy)
plt.show()

#计算浊音对应的帧
zhuo=calzhuo(Energy,nf)
print(zhuo)

#计算浊音每一帧的基音频率
xiangguanindex=[]
for i in range(5):
    for j in zhuo[i]:
        xiangguanindexi=calxiangguan(Frame,j,nw)
        xiangguanindex.append(xiangguanindexi)
    xiangguanindex.append(0 )
#画图
x = np.arange(1,len(xiangguanindex)+1)
plt.figure()
plt.title(u'浊音每一帧的基音频率')
plt.ylabel(u'基音频率')
plt.xlabel(u'每一部分的帧')
plt.plot(x,xiangguanindex)
plt.show()

#求预测增益
ans=[]
for i in zhuo[0]:
    A,E,K=lpc.levinson_1d(Frame[i],10)
    pre= signal.lfilter(A,[1],Frame[i])
    err=Frame[i]-pre
    erreng=calEnergy(err,1,160)
    result=Energy[i]/erreng
    ans.append(result)
    if i==180 :
        plt.figure()
        plt.subplot(2,1,1)
        x = np.arange(1, len(Frame[180]) + 1)
        plt.plot(x, Frame[180])
        plt.title(u'原始180帧')
        plt.subplot(2, 1, 2)
        x = np.arange(1, len(pre) + 1)
        plt.plot(x, pre)
        plt.title(u'预测180帧')
#画图
x = np.arange(1,len(ans)+1)
plt.figure()
plt.title(u'数字0的预测增益')
plt.plot(x,ans)
plt.show()


import logging
import os

import requests
from bs4 import BeautifulSoup

logging.basicConfig(level=logging.INFO)
token = os.getenv("TOKEN")

def get_json_response(upc):
    url = "https://www.you-buy.ca/en/ubcommon/usstore/search/products"

    payload = f"q={upc}&node_id=&page=1&brand=&ufulfilled=&price_range=&sort_by=&s_id=81&lang=&dc=&csrftoken={token}"
    headers = {
        "authority": "www.you-buy.ca",
        "accept": "text/html, */*; q=0.01",
        "accept-language": "en-GB,en;q=0.7",
        "content-type": "application/x-www-form-urlencoded; charset=UTF-8",
        "cookie": "__zlcmid=1CqlMVkSecMK0Yr; PHPSESSID=7013nlchs3u18s0b4tjrhcvhr0",
        "origin": "https://www.you-buy.ca",
        "referer": "https://www.you-buy.ca/en/search/?q=B00C91Q86I",
        "sec-fetch-dest": "empty",
        "sec-fetch-mode": "cors",
        "sec-fetch-site": "same-origin",
        "sec-gpc": "1",
        "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/106.0.0.0 Safari/537.36",
        "x-requested-with": "XMLHttpRequest",
    }

    logging.info("Get Product Url")
    response = requests.request("POST", url, headers=headers, data=payload, timeout=60)
    logging.info("Requests Status Code: %s", response.status_code)

    soup = BeautifulSoup(response.text, "lxml")

    return soup

def get_product_url(upc):
    soup = get_json_response(upc)
    try:
        product_url = soup.find("input", {"name": "product_url"}).get("value")
    except AttributeError:
        product_url = ""
    logging.info("Product Url: %s", product_url)
    return product_url


from django import forms
from .models import LedgbookPost
from .models import LedgbookMain

class PostForm(forms.ModelForm):

    class Meta:
        model = LedgbookPost
        fields = ('title','text')

class LedgbookMainForm(forms.ModelForm):
    class Meta:
        model = LedgbookMain
        fields = (
            'user'
            , 'year'
            , 'month'
            , 'degree'
            , 'prv_balance'
            , 'add_deposit'
            , 'revenue'
            , 'change'
            , 'change_rate'
            , 'trgt_add_deposit'
            , 'trgt_change_rate'
            , 'trgt_revenue'
            , 'trgt_cur_balance'
            , 'cur_balance'
            , 'invst_balance'
            , 'finished_yn'
            , 'achievement_rate'
        )


from protors.protors import ProtoRS
from util.log import Log

from util.args import get_args, save_args, get_optimizer
from util.data import get_dataloaders
from util.init import init_model
from util.net import get_network, freeze
from util.save import *
from util.analyze import *
from protors.train import train_epoch
from protors.test import eval
from protors.project import project
from protors.upsample import upsample

import torch
from shutil import copy
from copy import deepcopy
import argparse

def get_args_explain() -> argparse.Namespace:
    parser = argparse.ArgumentParser('Load and globally explain a ProtoRS model.')
    parser.add_argument('--dataset',
                        type=str,
                        default='CUB-200-2011',
                        help='Data set on which the ProtoRS model was trained on')
    parser.add_argument('--batch_size',
                        type=int,
                        default=64,
                        help='Batch size for projecting, testing, and detecting dead nodes')
    parser.add_argument('--disable_cuda',
                        action='store_true',
                        help='Flag that disables GPU usage if set')
    parser.add_argument('--state_dict_dir_model',
                        type=str,
                        default='',
                        help='The directory containing a state dict (checkpoint) with a pretrained protors. Note that training further from a checkpoint does not seem to work correctly. Evaluating a trained protors does work.')
    parser.add_argument('--log_dir',
                        type=str,
                        default='./runs/run_protors',
                        help='The directory in which output are to be put')
    parser.add_argument('--dir_for_saving_images',
                        type=str,
                        default='prototype_upsampling_results',
                        help='Directory for saving the prototypes, patches and heatmaps')
    parser.add_argument('--upsample_threshold',
                        type=float,
                        default=0.98,
                        help='Threshold (between 0 and 1) for visualizing the nearest patch of an image after upsampling. The higher this threshold, the larger the patches.')
    parser.add_argument('--no_reeval',
                        action='store_true',
                        help='Not to reevaluate model after loading')
    parser.add_argument('--no_upsample',
                        action='store_true',
                        help='Not to perform prototype upsampling')
    parser.add_argument('--no_ruleprint',
                        action='store_true',
                        help='Not to perform rule printing')
    args = parser.parse_args()
    return args

def explain_global(model:ProtoRS = None,
                    project_info = None,
                    device = None,
                    args = None,
                    log:Log = None):
    args = args or get_args()
    args.log_dir = args.log_dir + '/explained_model'
    args.rule_file = args.log_dir + '/ruleset.csv'

    if device is None:
        if not args.disable_cuda and torch.cuda.is_available():
            # device = torch.device('cuda')
            device = torch.device('cuda:{}'.format(torch.cuda.current_device()))
        else:
            device = torch.device('cpu')

    if log is None: log = Log('/tmp/protors_log') # dummy logger

    trainloader, projectloader, testloader, classes, num_channels = get_dataloaders(args) # Obtain the dataloader used for projection

    if model is None: # model needs to be loaded
        # load model
        if args.disable_cuda or not torch.cuda.is_available():
            model = torch.load(args.state_dict_dir_model+'/model.pth', map_location=device)
        else:
            model = torch.load(args.state_dict_dir_model+'/model.pth')
        model.to(device=device)

    # Reevaluate
    if hasattr(args, 'no_reeval') and not args.no_reeval:
        eval_info = eval(model, testloader, 'projected', device, log)
        print("Loaded model's accuracy: {0}".format(eval_info['test_accuracy']))

    #  Upsample prototypes
    if not hasattr(args, 'no_upsample') or (hasattr(args, 'no_upsample') and not args.no_upsample):
        # Project
        if project_info is None: # model was not projected beforehand
            # Perform projection without saving
            project_info, model = project(model, projectloader, device, args, log)
            eval_info = eval(model, testloader, 'projected', device, log)
            print("Projected model's accuracy: {0}".format(eval_info['test_accuracy']))

        # Upsample
        print('Upscaling prototypes ...')
        project_info = upsample(model, project_info, projectloader, '', args, log)
        print('Upscaling finished.')

    # Print rules
    if not hasattr(args, 'no_ruleprint') or (hasattr(args, 'no_ruleprint') and not args.no_ruleprint):
        print('Printing rule set ...')
        with open(args.rule_file, 'w') as rule_file:
            model.rule_print(classes, trainloader, device=device, file=rule_file)
        print('Rule set printed at {}'.format(args.rule_file))

        # Save extracted model
        model.save(f'{args.log_dir}')

if __name__ == '__main__':
    torch.autograd.set_detect_anomaly(True)
    args = get_args_explain()
    explain_global(args=args)