#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""

@author: andy
"""

import matplotlib.pyplot as plt
import subprocess
import matplotlib.gridspec as gridspec
import tensorflow as tf

def read_and_decode(filename,shape):
      tfrecord_file_queue = tf.train.string_input_producer(filename, name='queue')
      reader = tf.TFRecordReader()
      _, tfrecord_serialized = reader.read(tfrecord_file_queue)
      tfrecord_features = tf.parse_single_example(tfrecord_serialized,
                        features={
                            'label': tf.FixedLenFeature([], tf.int64),
                            'image': tf.FixedLenFeature([], tf.string),
                        }, name='features')
      image = tf.decode_raw(tfrecord_features['image'], tf.uint8)
      image = tf.reshape(image, shape)
      image = tf.cast(image, tf.float32) * (1. / 255)
      label = tfrecord_features['label']
      label = tf.cast(label, tf.int32)
      return  image, label

def imnormalzo(image):
    immin=(image).min()
    immax=(image).max()
    image=(image-immin)/(immax-immin+1e-8)
    return image

def saveasimagesandvideo(samples,out_dir,ffmpeg_loglevel='quiet', fps=5):
    [num_frames, image_size, _, channel] = samples.shape
    for ifr in range(num_frames):
        fig,ax = plt.subplots()
        ax.imshow(imnormalzo(samples[ifr]), aspect='equal')
        fig = plt.gcf()
        fig.set_size_inches(image_size/96.0,image_size/96.0)
        plt.subplots_adjust(top=1,bottom=0,left=0,right=1,hspace =0, wspace =0)
        plt.margins(0,0)
        plt.savefig("%s/%03d.png" % (out_dir, ifr),dpi=96.0,pad_inches=0.0)
        plt.close(fig)
    '''
    # video
    subprocess.call('ffmpeg -loglevel {} -r {} -i {}/%03d.png -vcodec mpeg4 -y {}/sample.avi'.format(
            ffmpeg_loglevel, fps, out_dir, out_dir), shell=True)
    '''

def plotimgs(samples,Nh,Nc,IMG_HEIGHT,IMG_WIDTH,channel):
    fig = plt.figure(figsize=(Nc, Nh))
    gs = gridspec.GridSpec(Nh, Nc)
    gs.update(wspace=0.01, hspace=0.01)

    for i, sample in enumerate(samples[0:Nh*Nc,:,:,:]):
        ax = plt.subplot(gs[i])
        plt.axis('off')
        ax.set_xticklabels([])
        ax.set_yticklabels([])
        ax.set_aspect('equal')
        if channel==1:
            image=sample.reshape(IMG_HEIGHT, IMG_WIDTH)
        else:
            image=sample.reshape(IMG_HEIGHT, IMG_WIDTH,channel)
        image=imnormalzo(image)
        plt.imshow(image)
    return fig


from typing import Any, Dict, List, Optional, Union

import attr
import humanfriendly
from jsonschema import ValidationError, validate

from matrix_content_scanner.utils.errors import ConfigError

_ONE_WEEK_SECONDS = 604800.0

def _parse_duration(duration: Optional[Union[str, float]]) -> Optional[float]:
    """Parse a time duration into a float representing an amount of second. If the given
    value is None, or already a float, returns it as is.

    Args:
        duration: The duration to parse.

    Returns:
        The number of seconds in the given duration.
    """
    if duration is None or isinstance(duration, float):
        return duration

    try:
        return humanfriendly.parse_timespan(duration)
    except humanfriendly.InvalidTimespan as e:
        raise ConfigError(e)

def _parse_size(size: Optional[Union[str, float]]) -> Optional[float]:
    """Parse a file size into a float representing the number of bytes for that size. If
    the given value is None, or already a float, returns it as is.

    Args:
        size: The size to parse.

    Returns:
        The number of bytes represented by the given size.
    """
    if size is None or isinstance(size, float):
        return size

    try:
        return humanfriendly.parse_size(size)
    except humanfriendly.InvalidSize as e:
        raise ConfigError(e)

# Schema to validate the raw configuration dictionary against.
_config_schema = {
    "type": "object",
    "required": ["web", "scan", "crypto"],
    "additionalProperties": False,
    "properties": {
        "web": {
            "type": "object",
            "required": ["host", "port"],
            "additionalProperties": False,
            "properties": {
                "host": {"type": "string"},
                "port": {"type": "integer"},
            },
        },
        "scan": {
            "type": "object",
            "required": ["script", "temp_directory"],
            "additionalProperties": False,
            "properties": {
                "script": {"type": "string"},
                "temp_directory": {"type": "string"},
                "removal_command": {"type": "string"},
                "allowed_mimetypes": {"type": "array", "items": {"type": "string"}},
            },
        },
        "download": {
            "type": "object",
            "additionalProperties": False,
            "properties": {
                "base_homeserver_url": {"type": "string"},
                "proxy": {"type": "string"},
                "additional_headers": {
                    "type": "object",
                    "additionalProperties": {"type": "string"},
                },
            },
        },
        "crypto": {
            "type": "object",
            "required": ["pickle_path", "pickle_key"],
            "additionalProperties": False,
            "properties": {
                "pickle_path": {"type": "string"},
                "pickle_key": {"type": "string"},
            },
        },
        "result_cache": {
            "type": "object",
            "additionalProperties": False,
            "properties": {
                "max_size": {"type": "integer"},
                "ttl": {"type": ["string", "number"]},
                "exit_codes_to_ignore": {
                    "type": "array",
                    "items": {"type": "integer"},
                },
                "max_file_size": {"type": ["string", "number"]},
            },
        },
    },
}

@attr.s(auto_attribs=True, frozen=True)
class WebConfig:
    """Configuration for serving the HTTP API."""

    host: str
    port: int

@attr.s(auto_attribs=True, frozen=True, slots=True)
class ScanConfig:
    """Configuration for scanning files."""

    script: str
    temp_directory: str
    removal_command: str = "rm"
    allowed_mimetypes: Optional[List[str]] = None

@attr.s(auto_attribs=True, frozen=True, slots=True)
class ResultCacheConfig:
    """Configuration for caching scan results."""

    max_size: int = 1024
    ttl: float = attr.ib(default=_ONE_WEEK_SECONDS, converter=_parse_duration)
    exit_codes_to_ignore: Optional[List[int]] = None
    max_file_size: Optional[float] = attr.ib(default=None, converter=_parse_size)

@attr.s(auto_attribs=True, frozen=True, slots=True)
class DownloadConfig:
    """Configuration for downloading files."""

    base_homeserver_url: Optional[str] = None
    proxy: Optional[str] = None
    additional_headers: Optional[Dict[str, str]] = None

@attr.s(auto_attribs=True, frozen=True, slots=True)
class CryptoConfig:
    """Configuration for decrypting encrypted bodies."""

    pickle_path: str
    pickle_key: str

class MatrixContentScannerConfig:
    def __init__(self, config_dict: Dict[str, Any]):
        if not isinstance(config_dict, dict):
            raise ConfigError("Bad configuration format")

        try:
            validate(config_dict, _config_schema)
        except ValidationError as e:
            raise ConfigError(e.message)

        self.web = WebConfig(**(config_dict.get("web") or {}))
        self.scan = ScanConfig(**(config_dict.get("scan") or {}))
        self.crypto = CryptoConfig(**(config_dict.get("crypto") or {}))
        self.download = DownloadConfig(**(config_dict.get("download") or {}))
        self.result_cache = ResultCacheConfig(**(config_dict.get("result_cache") or {}))


import time
import os
import shutil
import multiprocessing

def copy_item(item):
    source_item, destination_item = item
    try:
        if os.path.isfile(source_item):
            shutil.copy2(source_item, destination_item)
        else:
            shutil.copytree(source_item, destination_item)
        # print(f"Copied item: {destination_item}")
    except Exception as e:
        print(f"Failed to copy item: {e}")

if __name__ == '__main__':
    start_time = time.time()
    # Configuration
    source_path = "demo_files4/"
    destination_path = "/home/tmp"

    # Create a multiprocessing.Pool
    with multiprocessing.Pool() as pool:
        # Iterate over files and directories in source path
        for root, dirs, files in os.walk(source_path):
            for file in files:
                source_file = os.path.join(root, file)
                destination_file = os.path.join(
                    destination_path,
                    os.path.relpath(source_file, source_path)
                )
                item = (source_file, destination_file)
                pool.apply_async(copy_item, (item,))
            for dir in dirs:
                source_dir = os.path.join(root, dir)
                destination_dir = os.path.join(
                    destination_path,
                    os.path.relpath(source_dir, source_path)
                )
                item = (source_dir, destination_dir)
                pool.apply_async(copy_item, (item,))

        # Close the pool and wait for all processes to finish
        pool.close()
        pool.join()

    print("All items copied and took seconds =",
          time.time() - start_time)


import copy
import logging
import os
import shutil
import tempfile
import pysam
import compare_reads

logger = logging.getLogger(__name__)

def is_file_coordinate_sorted(path, reads_to_check=1000):
    """Determine if first 1000 reads are coordinate sorted."""
    with pysam.AlignmentFile(path) as f:
        i = 0
        current_start = 0
        current_tid = 0
        try:
            if f.header['HD']['SO'] == 'coordinate':
                # We trust the header, seems most sane
                logger.info("%s is sorted by coordinate", path)
                return True
        except Exception:
            pass
        for r in f:
            if i > reads_to_check:
                return True
            if not r.is_unmapped:
                i += 1
                if r.reference_start >= current_start and r.tid >= current_tid:
                    current_start = r.reference_start
                    current_tid = r.tid
                    continue
                else:
                    logger.info("%s is not sorted by coordinate", path)
                    return False
    logger.info("%s is sorted by coordinate", path)
    return True

def get_mean_read_length(path, reads_to_check=1000):
    """Get mean read length for the first reads in source."""
    read_length = 0
    i = 0  # In case no reads are in source, avoids potential unbounded variable error
    with pysam.AlignmentFile(path) as source:
        for i, r in enumerate(source):
            if i >= reads_to_check:
                break
            read_length += r.query_length
    mean_read_length = int(read_length / i) if i > 0 else 0
    logger.info("Mean read length is '%s'", mean_read_length)
    return mean_read_length

def get_queryname_positions(fn, chunk_size=10000, threads=0):
    """
    Get positions in order to split alignment file into equal-sized portions.

    Return a list of tuples, where the first item is the current file position for the first read,
    and the second item is the last query_name of the chunk.
    """
    with pysam.AlignmentFile(fn, threads=threads) as f:
        start = f.tell()
        last_pos = start
        seek_positions = []
        qn = ''
        count = 0
        for r in f:
            current_query_name = r.query_name
            if current_query_name != qn:
                # Next query_name
                count += 1
                if count % chunk_size == 0 and qn:
                    # We've reach a chunk, we append the last_pos as a new start
                    seek_positions.append((start, qn))
                    start = last_pos
                qn = current_query_name
            if (count + 1) % chunk_size == 0:
                last_pos = f.tell()
        seek_positions.append((start, qn))
    return seek_positions

def get_reads(fn, start, last_qname, threads=0):
    """Get reads starting at `start` and ending with last_qname."""
    with pysam.AlignmentFile(fn, threads=threads) as f:
        # The first read returned (even after seeking!) is always the first read in the file.
        # Calling next once resolves that.
        if not f.tell() == start:
            next(f)
            f.seek(start)
        reads = []
        for r in f:
            if compare_reads._compare_sort_with_queryname(r, last_qname) < 1:
                reads.append(r)
            else:
                break
    return reads

def start_positions_for_last_qnames(fn, last_qnames, threads=0):
    """Return start positions that returns the first read after the current last qname."""
    with pysam.AlignmentFile(fn, threads=threads) as f1, pysam.AlignmentFile(fn, threads=threads) as f2:
        last_qnames = copy.deepcopy(last_qnames)
        seek_positions = []
        last_seek = f1.tell()
        i = 0
        next(f1)
        current_segment = next(f1)
        next(f2)
        while last_qnames:
            current_last_qname = last_qnames.pop(0)
            try:
                while compare_reads._compare_sort_with_queryname(current_segment, current_last_qname) < 1:
                    # IOW while we haven't iterated past the expected read position
                    current_segment = next(f1)
                    next(f2)
                i += 1
                logging.info("Got chunk %i", i)
                seek_positions.append(last_seek)
                last_seek = f2.tell()
            except StopIteration:
                break
        seek_positions.append(last_seek)
        return seek_positions

def merge_bam(bam_collection, output_path, template_bam=None, sort_order=None, threads=1):
    """Merge a readname sorted collection of BAM files."""
    bam_collection = [bam for bam in bam_collection if os.path.exists(bam)]
    if not template_bam:
        template_bam = bam_collection[0]
    pysam.cat('-h', template_bam, '-o', output_path, *bam_collection)
    for bam in bam_collection:
        try:
            os.remove(bam)
        except OSError:
            pass
    if sort_order:
        sort_bam(inpath=output_path, output=output_path, sort_order=sort_order, threads=threads)
    return output_path

def sort_bam(inpath, output, sort_order, threads=1, cram=False, reference_fasta=None):
    """
    Sort bam file at inpath using sort_order and write output to `output`.

    Specify `cram=True` and supply `reference_fasta` to write output as CRAM file.
    """
    fd = None
    if inpath == output:
        # We sort 'in place'
        fd, temp_out = tempfile.mkstemp(dir=os.path.dirname(output))
    else:
        temp_out = output
    args = ["-@%s" % threads]
    if sort_order == 'queryname':
        args.append('-n')
    if cram:
        args.extend(['--output-fmt=cram,no_ref'])
    args.extend(['-o', temp_out, inpath])
    pysam.sort(*args)
    if temp_out != output:
        shutil.move(temp_out, output)
    if fd:
        os.close(fd)
    return output

def index_bam(inpath):
    """Index BAM file at input."""
    if not os.path.exists("%s.bai" % inpath):
        pysam.index(inpath)

def split_locations_between_clusters(bamfile, self_tag='AD', other_tag='BD', distance=1000000, region=None):
    """Split a bam file into multiple chunks, where chunks have no tagged reads close to the split."""
    # TODO: modify this so we don't split small chromosomes, instead we should "merge" small chromosomes
    index_bam(bamfile)
    limit_chrom = None
    limit_start = None
    limit_end = None
    if region:
        region = region.split(':')
        limit_chrom = region[0]
        limit_start, limit_end = [int(i) for i in region[1].split('-')]
    with pysam.AlignmentFile(bamfile) as f:
        name_length = [(chrom['SN'], chrom['LN']) for chrom in f.header['SQ']]
        chunks = []
        for (name, length) in name_length:
            if limit_chrom and limit_chrom != name:
                continue
            chunk = []
            split_pos = [i for i in range(1, length, distance)]
            split_pos.append(length)
            start_end_pos = [list(z) for z in zip(split_pos[:-1], split_pos[1:])]
            for i, (start, end) in enumerate(start_end_pos):
                if limit_end:
                    if end > limit_end or start < limit_start:
                        continue
                if not end == length:  # i.e this is not the last chunk
                    end = find_end(f, chrom=name, end=end, self_tag=self_tag, other_tag=other_tag)
                    start_end_pos[i + 1][0] = end  # Update the next start position with the new end
                # pysam doesn't like to start coordinates at 0, so we just add +1
                chunk.append("%s:%s-%s" % (name, start, end))
            chunks.extend(chunk)
    return chunks

def find_end(f, chrom, end, self_tag, other_tag, padding=5000):
    """Find a position where the distance between tags is high and we can split safely."""
    min_end = end - padding
    max_end = end + padding
    current_end = min_end
    tagged_reads = [r for r in f.fetch(reference=chrom, start=min_end, end=max_end) if not r.is_unmapped and (r.has_tag(self_tag) or r.has_tag(other_tag))]
    if not tagged_reads:
        return end
    distance_to_tag = tagged_reads[0].reference_start - min_end
    pos = int((min_end + tagged_reads[0].reference_start) / 2.0)
    for r in tagged_reads:
        current_distance = r.reference_start - current_end
        if current_distance > distance_to_tag:
            distance_to_tag = current_distance
            pos = int((r.reference_start + current_end) / 2.0)
        current_end = r.reference_start
    if max_end - current_end > distance_to_tag:
        pos = int((max_end + current_end) / 2.0)
    return pos

class BamAlignmentWriter(object):
    """Wrap pysam.AlignmentFile with samtools for multithreaded compressed writing."""

    def __init__(self, path, template=None, header=None, threads=1, sort_order='coordinate'):
        """
        Write Bam files.

        Use this class with a context handler.

        :param path: Wite bam file to location `path`.
        :param template: Specify either template or header to write a bam file.
        :param header: Specify either template or header to write a bam file.
        :param sort_order: Can be `coordinate` or `queryname` and will cause the output file to sorted by this strategy.
        """
        self.template = template
        self.header = header
        self.path = path
        self.threads = threads
        self.sort_order = sort_order

    def close(self):
        """
        Close filehandles and subprocess safely.

        If necessary will sort the the file.
        """
        self.af.close()
        if os.path.exists(self.path):
            sort_order = 'coordinate' if is_file_coordinate_sorted(self.path) else 'queryname'
            if sort_order != self.sort_order:
                sort_bam(inpath=self.path, output=self.path, sort_order=self.sort_order, threads=self.threads)

    def __enter__(self):
        """Provide context handler entry."""
        self.af = pysam.AlignmentFile(self.path, mode="wb", template=self.template, header=self.header, threads=self.threads)
        return self.af

    def __exit__(self, type, value, traceback):
        """Provide context handler exit."""
        self.close()

class BamAlignmentReader(object):
    """Wraps pysam.AlignmentFile with sambamba for reading if input file is a bam file."""

    def __init__(self, path, sort_order=None, threads=4, region=None, index=False):
        """
        Read Bam files.

        Use this class with a contexthandler.

        :param path: Path to read bam file from.
        :param external_bin: Specify `samtools` to use samtools or `None` to use pysam for writing to `path`.
        :param sort_order: Can be `coordinate` or `queryname` and will cause the output file to sorted by this strategy.
        """
        self.path = path
        self.sort_order = sort_order
        self.threads = threads
        self.region = region
        self.index = index
        self.setup()

    def setup(self):
        """Index and sort alignment file if necessary."""
        if self.sort_order:
            sort_order = 'coordinate' if is_file_coordinate_sorted(self.path) else 'queryname'
            if sort_order != self.sort_order:
                self.path = sort_bam(inpath=self.path, output=self.path, sort_order=self.sort_order, threads=self.threads)
        if self.region or self.index:
            index_bam(self.path)

    def close(self):
        """Close filehandles and subprocess safely."""
        self.af.close()

    def __enter__(self):
        """Provide context handler entry."""
        self.af = pysam.AlignmentFile(self.path, threads=self.threads - 1)
        return self.af

    def __exit__(self, type, value, traceback):
        """Provide context handler exit."""
        self.close()


import random

import numpy
from pandas import DataFrame

import matplotlib.pyplot as plt
from matplotlib.patches import Circle
from matplotlib import cm
from matplotlib import colors
from mpl_toolkits.axes_grid1 import make_axes_locatable

import cartopy.crs as ccrs
import cartopy.feature as cfeature
from cartopy.io.shapereader import Reader
from cartopy.feature import ShapelyFeature

from collections import namedtuple

from .geometry import midpoint, points_circumference, buffer_convex_hull, build_isogloss

DPI = 1200

class MattangError(Exception):
    pass

class FeatureMap:
    """Visualise linguistic data from a spreadsheet.
    """
    def __init__(self):

        self.projection = None
        self.shapefile = None
        self.extent = None
        self.discrete_markers = []
        self.continuous_markers = []
        self.colourbars = []
        self.contours = []

    def load_data(self, dataframe: DataFrame):
        """Precompute various things needed to plot the supplied languages and their features.
        """
        self.dataframe = dataframe
        self._plotdata = {}
        self.projection = ccrs.PlateCarree()

        for feature in self.dataframe.columns:
            data = self.dataframe[feature]
            ndata = len(data)
            try:
                if self.dataframe.dtypes[feature] in ["float64", "int64"]:
                    dtype = "continuous"
                    dnorm = colors.Normalize(min(data), max(data))
                    dgroups = self.dataframe.groupby(feature)
                    drange = int(ndata / 2)
                else:
                    dtype = "discrete"
                    dnorm = colors.BoundaryNorm([i for i in range(ndata)], ndata)
                    dgroups = self.dataframe.groupby(feature)
                    drange = len(dgroups)

                self._plotdata[feature] = {"type": dtype, "norm": dnorm, "groups": dgroups, "range": drange,}
            except TypeError as error:
                # Some field types can have unhashable stuff that can't be groupby'd, so we skip them
                print("Skipping feature {} due to error \"{}\"".format(feature, error))

    def init_map(self, shapefile="", extent=None, projection=ccrs.PlateCarree()):
        """Set up map figure and axis.
        """
        self.projection = projection
        self.fig, self.axis = plt.subplots(1, 1, subplot_kw=dict(projection=projection))

        if shapefile:
            shape = ShapelyFeature(
                Reader(shapefile).geometries(),
                projection,
                edgecolor="k",
                facecolor="none",
            )
            self.axis.add_feature(shape)
            self.shapefile = shapefile
        else:
            self.axis.coastlines(resolution='50m', color='black', linewidth=1)
        if extent:
            self.axis.set_extent(extent, projection)
            self.extent = extent

    def _draw_pie_marker(self, language, features, colours, size=250):
        """sections: (feattype, proportion)
        """
        colourmaps = []
        for f, c in zip(features, colours):
            colourmaps.append(cm.get_cmap(c, self._plotdata[f]["range"]))

        cumsum = numpy.cumsum([2 for f in features])
        cumsum = cumsum / cumsum[-1]
        pie = [0] + cumsum.tolist()

        slices = []
        for r1, r2, feat, cmap in zip(pie[:-1], pie[1:], features, colourmaps):
            # Compute colour for this feature slice
            norm = self._plotdata[feat]["norm"]
            value = language[feat]
            if self._plotdata[feat]["type"] == "discrete":
                value = self._plotdata[feat]["groups"].get_group(value).index[0]
            colour = cmap(norm(value))

            # Build the pie slice
            angles = numpy.linspace(2 * numpy.pi * r1, 2 * numpy.pi * r2)
            x = [0] + numpy.cos(angles).tolist()
            y = [0] + numpy.sin(angles).tolist()
            xy = numpy.column_stack([x, y])
            marker = {"marker": xy, "facecolor": colour, "s": size, "alpha": 1,}
            slices.append(marker)

            # Save the markers for clearing the map, drawing the legend, etc.
            if self._plotdata[feat]["type"] == "discrete":
                self.discrete_markers.append(marker)
            else:
                self.continuous_markers.append(marker)

        # Draw the slices on the axis
        for slyce in slices:
            self.axis.scatter(
                [language["longitude"]],
                [language["latitude"]],
                **slyce,
                transform=self.projection,
            )

    def _draw_pie_markers(self, features, colours):
        for _, lang in self.dataframe.iterrows():
            self._draw_pie_marker(lang, features, colours)
        return

    def _draw_colourbars(self, features, colours):
        colourmaps = [cm.get_cmap(c) for c in colours]
        divider = make_axes_locatable(self.axis)

        # Need to make dummy mappables for the colourbars since we plotted the markers one by one
        for feat, cmap in zip(features, colourmaps):
            if self._plotdata[feat]["type"] == "continuous":
                mappable = cm.ScalarMappable(cmap=cmap, norm=self._plotdata[feat]["norm"])
                ax_cb = divider.new_horizontal(size="5%", pad=0.5, axes_class=plt.Axes)
                self.fig.add_axes(ax_cb)
                bar = plt.colorbar(mappable, cax=ax_cb)
                bar.set_label(feat)
                self.colourbars.append(bar)
        return

    def _draw_legend(self):
        self.axis.legend(self.discrete_markers)
        return

    def _draw_isogloss(self, feature, colour, style, padding):
        groupby = self._plotdata[feature]["groups"]
        group_xy = zip(
            [series.latitude for group, series in groupby],
            [series.longitude for group, series in groupby],
        )
        group_points = [list(zip(lons, lats)) for lats, lons in group_xy]
        for points in group_points:
            filtered = [p for p in points if True not in numpy.isnan(p)]
            print("points:", points, "filtered:", filtered)
            if filtered:
                isogloss = build_isogloss(filtered, padding=padding)
                for point in isogloss:
                    x, y = point
                    self.axis.plot(x, y, "".join([colour, style]), transform=self.projection)

    def _draw_isoglosses(self, features, colour="k", style="-", padding=.1):
        for feature in features:
            self._draw_isogloss(feature, colour, style, padding)
        return

    def draw(self, filename, features, colours=[], isoglosses=[]):
        # Clear the map
        self.init_map(self.shapefile, self.extent, self.projection)

        # Check user input
        if not features:
            raise MattangError("Must specify at least one feature to plot!")

        for f in features:
            if f not in self.dataframe.columns:
                raise MattangError("{} is not a valid feature".format(f))

        if not colours:
            # Use randomly selected colourmaps
            allcolours = plt.colormaps()
            colours = [random.choice(allcolours) for f in features]

        if len(features) != len(colours):
            raise MattangError("Feature and colour list lengths must match")

        # Build and show the map
        self._draw_pie_markers(features, colours)
        if isoglosses:
            self._draw_isoglosses(isoglosses)
        #self._draw_legend()
        self._draw_colourbars(features, colours)
        self.fig.set_dpi(DPI)
        self.fig.tight_layout()
        plt.savefig(filename)


import cv2
import numpy as np
from PIL import Image
import database as dbs
from pytesseract import pytesseract

def tesseract():
    path_to_tesseract = r'D:\Programs\Tesseract\tesseract.exe'
    image_path = 'test.jpg'
    pytesseract.tesseract_cmd = path_to_tesseract
    text = pytesseract.image_to_string(Image.open(image_path))
    print(text[:-1])

camera = cv2.VideoCapture(0, cv2.CAP_DSHOW)
amarilloBajo = np.array([15,100,20],np.uint8)
amarilloAlto = np.array([45,255,255],np.uint8)

while True:
    #  _,image = camera.read()
     ret, frame = camera.read()

     if ret == True:
         frameHSV = cv2.cvtColor(frame, cv2.COLOR_BGR2HSV)
         mask = cv2.inRange(frameHSV, amarilloBajo,amarilloAlto)

        # hsv = cv2.cvtColor(image, cv2.COLOR_BGR2HSV)
        # # Threshold of blue in HSV space
        # lower_blue = np.array([60, 35, 140])
        # upper_blue = np.array([180, 255, 255])
        # mask = cv2.inRange(hsv, lower_blue, upper_blue)

         cv2.imshow('frame',frame)
         res = cv2.bitwise_and(frame, frame, mask=mask)
         cv2.imshow("res", res)
         gray = cv2.cvtColor(res, cv2.COLOR_BGR2GRAY)

         if cv2.waitKey(1)& 0xFF==ord('s'):
            cv2.imwrite('test.jpg',cv2.blur(gray,(3,3)))
            break

camera.release()
cv2.destroyAllWindows()
tesseract()


import json
import uvicorn
from fastapi import FastAPI
import logging
from logging.config import dictConfig
from pydantic import BaseModel
import pandas as pd
import pickle
from sklearn.neighbors import NearestNeighbors
from sentence_transformers import SentenceTransformer
import math

class LogConfig(BaseModel):
    """Logging configuration to be set for the server"""

    LOGGER_NAME: str = "api"
    LOG_FORMAT: str = "%(levelprefix)s | %(asctime)s | %(message)s"
    LOG_LEVEL: str = "DEBUG"

    # Logging config
    version = 1
    disable_existing_loggers = False
    formatters = {
        "default": {
            "()": "uvicorn.logging.DefaultFormatter",
            "fmt": LOG_FORMAT,
            "datefmt": "%Y-%m-%d %H:%M:%S",
        },
    }
    handlers = {
        "default": {
            "formatter": "default",
            "class": "logging.StreamHandler",
            "stream": "ext://sys.stderr",
        },
    }
    loggers = {
        LOGGER_NAME: {"handlers": ["default"], "level": LOG_LEVEL},
    }

dictConfig(LogConfig().dict())
logger = logging.getLogger("api")

app = FastAPI()
df = pd.read_csv(open("movie_data.csv"))
embeddings = pickle.load(open("movie_embeddings_simple.pickle", "rb"))
nbrs = NearestNeighbors(n_neighbors=20, algorithm="ball_tree").fit(embeddings)
embedder = SentenceTransformer('all-mpnet-base-v2')

def movie_series_to_dict(series: pd.Series) -> dict:
    out = series.to_dict()
    out = {k: None if not isinstance(v, str) and math.isnan(v) else v for k, v in out.items()}
    for k in ["genres", "keywords", "countries"]:
        out[k] = json.loads(out[k])
    return out

@app.get("/")
async def root(sentence: str):
    sentence_embedding = embedder.encode(sentence, convert_to_tensor=True)
    dists, indices = nbrs.kneighbors(sentence_embedding.unsqueeze(0))
    movie_output = [
        movie_series_to_dict(df.loc[i])
        for _, i in zip(dists[0], indices[0])
    ]

    return movie_output

if __name__ == "__main__":
    uvicorn.run(app, host="0.0.0.0", port=8000)


from PIL import Image, ImageDraw
import sys
s = sys.argv[1]

img = Image.open(s)
width, height = img.size
white = (255, 255, 255)
image = Image.new("RGB", (width, height), white)
draw = ImageDraw.Draw(image)

for y in range(height):
    for x in range(width):
        rgb = img.getpixel((x,y))
        RGB = []

        for i in range(len(rgb)):
        		RGB.append(int(rgb[i] / 2) )

        color = (RGB[0],RGB[1],RGB[2])
        draw.point((x,y), color)

image.save("Q2.png")