import csv
import io
import json
import logging
import os
import shutil
import subprocess
import tempfile
import zipfile
from collections import defaultdict
from concurrent.futures import ThreadPoolExecutor, as_completed
from copy import deepcopy
from datetime import timedelta, datetime
from typing import List, Tuple

import click
import numpy as np
from tabulate import tabulate
from termcolor import colored

import training_speech
from training_speech import utils, ffmpeg, sox, exceptions, vad

CURRENT_DIR = os.path.dirname(__file__)

logger = logging.getLogger()

@click.group()
def cli():
    pass

@cli.command()
@click.argument('source_name')
@click.option('-y', '--yes', is_flag=True, default=False, help='override existing transcript if any')
@click.option('--add-to-git/--no-add-to-git', is_flag=True, default=True)
def build_transcript(source_name, yes, add_to_git):
    source = training_speech.get_source(source_name)
    path_to_epub = os.path.join(CURRENT_DIR, 'data/epubs/', source['ebook'])

    path_to_transcript = os.path.join(CURRENT_DIR, f'data/transcripts/{source_name}.txt')
    if yes is False and os.path.isfile(path_to_transcript):
        click.confirm(text=f'{path_to_transcript} already exists. Override ?', default=False, abort=True)

    with open(path_to_transcript, 'w') as f:
        f.writelines(utils.read_epub(path_to_epub, path_to_xhtmls=source.get('ebook_parts', ['part1.xhtml'])))

    if add_to_git:
        subprocess.call(f'git add {path_to_transcript}'.split(' '))
    click.echo(f'transcript {path_to_transcript} added to git')

audio_player = None

def cut_fragment_audio(fragment: dict, input_file: str, output_dir: str=utils.CACHE_DIR, salt: str=None, force=False):
    if not os.path.isdir(output_dir):
        os.mkdir(output_dir)

    fragment_hash = utils.get_fragment_hash(fragment, salt=salt)
    path_to_fragment_audio = os.path.join(output_dir, f'{fragment_hash}.wav')
    if force or not os.path.isfile(path_to_fragment_audio):
        ffmpeg.cut(input_file, path_to_fragment_audio, from_=fragment['begin'], to=fragment['end'])
    return path_to_fragment_audio

def cut_fragments_audio(fragments: List[dict], input_file: str, output_dir: str=utils.CACHE_DIR):
    # generate fragments
    with click.progressbar(length=len(fragments), show_eta=True, label='cut audio into fragments') as bar:
        def _cut(f: dict):
            p = cut_fragment_audio(f, input_file, output_dir)
            bar.update(1)
            return p

        with ThreadPoolExecutor() as executor:
            return executor.map(_cut, fragments)

@cli.command()
@click.argument('source_name')
@click.option('-r', '--restart', is_flag=True, default=False, help='restart validation from scratch')
@click.option('-s', '--speed', default=1.3, help='set audio speed')
@click.option('-ar', '--audio-rate', default=16000)
@click.option('-nc', '--no-cache', is_flag=True, default=None)
@click.option('-f', '--fast', is_flag=True, default=False)
@click.option('--start', type=int, default=0)
def check_alignment(source_name, restart, speed, audio_rate, no_cache, fast, start):
    import inquirer
    source = training_speech.get_source(source_name)
    path_to_alignment = os.path.join(CURRENT_DIR, f'data/alignments/{source_name}.json')
    path_to_transcript = os.path.join(CURRENT_DIR, f'data/transcripts/{source_name}.txt')
    path_to_mp3 = os.path.join(CURRENT_DIR, 'data/mp3', source['audio'])

    if no_cache and os.path.isdir(utils.CACHE_DIR):
        shutil.rmtree(utils.CACHE_DIR)
        os.mkdir(utils.CACHE_DIR)

    # generate wav if do not exists yet
    with open(path_to_mp3, 'rb') as f:
        file_hash = utils.hash_file(f)
    path_to_wav = os.path.join(utils.CACHE_DIR, f'{file_hash}.wav')
    if not os.path.exists(path_to_wav):
        ffmpeg.convert(
            from_=path_to_mp3,
            to=os.path.join(utils.CACHE_DIR, f'{file_hash}.wav'),
            rate=audio_rate,
            channels=1
        )

    # retrieve transcript
    with open(path_to_transcript) as f:
        transcript = [l.strip() for l in f.readlines()]
    transcript = [l for l in transcript if l]  # rm empty lines

    # detect silences through VAD
    silences = vad.list_silences(path_to_wav=path_to_wav, frame_duration=20)

    if not restart and os.path.isfile(path_to_alignment):
        with open(path_to_alignment) as f:
            existing_alignment = json.load(f)
    else:
        existing_alignment = []

    alignment = utils.build_alignment(
        transcript=transcript,
        path_to_audio=path_to_wav,
        existing_alignment=existing_alignment,
        silences=silences,
        generate_labels=True,
    )

    def _check_alignment(index: int, alignment: List[dict]):
        click.clear()
        fragment = alignment[index]
        prev_fragments = alignment[max(i - 1, 0):i]
        next_fragments = alignment[i + 1:i + 3]

        print(colored(
            f'\nplaying #{i + 1:03d}: @@ {timedelta(seconds=fragment["begin"])}  {timedelta(seconds=fragment["end"])} ({fragment["end"] - fragment["begin"]:0.3f}) @@',  # noqa
            'yellow',
            attrs=['bold']
        ))
        if prev_fragments:
            for prev_ in prev_fragments:
                print(colored(prev_['text'], 'grey'))
        print(colored(fragment['text'], 'magenta' if fragment.get('warn') else 'green', attrs=['bold']))
        if next_fragments:
            for next_ in next_fragments:
                print(colored(next_['text'], 'grey'))

        todo = set()
        pool = ThreadPoolExecutor()

        def play_audio():
            path_to_audio = cut_fragment_audio(fragment, path_to_wav)
            global audio_player

            with sox.play(path_to_audio, speed=speed) as player:
                audio_player = player

        def play_audio_slow():
            path_to_audio = cut_fragment_audio(fragment, path_to_wav)
            global audio_player

            with sox.play(path_to_audio) as player:
                audio_player = player

        todo.add(pool.submit(play_audio))

        def ask_right_transcript(current: List[str]):
            new_text = click.edit(text='\n'.join(current), require_save=False)
            return [
                l.strip()
                for l in new_text.strip().split('\n')
                if l.strip()
            ]

        def ask_what_next():
            if prev_fragments:
                silence_before, silence_between, silence_after = utils.transition_silences(
                    prev_fragments[-1],
                    fragment,
                    silences
                )
                can_cut_start_on_prev_silence = silence_before is not None
                can_cut_start_on_next_silence = silence_after is not None
            else:
                can_cut_start_on_prev_silence = can_cut_start_on_next_silence = False
            if next_fragments:
                silence_before, silence_between, silence_after = utils.transition_silences(
                    fragment,
                    next_fragments[0],
                    silences
                )
                can_cut_end_on_prev_silence = silence_before is not None
                can_cut_end_on_next_silence = silence_after is not None
            else:
                can_cut_end_on_prev_silence = can_cut_end_on_next_silence = False

            try:
                next_: str = inquirer.prompt([
                    inquirer.List(
                        'next',
                        message="\nWhat should I do ?",
                        choices=(
                                ['approve', 'repeat'] +
                                (['go_back'] if prev_fragments else []) +
                                ['edit'] +
                                (['wrong_start__cut_on_previous_silence'] if can_cut_start_on_prev_silence else []) +
                                (['wrong_start__cut_on_next_silence'] if can_cut_start_on_next_silence else []) +
                                (['wrong_end__cut_on_previous_silence'] if can_cut_end_on_prev_silence else []) +
                                (['wrong_end__cut_on_next_silence'] if can_cut_end_on_next_silence else []) +
                                (['enable'] if fragment.get('disabled') else ['disable']) +
                                ['toggle_fast_mode','quit']),
                    ),
                ])['next']
            except TypeError:
                raise exceptions.QuitException
            except Exception:
                next_ = 'quit'

            global audio_player
            try:
                audio_player.kill()
            except:
                pass

            if next_ == 'repeat':
                todo.add(pool.submit(play_audio_slow))
                todo.add(pool.submit(ask_what_next))
            elif next_ == 'toggle_fast_mode':
                raise exceptions.ToggleFastModeException
            elif next_ == 'go_back':
                prev_fragments[-1].pop('disabled', None)
                prev_fragments[-1].pop('approved', None)
                raise exceptions.GoBackException
            elif next_ == 'edit':
                new_transcript = ask_right_transcript([t['text'] for t in prev_fragments + [fragment] + next_fragments])
                raise exceptions.SplitException(
                    start=i-len(prev_fragments),
                    end=i+len(next_fragments),
                    new_transcript=new_transcript,
                )

            elif next_ == 'wrong_start__cut_on_previous_silence':
                prev_fragment = prev_fragments[-1]
                silence_before, _, _ = utils.transition_silences(prev_fragment, fragment, silences)
                fragment['begin'] = round(max(silence_before[1] - 0.35, silence_before[1]), 3)
                prev_fragment['end'] = round(min(silence_before[0] + 0.35, silence_before[1]), 3)
                cut_fragment_audio(fragment, input_file=path_to_wav)
                cut_fragment_audio(fragment, input_file=path_to_wav)
                todo.add(pool.submit(play_audio))
                todo.add(pool.submit(ask_what_next))
            elif next_ == 'wrong_start__cut_on_next_silence':
                prev_fragment = prev_fragments[-1]
                _, _, silence_after = utils.transition_silences(prev_fragment, fragment, silences)
                prev_fragment['end'] = round(min(silence_after[0] + 0.35, silence_after[1]), 3)
                fragment['begin'] = round(max(silence_after[1] - 0.35, silence_after[0]), 3)
                cut_fragment_audio(prev_fragment, input_file=path_to_wav)
                cut_fragment_audio(fragment, input_file=path_to_wav)
                todo.add(pool.submit(play_audio))
                todo.add(pool.submit(ask_what_next))
            elif next_ == 'wrong_end__cut_on_previous_silence':
                next_fragment = next_fragments[0]
                silence_before, _, _ = utils.transition_silences(fragment, next_fragment, silences)
                fragment['end'] = round(min(silence_before[0] + 0.35, silence_before[1]), 3)
                next_fragment['begin'] = round(max(silence_before[1] - 0.35, silence_before[1]), 3)
                cut_fragment_audio(fragment, input_file=path_to_wav)
                cut_fragment_audio(next_fragment, input_file=path_to_wav)
                todo.add(pool.submit(play_audio))
                todo.add(pool.submit(ask_what_next))
            elif next_ == 'wrong_end__cut_on_next_silence':
                next_fragment = next_fragments[0]
                _, _, silence_after = utils.transition_silences(fragment, next_fragment, silences)
                fragment['end'] = round(min(silence_after[0] + 0.35, silence_after[1]), 3)
                next_fragment['begin'] = round(max(silence_after[1] - 0.35, silence_after[0]), 3)
                cut_fragment_audio(fragment, input_file=path_to_wav)
                cut_fragment_audio(next_fragment, input_file=path_to_wav)
                todo.add(pool.submit(play_audio))
                todo.add(pool.submit(ask_what_next))
            elif next_ == 'approve':
                fragment['approved'] = True
            elif next_ == 'pass':
                fragment.pop('approved', None)
            elif next_ == 'disable':
                fragment['disabled'] = True
                fragment.pop('approved', None)
            elif next_ == 'enabled':
                fragment['approved'] = True
                fragment.pop('disabled', None)
            elif next_ == 'quit':
                raise exceptions.QuitException
            else:
                raise NotImplementedError

        todo.add(pool.submit(ask_what_next))

        while todo:
            for future in as_completed(t for t in todo):
                todo.remove(future)
                future.result()

        pool.shutdown(wait=True)

    cut_fragments_audio(alignment, input_file=path_to_wav)

    # iterate over successive fragments
    start = start - 1
    i = start
    done = False
    while i < len(alignment) and not done:
        fragment = alignment[i]

        if start != i and (fragment.get('approved') or fragment.get('disabled')):
            click.echo(f'skip fragment#{i} {fragment["text"]}')
            i += 1
            continue

        if fast and i != 0 and i < len(alignment) - 1 and not fragment.get('warn'):
            fragment.update(
                approved=True,
                approved_auto=True,
            )
            click.echo(f'approve fragment#{i} {fragment["text"]}')
            i += 1
            continue

        try:
            _check_alignment(index=i, alignment=alignment)
        except exceptions.ToggleFastModeException:
            fast = not fast
            continue
        except exceptions.GoBackException:
            i -= 1
            continue
        except exceptions.QuitException:
            try:
                audio_player.kill()
            except:
                pass
            exit(1)
        except exceptions.SplitException as e:
            fragment.pop('approved', None)
            fragment.pop('disabled', None)
            audio_start: float = alignment[e.start]['begin']
            audio_end: float = alignment[e.end]['end']

            with tempfile.NamedTemporaryFile(suffix='.wav') as file_:
                sox.trim(path_to_wav, file_.name, from_=audio_start, to=audio_end)

                sub_alignment = utils.build_alignment(
                    transcript=e.new_transcript,
                    path_to_audio=file_.name,
                    existing_alignment=[
                        dict(
                            text=f['text'],
                            begin=f['begin'] - audio_start,
                            end=f['end'] - audio_start,
                            approved=f.get('approved', False),
                            disabled=f.get('disabled', False),
                        )
                        for f in alignment[e.start:e.end+1]
                    ],
                    silences=[
                        [max(s_start - audio_start, 0.), s_end - audio_start]
                        for s_start, s_end in silences
                        if s_end > audio_start and s_start < audio_end
                    ],
                    generate_labels=False,
                    language=source['language'],
                )

            for nf in sub_alignment:
                nf["begin"] += audio_start
                nf["end"] += audio_start

            alignment = (
                    alignment[:e.start] +
                    sub_alignment +
                    alignment[e.end+1:]
            )
            cut_fragments_audio(alignment, input_file=path_to_wav)
            i -= e.start

        # save progress
        with open(path_to_alignment, 'w') as dest:
            to_save = deepcopy(alignment)
            for f in to_save:
                f.pop('warn', None)
            json.dump(
                obj=to_save,
                fp=dest,
                sort_keys=True,
                indent=2,
            )
        with open(path_to_transcript, 'w') as f:
            f.writelines('\n'.join(f['text'] for f in alignment) + '\n')

        i += 1

MAPPINGS = [
    ('s3://audiocorp/epubs/', os.path.join(CURRENT_DIR, 'data/epubs/'), 'ebook'),  # epubs
    ('s3://audiocorp/mp3/', os.path.join(CURRENT_DIR, 'data/mp3/'), 'audio'),  # mp3
    ('s3://audiocorp/releases/', os.path.join(CURRENT_DIR, 'data/releases/'), 'releases'),
]

@cli.command()
@click.option('-s', '--source_name', default=None)
def download(source_name):
    for s3, local, key in MAPPINGS:
        if key in {'releases'}:
            continue
        local = os.path.abspath(local)
        options = ''
        if source_name:
            source = training_speech.get_source(source_name, validate=False)
            options += f' --exclude * --include {source[key]}'

        sync_cmd = f'aws s3 sync {s3} {local}{options}'
        print(sync_cmd)
        subprocess.call(sync_cmd.strip().split(' '))

@cli.command()
@click.option('-s', '--source_name', default=None)
@click.option('-r', '--releases', is_flag=True, default=False, help='releases only')
def upload(source_name, releases):
    for s3, local, key in MAPPINGS:
        local = os.path.abspath(local)
        options = ' --exclude .gitkeep'
        if (source_name and key in {'releases'}) or (releases and key != 'releases'):
            continue
        if key == 'releases':
            options += ' --acl public-read'
        else:
            options += ' --exclude *.zip'
        if source_name:
            source = training_speech.get_source(source_name)
            options += f' --exclude * --include {source[key]}'

        sync_cmd = f'aws s3 sync {local} {s3}{options}'
        print(sync_cmd)
        subprocess.call(sync_cmd.strip().split(' '))

@cli.command()
@click.option('-f', '--full', is_flag=True, default=False, help='display additionnal info')
def stats(full):
    sources = training_speech.sources()
    sources_data = []
    total_dur = timedelta(seconds=0)
    total_available = timedelta(seconds=0)
    total_count = 0
    per_language_count = defaultdict(float)
    per_language_dur = defaultdict(timedelta)
    per_language_available = defaultdict(timedelta)
    for name, metadata in sources.items():
        info = training_speech.source_info(name)
        path_to_mp3 = os.path.join(CURRENT_DIR, 'data/mp3', metadata['audio'])
        if full and os.path.isfile(path_to_mp3):
            mp3_duration = timedelta(seconds=ffmpeg.audio_duration(path_to_mp3))
        else:
            mp3_duration = None
        sources_data.append([
            name,
            info['status'],
            f'{int(info["progress"] * 100)} %',
            info['approved_count'],
            utils.format_timedelta(info['approved_duration']),
            utils.format_timedelta(mp3_duration) if mp3_duration else '?',
            metadata['language'],
        ])
        total_dur += info['approved_duration']
        total_count += info['approved_count']
        per_language_count[metadata['language']] += info['approved_count']
        per_language_dur[metadata['language']] += info['approved_duration']
        if mp3_duration:
            per_language_available[metadata['language']] += mp3_duration
            total_available += mp3_duration

    sources_data.append([])
    sources_data.append([
        'TOTAL',
        ''
        '',
        '',
        total_count,
        utils.format_timedelta(total_dur),
        utils.format_timedelta(total_available),
    ])
    for language, count in per_language_count.items():
        sources_data.append([
            f'TOTAL {language}',
            ''
            '',
            '',
            count,
            utils.format_timedelta(per_language_dur[language]),
            utils.format_timedelta(per_language_available[language]),
            language,
        ])
    print('\n' + tabulate(
        sources_data,
        headers=['Source', 'Status', 'Progress', '# speeches', 'Speeches duration', 'mp3 duration', 'Language'],
        tablefmt='pipe',
    ))

@cli.command()
@click.option('-r', '--audio-rate', default=16000)
@click.option('-l', '--language', type=click.Choice(['fr_FR']), default=None)
def release(audio_rate, language):
    per_language_sources = defaultdict(list)
    per_language_speakers = defaultdict(set)
    for name, metadata in training_speech.sources().items():
        info = training_speech.source_info(name)
        if info['status'] in {'DONE', 'WIP'}:
            per_language_sources[metadata['language']].append((name, metadata, info))
            per_language_speakers[metadata['language']].add(metadata['speaker'])

    today_str = datetime.now().isoformat()[:10]
    releases_data = []

    for source_language, sources in per_language_sources.items():
        if language and source_language != language:
            continue

        release_name = f'{today_str}_{source_language}'
        path_to_release = os.path.join(CURRENT_DIR, 'data/releases', f'{release_name}.zip')
        print(f'start building {release_name}')
        # generate fragments
        p_label = f'convert mp3 files to mono 16bits {audio_rate}Hz wav'
        with click.progressbar(length=len(sources), show_eta=True, label=p_label) as bar:
            with ThreadPoolExecutor() as executor:
                def _process_source(source_data: Tuple[str, dict, dict]):
                    source_name, metadata, _ = source_data
                    try:
                        path_to_alignment = os.path.join(CURRENT_DIR, f'data/alignments/{source_name}.json')
                        path_to_mp3 = os.path.join(CURRENT_DIR, 'data/mp3', metadata['audio'])
                        # generate wav if do not exists yet
                        with open(path_to_mp3, 'rb') as f:
                            file_hash = utils.hash_file(f)
                        path_to_wav = os.path.join(utils.CACHE_DIR, f'{file_hash}.wav')
                        if not os.path.exists(path_to_wav):
                            ffmpeg.convert(from_=path_to_mp3, to=path_to_wav, rate=audio_rate, channels=1)

                        with open(path_to_alignment) as file_:
                            source_fragments = [
                                dict(name=f'{source_name}_{i + 1:04d}', source_file=path_to_wav, **f)
                                for i, f in enumerate(json.load(file_))
                                if f.get('approved')
                            ]
                        bar.update(1)
                        return source_fragments
                    except Exception as e:
                        print(f'cannot process source {source_name}. {e}')
                        raise e

                fragments = []
                for source_fragments in executor.map(_process_source, sources):
                    # skip empty speeches and longer than 15s
                    fragments += [f for f in source_fragments if 0.1 <= f['end'] - f['begin'] <= 15]

        # generate fragments
        p_label = f'cut audio fragments'
        with click.progressbar(length=len(fragments), show_eta=True, label=p_label) as bar:
            with ThreadPoolExecutor() as executor:
                def _cut(f: dict):
                    try:
                        p = cut_fragment_audio(f, f['source_file'], salt=f['name'])
                        bar.update(1)
                        return p
                    except Exception as e:
                        print(f'cannot extract fragment {fragment["name"]}. {e}')

                audio_fragments_pathes = executor.map(_cut, fragments)

        p_label = f'generate {release_name}.zip file'
        with click.progressbar(length=len(fragments) + 1, show_eta=True, label=p_label) as bar:
            with zipfile.ZipFile(path_to_release, 'w') as zip_file:
                # create CSV
                string_buffer = io.StringIO()
                writer = csv.DictWriter(string_buffer, delimiter=',', fieldnames=['path', 'duration', 'text'])
                writer.writeheader()

                for fragment, tmp_audio in zip(fragments, audio_fragments_pathes):
                    archive_audio_path = f'{fragment["name"]}.wav'
                    zip_file.write(tmp_audio, arcname=archive_audio_path, compress_type=zipfile.ZIP_DEFLATED)
                    writer.writerow(dict(
                        path=archive_audio_path,
                        duration=round(fragment['end'] - fragment['begin'], 3),
                        text=fragment['text']
                    ))
                    bar.update(1)
                    os.unlink(tmp_audio)

                zip_file.writestr('data.csv', string_buffer.getvalue())
            bar.update(1)

        releases_data.append([
            f'[{release_name}](https://s3.eu-west-3.amazonaws.com/audiocorp/releases/{release_name}.zip)',
            len(fragments),
            len(per_language_speakers[source_language]),
            utils.format_timedelta(timedelta(seconds=round(sum(round(f['end'] - f['begin'], 3) for f in fragments)))),
            source_language,
        ])

    print('\n' + tabulate(
        releases_data,
        headers=['Name', '# speeches', '# speakers', 'Total Duration', 'Language'],
        tablefmt='pipe',
    ))

@cli.command()
@click.argument('source_name')
@click.argument('from_id', type=int)
@click.argument('to_id', type=int)
@click.option('-ar', '--audio-rate', default=16000)
def make_test(source_name, from_id, to_id, audio_rate):
    source = training_speech.get_source(source_name)
    path_to_alignment = os.path.join(CURRENT_DIR, f'data/alignments/{source_name}.json')
    path_to_transcript = os.path.join(CURRENT_DIR, f'data/transcripts/{source_name}.txt')
    path_to_mp3 = os.path.join(CURRENT_DIR, 'data/mp3', source['audio'])

    # generate wav if do not exists yet
    with open(path_to_mp3, 'rb') as f:
        file_hash = utils.hash_file(f)
    path_to_wav = os.path.join(utils.CACHE_DIR, f'{file_hash}.wav')
    if not os.path.exists(path_to_wav):
        ffmpeg.convert(
            from_=path_to_mp3,
            to=os.path.join(utils.CACHE_DIR, f'{file_hash}.wav'),
            rate=audio_rate,
            channels=1
        )

    # retrieve transcript
    with open(path_to_transcript) as f:
        transcript = [l.strip() for l in f.readlines()]
    transcript = [l for l in transcript if l]  # rm empty lines

    if os.path.isfile(path_to_alignment):
        with open(path_to_alignment) as f:
            existing_alignment = json.load(f)
    else:
        existing_alignment = []

    # detect silences
    silences = vad.list_silences(path_to_wav=path_to_wav, frame_duration=20)

    alignment = utils.build_alignment(
        transcript=transcript,
        path_to_audio=path_to_wav,
        existing_alignment=existing_alignment,
        silences=silences,
        generate_labels=True,
    )
    remaining = alignment[from_id - 1:to_id]

    with tempfile.NamedTemporaryFile(suffix='.wav') as file_:
        ffmpeg.cut(path_to_wav, file_.name, from_=remaining[0]['begin'], to=remaining[-1]['end'])
        with open(file_.name, 'rb') as f:
            file_hash = utils.hash_file(f)[:8]
        path_to_sub_audio = f'tests/assets/{file_hash}.wav'
        shutil.copy(file_.name, os.path.join(CURRENT_DIR, path_to_sub_audio))

    new_alignment = utils.build_alignment(
        transcript=[f['text'] for f in remaining],
        path_to_audio=path_to_sub_audio,
        existing_alignment=[],
        silences=vad.list_silences(path_to_wav=path_to_sub_audio, frame_duration=20),
        generate_labels=True,
    )

    print(BUILD_ALIGNMENT_TEST_TEMPLATE.format(
        file_hash=file_hash,
        source_name=source_name,
        from_=timedelta(seconds=remaining[0]['begin']),
        to=timedelta(seconds=remaining[-1]['end']),
        transcript='\n        '.join(f"'{f['text']}'," for f in new_alignment),
        alignment='\n        '.join("dict(begin={begin}, end={end}, text='{text}'),".format(**f) for f in new_alignment),
        vad_mode=utils.DEFAULT_VAD_MODE,
        vad_frame_duration=utils.DEFAULT_VAD_FRAME_DURATION,
    ))

BUILD_ALIGNMENT_TEST_TEMPLATE = """
    # {source_name} @@ {from_} {to} @@
    ('{file_hash}.wav', {vad_mode}, {vad_frame_duration}, [
        {transcript}
    ], [], [
        {alignment}
    ]),
"""

@cli.command()
@click.argument('source_name')
def source_stats(source_name):
    source = training_speech.get_source(source_name)
    path_to_alignment = os.path.join(CURRENT_DIR, f'data/alignments/{source_name}.json')
    path_to_transcript = os.path.join(CURRENT_DIR, f'data/transcripts/{source_name}.txt')
    path_to_mp3 = os.path.join(CURRENT_DIR, 'data/mp3', source['audio'])

    # generate wav if do not exists yet
    with open(path_to_mp3, 'rb') as f:
        file_hash = utils.hash_file(f)
    path_to_wav = os.path.join(utils.CACHE_DIR, f'{file_hash}.wav')
    if not os.path.exists(path_to_wav):
        ffmpeg.convert(
            from_=path_to_mp3,
            to=os.path.join(utils.CACHE_DIR, f'{file_hash}.wav'),
            rate=16000,
            channels=1
        )

    # retrieve transcript
    with open(path_to_transcript) as f:
        transcript = [l.strip() for l in f.readlines()]
    transcript = [l for l in transcript if l]  # rm empty lines

    if os.path.isfile(path_to_alignment):
        with open(path_to_alignment) as f:
            existing_alignment = json.load(f)
    else:
        existing_alignment = []

    # detect silences
    silences = vad.list_silences(path_to_wav=path_to_wav, frame_duration=utils.DEFAULT_VAD_FRAME_DURATION)

    alignment = utils.build_alignment(
        transcript=transcript,
        path_to_audio=path_to_wav,
        existing_alignment=existing_alignment,
        silences=silences,
        generate_labels=True,
    )

    transitions_durations = []
    fragments_durations = []
    for prev_fragment, next_fragment in zip(alignment[:-1], alignment[1:]):
        silence_before, silence_between, silence_after = \
            utils.transition_silences(prev_fragment, next_fragment, silences)
        if silence_between:
            transitions_durations.append(silence_between[1] - silence_between[0])
        fragments_durations.append(next_fragment['end'] - next_fragment['begin'])
    t_mean = np.array(transitions_durations).mean()
    t_std = np.array(transitions_durations).std()
    f_mean = np.array(fragments_durations).mean()
    f_std = np.array(fragments_durations).std()
    print('\n' + tabulate(
        [
            [
                'transition dur (s)',
                len(transitions_durations),
                round(t_mean, 3),
                round(t_std, 3),
                f'{round(t_mean - t_std, 3)} - {round(t_mean + t_std, 3)}',
                f'{round(t_mean - 2 * t_std, 3)} - {round(t_mean + 2 * t_std, 3)}',
                f'{max(0, round(t_mean - 3 * t_std, 3))} - {round(t_mean + 3 * t_std, 3)}',
                min(transitions_durations),
                max(transitions_durations),
            ],
            [
                'fragment dur (s)',
                len(fragments_durations),
                round(f_mean, 3),
                round(f_std, 3),
                f'{round(f_mean - f_std, 3)} - {round(f_mean + f_std, 3)}',
                f'{round(f_mean - 2 * f_std, 3)} - {round(f_mean + 2 * f_std, 3)}',
                f'{max(0, round(f_mean - 3 * f_std, 3))} - {round(f_mean + 3 * f_std, 3)}',
                min(fragments_durations),
                max(fragments_durations),
            ],
        ],
        headers=['Metric', 'count', 'avg', 'std', '70%', '95%', '95%', 'min', 'max'],
        tablefmt='pipe',
    ))

if __name__ == '__main__':
    cli()


from collections import OrderedDict
import logging
from typing import Sequence

import numpy as np
import scipy.signal
from scipy.stats import norm

from merlion.transform.base import TransformBase, InvertibleTransformBase
from merlion.utils import UnivariateTimeSeries, TimeSeries

logger = logging.getLogger(__name__)

class MovingAverage(InvertibleTransformBase):
    """
    Computes the n_steps-step moving average of the time series, with
    the given relative weights assigned to each time in the moving average
    (default is to take the non-weighted average). Zero-pads the input time
    series to the left before taking the moving average.
    """

    def __init__(self, n_steps: int = None, weights: Sequence[float] = None):
        super().__init__()
        assert (
            n_steps is not None and n_steps > 0
        ) or weights is not None, "Must specify at least one of n_steps or weights for MovingAverage"
        if weights is None:
            weights = np.ones(n_steps) / n_steps
        elif n_steps is None:
            n_steps = len(weights)
        else:
            assert len(weights) == n_steps
        assert len(weights) >= 2, "Must compute a moving average with a window size of at least 2"
        self.n_steps = n_steps
        self.weights = weights

    @property
    def requires_inversion_state(self):
        return False

    @property
    def _n_pad(self):
        return len(self.weights) - 1

    def train(self, time_series: TimeSeries):
        pass

    def __call__(self, time_series: TimeSeries) -> TimeSeries:
        new_vars = OrderedDict()
        for name, var in time_series.items():
            x = np.concatenate((np.full(self._n_pad, var.np_values[0]), var.np_values))
            y = scipy.signal.correlate(x, self.weights, mode="full")[self._n_pad : -self._n_pad]
            new_vars[name] = UnivariateTimeSeries(var.index, y)

        ret = TimeSeries(new_vars, check_aligned=False)
        ret._is_aligned = time_series.is_aligned
        return ret

    def _invert(self, time_series: TimeSeries) -> TimeSeries:
        new_vars = OrderedDict()
        for name, var in time_series.items():
            left_pad = np.cumsum(self.weights[-1:0:-1]) * var.np_values[0]
            right_pad = np.full(self._n_pad, var.np_values[-1])
            y = np.concatenate((left_pad, var.np_values, right_pad))
            x = scipy.signal.deconvolve(y, self.weights[-1::-1])[0][self._n_pad :]
            new_vars[name] = UnivariateTimeSeries(var.index, x)

        ret = TimeSeries(new_vars, check_aligned=False)
        ret._is_aligned = time_series.is_aligned
        return ret

class MovingPercentile(TransformBase):
    """
    Computes the n-step moving percentile of the time series.
    For datapoints at the start of the time series which are preceded by
    fewer than ``n_steps`` datapoints, the percentile is computed using only the
    available datapoints.
    """

    def __init__(self, n_steps: int, q: float):
        """
        :param q: The percentile to use. Between 0 and 100 inclusive.
        :param n_steps: The number of steps to use.
        """
        super().__init__()
        assert 0 <= q <= 100
        assert 1 <= n_steps
        self.n_steps = int(n_steps)
        self.q = q

    def train(self, time_series: TimeSeries):
        pass

    def __call__(self, time_series: TimeSeries) -> TimeSeries:
        new_vars = OrderedDict()
        for name, var in time_series.items():
            x = var.np_values
            new_x = []
            for i in range(len(x)):
                window = x[max(0, i - self.n_steps + 1) : i + 1]
                new_x.append(np.percentile(window, self.q))
            new_vars[name] = UnivariateTimeSeries(var.index, new_x)
        ret = TimeSeries(new_vars, check_aligned=False)
        ret._is_aligned = time_series.is_aligned
        return ret

class ExponentialMovingAverage(InvertibleTransformBase):
    r"""
    Computes the exponential moving average (normalized or un-normalized) of the
    time series, with smoothing factor alpha (lower alpha = more smoothing).
    alpha must be between 0 and 1.

    The unnormalized moving average ``y`` of ``x`` is computed as

    .. math::
        \begin{align*}
        y_0 & = x_0 \\
        y_i & = (1 - \alpha) \cdot y_{i-1} + \alpha \cdot x_i
        \end{align*}

    The normalized moving average ``y`` of ``x`` is computed as

    .. math::

        y_i = \frac{x_i + (1 - \alpha) x_{i-1} + \ldots + (1 - \alpha)^i x_0}
        {1 + (1 - \alpha) + \ldots + (1 - \alpha)^i}

    Upper and lower confidence bounds, ``l`` and ``u``, of the exponential moving
    average are computed using the exponential moving standard deviation, ``s``, and ``y`` as

    .. math::
        l_i = y_i + z_{\frac{1}{2} (1-p)} \times s_i \\
        u_i = u_o + z_{\frac{1}{2} (1+p)} \times s_i

    If condfidence bounds are included, the returned time series will contain
    the upper and lower bounds as additional univariates. For example if the
    transform is applied to a time series with two univariates "x" and "y",
    the resulting time series will contain univariates with the following names:
    "x", "x_lb", "x_ub", "y", "y_lb", "y_ub".
    """

    def __init__(self, alpha: float, normalize: bool = True, p: float = 0.95, ci: bool = False):
        """
        :param alpha: smoothing factor to use for exponential weighting.
        :param normalize: If True, divide by the decaying adjustment in
            beginning periods.
        :param p: confidence level to use if returning the upper and lower
            bounds of the confidence interval.
        :param ci: If True, return the the upper and lower confidence bounds
            of the the exponential moving average as well.
        """
        super().__init__()
        self.alpha = alpha
        self.normalize = normalize
        self.p = p
        self.ci = ci

    @property
    def requires_inversion_state(self):
        """
        ``False`` because the exponential moving average is stateless to invert.
        """
        return False

    def train(self, time_series: TimeSeries):
        pass

    def __call__(self, time_series: TimeSeries) -> TimeSeries:
        new_vars = OrderedDict()
        for name, var in time_series.items():
            emw = var.to_pd().ewm(alpha=self.alpha, adjust=self.normalize)
            ema = emw.mean()
            new_vars[name] = UnivariateTimeSeries.from_pd(ema)
            if self.ci:
                ems = emw.std()
                ems[0] = ems[1]
                new_vars[f"{name}_lb"] = UnivariateTimeSeries.from_pd(ema + norm.ppf(0.5 * (1 - self.p)) * ems)
                new_vars[f"{name}_ub"] = UnivariateTimeSeries.from_pd(ema + norm.ppf(0.5 * (1 + self.p)) * ems)

        ret = TimeSeries(new_vars, check_aligned=False)
        ret._is_aligned = time_series.is_aligned
        return ret

    def _invert(self, time_series: TimeSeries) -> TimeSeries:
        new_vars = OrderedDict()
        for name, var in time_series.items():
            # check whether varaiable is an upper or lower confidence bound
            if isinstance(name, str) and (name.endswith("_lb") or name.endswith("_ub")):
                continue
            t, y = var.index, var.np_values

            # Geometric series formula for (1 - alpha)^0 + ... + (1 - alpha)^i
            # to unnormalize the EWM before inverting it
            if self.normalize:
                weights = 1 - (1 - self.alpha) ** np.arange(1, len(y) + 1)
                y = y * weights / self.alpha
                x = y[1:] - (1 - self.alpha) * y[:-1]

            # Direct inversion of one-step update for unnormalized EWMA
            else:
                x = (y[1:] - (1 - self.alpha) * y[:-1]) / self.alpha

            x = np.concatenate((y[:1], x))
            new_vars[name] = UnivariateTimeSeries(t, x)

        ret = TimeSeries(new_vars, check_aligned=False)
        ret._is_aligned = time_series.is_aligned
        return ret

class DifferenceTransform(InvertibleTransformBase):
    """
    Applies a difference transform to the input time series. We include it
    as a moving average because we can consider the difference transform
    to be a 2-step moving "average" with weights w = [-1, 1].
    """

    def train(self, time_series: TimeSeries):
        pass

    def __call__(self, time_series: TimeSeries) -> TimeSeries:
        x0 = {}
        new_vars = OrderedDict()
        for name, var in time_series.items():
            x0[name] = var[0]
            if len(var) <= 1:
                logger.warning(f"Cannot apply a difference transform to a time series of length {len(var)} < 2")
                new_vars[name] = UnivariateTimeSeries([], [])
            else:
                new_vars[name] = UnivariateTimeSeries.from_pd(var.diff())[1:]

        self.inversion_state = x0
        ret = TimeSeries(new_vars, check_aligned=False)
        ret._is_aligned = time_series.is_aligned
        return ret

    def _invert(self, time_series: TimeSeries) -> TimeSeries:
        new_vars = OrderedDict()
        for name, var in time_series.items():
            t0, x0 = self.inversion_state[name]
            var = UnivariateTimeSeries([t0], [x0]).concat(var).cumsum()
            new_vars[name] = UnivariateTimeSeries.from_pd(var)

        ret = TimeSeries(new_vars, check_aligned=False)
        ret._is_aligned = time_series.is_aligned
        return ret

class LagTransform(InvertibleTransformBase):
    """
    Applies a lag transform to the input time series. Each x(i) gets mapped
    to x(i) - x(i-k). We include it as a moving average because we can consider
    the lag transform to be a k+1-step moving "average" with weights
    w = [-1, 0,..., 0, 1]. One may optionally left-pad the sequence with the
    first value in the time series.
    """

    def __init__(self, k: int, pad: bool = False):
        super().__init__()
        assert k >= 1
        self.k = k
        self.pad = pad

    def train(self, time_series: TimeSeries):
        pass

    def __call__(self, time_series: TimeSeries) -> TimeSeries:
        all_tk, all_xk = {}, {}
        new_vars = OrderedDict()
        for name, var in time_series.items():
            # Apply any x-padding or t-truncating necessary
            t, x = var.index, var.np_values
            all_xk[name] = x[: self.k]
            if self.pad:
                all_tk[name] = t[:0]
                x = np.concatenate((np.full(self.k, x[0]), x))
            else:
                all_tk[name] = t[: self.k]
                t = t[self.k :]

            if len(var) <= self.k and not self.pad:
                logger.warning(
                    f"Cannot apply a {self.k}-lag transform to a time series of length {len(var)} <= {self.k}"
                )
                new_vars[name] = UnivariateTimeSeries([], [])
            else:
                new_vars[name] = UnivariateTimeSeries(t, x[self.k :] - x[: -self.k])

        self.inversion_state = all_tk, all_xk
        return TimeSeries(new_vars)

    def _invert(self, time_series: TimeSeries) -> TimeSeries:
        all_tk, all_xk = self.inversion_state
        new_vars = OrderedDict()
        for name, var in time_series.items():
            tk, xk = all_tk[name], all_xk[name]
            t = tk.union(var.index)
            if len(t) == len(xk) + len(var):  # no padding
                y = np.concatenate((xk, var.np_values))
            elif len(t) == len(var):  # padding
                y = np.asarray(var.values)
                y[: len(xk)] = xk
            else:
                raise RuntimeError("Something went wrong: inversion state has unexpected size.")

            x = np.zeros(len(t))
            for i in range(self.k):
                x[i :: self.k] = np.cumsum(y[i :: self.k])
            new_vars[name] = UnivariateTimeSeries(t, x)
        return TimeSeries(new_vars)

    def compute_lag(self, var: UnivariateTimeSeries) -> UnivariateTimeSeries:
        t, x = var.index, var.np_values
        if self.pad:
            x = np.concatenate((np.full(self.k, x[0]), x))
        vals = x[self.k :] - x[: -self.k]
        times = t if self.pad else t[self.k :]
        return UnivariateTimeSeries(times, vals)


import torch
import torch.nn as nn
from pymotion.ops.forward_kinematics_torch import fk
from pymotion.ops.skeleton_torch import from_root_dual_quat

class MSE_DQ(nn.Module):
    def __init__(self, param, parents, device) -> None:
        super().__init__()
        self.mse = nn.MSELoss()
        self.param = param
        self.parents = parents
        self.device = device

    def set_mean(self, mean_dqs):
        self.mean_dqs = mean_dqs.unsqueeze(-1)

    def set_std(self, std_dqs):
        self.std_dqs = std_dqs.unsqueeze(-1)

    def set_offsets(self, offsets):
        self.joint_distances = torch.norm(offsets, dim=1)

    def forward_generator(self, input, target):
        dqs = input.clone()
        target_dqs = target.clone()
        # Dual Quaternions MSE Loss
        loss_joints = self.mse(dqs[:, 8:, :], target_dqs[:, 8:, :])
        loss_root = self.mse(dqs[:, :8, :], target_dqs[:, :8, :])
        return loss_root * self.param["lambda_root"] + loss_joints

class MSE_DQ_FK(nn.Module):
    def __init__(self, param, parents, device) -> None:
        super().__init__()
        self.mse = nn.MSELoss()
        self.param = param
        self.parents = torch.Tensor(parents).long().to(device)
        self.device = device

        # indices without sparse input
        self.indices_no_sparse = []
        for i in range(0, 22):
            if i not in self.param["sparse_joints"]:
                self.indices_no_sparse.append(i)
        self.indices_no_sparse = torch.tensor(self.indices_no_sparse).to(self.device)

    def set_mean(self, mean_dqs):
        self.mean_dqs = mean_dqs.unsqueeze(-1)

    def set_std(self, std_dqs):
        self.std_dqs = std_dqs.unsqueeze(-1)

    def set_offsets(self, offsets):
        self.offsets = offsets  # denormalized
        self.joint_distances = torch.norm(offsets, dim=1)

    def forward_ik(self, input, input_ik, input_decoder, target):
        dqs = input_ik.clone()
        dqs_decoder = input_decoder.clone()
        target_dqs = target.clone()
        # add fake root rotation to input_ik (it is to keep the code compatible with previous versions)
        # without using the root rotation predicted from the decoder
        dqs = torch.cat((torch.zeros_like(dqs[:, :8, :]), dqs), dim=1)
        # FK Loss
        # denormalize dual quaternions
        dqs = dqs * self.std_dqs + self.mean_dqs
        dqs[:, :8, :] = (
            torch.tensor([1, 0, 0, 0, 0, 0, 0, 0]).unsqueeze(0).unsqueeze(-1)
        )  # make root quaternion identity (so asserts will pass)
        dqs = dqs.reshape((dqs.shape[0], -1, 8, dqs.shape[-1])).permute(0, 3, 1, 2)
        dqs_decoder = dqs_decoder * self.std_dqs + self.mean_dqs
        dqs_decoder[:, :8, :] = (
            torch.tensor([1, 0, 0, 0, 0, 0, 0, 0]).unsqueeze(0).unsqueeze(-1)
        )  # make root quaternion identity (no root)
        dqs_decoder = dqs_decoder.reshape(
            (dqs_decoder.shape[0], -1, 8, dqs_decoder.shape[-1])
        ).permute(0, 3, 1, 2)
        target_dqs = target_dqs * self.std_dqs + self.mean_dqs
        target_dqs = target_dqs.reshape(
            (target_dqs.shape[0], -1, 8, target_dqs.shape[-1])
        ).permute(0, 3, 1, 2)
        # compute rotations and translations local to their parents
        _, local_rot = from_root_dual_quat(dqs, self.parents)
        local_rot[..., 0, :] = torch.tensor([1, 0, 0, 0]).to(
            self.device
        )  # no global rotation
        _, local_rot_decoder = from_root_dual_quat(dqs_decoder, self.parents)
        local_rot_decoder[..., 0, :] = torch.tensor([1, 0, 0, 0]).to(
            self.device
        )  # no global rotation
        _, target_local_rot = from_root_dual_quat(target_dqs, self.parents)
        target_local_rot[..., 0, :] = torch.tensor([1, 0, 0, 0]).to(
            self.device
        )  # no global rotation
        # compute final positions (root space) using standard FK
        joint_poses, joint_rot_mat = fk(
            local_rot,
            torch.zeros((local_rot.shape[0], local_rot.shape[1], 3)),
            self.offsets,
            self.parents,
        )
        target_joint_poses, target_joint_rot_mat = fk(
            target_local_rot,
            torch.zeros((target_local_rot.shape[0], target_local_rot.shape[1], 3)),
            self.offsets,
            self.parents,
        )
        decoder_joint_poses, decoder_rot_mat = fk(
            local_rot_decoder,
            torch.zeros((local_rot_decoder.shape[0], local_rot_decoder.shape[1], 3)),
            self.offsets,
            self.parents,
        )

        # positions
        loss_ee = self.mse(
            joint_poses[:, :, self.param["sparse_joints"][1:], :],
            target_joint_poses[:, :, self.param["sparse_joints"][1:], :],
        )
        # rotations
        loss_ee += self.mse(
            joint_rot_mat[:, :, self.param["sparse_joints"][1:], :, :],
            target_joint_rot_mat[:, :, self.param["sparse_joints"][1:], :, :],
        )

        # regularization
        loss_ee_reg = self.mse(
            decoder_joint_poses[:, :, self.indices_no_sparse, :],
            joint_poses[:, :, self.indices_no_sparse, :],
        )
        loss_ee_reg += self.mse(
            decoder_rot_mat[:, :, self.indices_no_sparse, :, :],
            joint_rot_mat[:, :, self.indices_no_sparse, :, :],
        )

        return (
            loss_ee * self.param["lambda_ee"]
            + loss_ee_reg * self.param["lambda_ee_reg"]
        )


import unittest

from pgdumplib import exceptions

class ExceptionTestCase(unittest.TestCase):

    def test_repr_formatting(self):
        exc = exceptions.EntityNotFoundError('public', 'table')
        self.assertEqual(
            repr(exc), "<EntityNotFound namespace='public' table='table'>")

    def test_str_formatting(self):
        exc = exceptions.EntityNotFoundError('public', 'table')
        self.assertEqual(
            str(exc), 'Did not find public.table in the table of contents')


from robot.api import logger
from chargeamps import CentralSystem as cs

import time
class centralsystem:
    ROBOT_LIBRARY_SCOPE = 'SUITE'

    def start_CentralSystem(self, addr, port):
        if not hasattr(self, "cs"):
            self.cs = cs.CentralSystem()
            self.cs.serve(addr, port)
        elif self.cs.addr != addr or self.cs.port != port:
            logger.console("CentralSystem with different config, bailing")
            raise Exception("CentralSystem already running with different config")

    def wait_for_ChargePoint_to_connect(self):
        cp_id = self.cs.wait_for_connection()
        time.sleep(0.5)  # Sleep a bit for startup comm to finish
        return cp_id

    def send_TriggerMessage(self, charge_id, msg):
        chargepoint = self.cs.find_chargepoint(charge_id)
        chargepoint.trigger_message(msg)

    def disconnect_central_system(self):
        pass

    def clear_msg_queue(self, charge_id):
        chargepoint = self.cs.find_chargepoint(charge_id)
        chargepoint.clear_msg_queue()

    def wait_for_msg(self, charge_id, msg):
        chargepoint = self.cs.find_chargepoint(charge_id)
        return chargepoint.wait_msg(msg)

    def add_msg_trap(self, charge_id, msg):
        chargepoint = self.cs.find_chargepoint(charge_id)
        chargepoint.add_msg_trap(msg)

    def create_message(self, msgType, **kwargs):
        ret = {}
        ret["type"] = msgType
        ret["payload"] = {}
        return ret

    def set_message_field(self, msg,  field, value):
        # field is dot separated dict
        fields = field.split('.')
        if fields[0] not in msg["payload"].keys():
            msg["payload"][fields[0]] = {}
        tmp = msg["payload"][fields[0]]
        for f in fields[1:]:
            if fields.index(f) == len(fields) - 1:
                tmp[f] = value
                return
            elif f not in tmp.keys():
                tmp[f] = {}
            tmp = tmp[f]

    def set_light_intensity(self, cp_id, intensity):
        chargepoint = self.cs.find_chargepoint(cp_id)
        chargepoint.light_intensity = int(intensity)

    def get_light_intensity(self, cp_id):
        chargepoint = self.cs.find_chargepoint(cp_id)
        return chargepoint.light_intensity

def main():
    sys = centralsystem()
    sys.start_CentralSystem('0.0.0.0', 9000)
    charge = sys.wait_for_ChargePoint_to_connect()
    sys.send_TriggerMessage(charge, "BootNotification")
    sys.wait_for_msg(charge, "BootNotification")
    while True:
        pass
    #sys.wait_for_msg(charge)
    #sys.disconnect_central_system()

if __name__ == "__main__":
    main()


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

# In[29]:

#Import our libraries

import keras
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns

# In[30]:

#Get our training and test data

train = pd.read_csv(r"C:\Users\rohit\Downloads\dataset_mnist\sign_mnist_train.csv")
test = pd.read_csv(r"C:\Users\rohit\Downloads\dataset_mnist\sign_mnist_test.csv")

# In[31]:

#inspecting train data

train.head()

# In[32]:

train.shape

# In[33]:

test.shape

# In[34]:

#Get our training labels

labels = train['label'].values
labels

# In[35]:

#View the unique label 24 in total

unique_val = np.array(labels)
print(unique_val)
np.unique(unique_val)

# In[36]:

#Plot the quanities in each graph

plt.figure(figsize = (18,8))
sns.countplot(x =labels)

# In[37]:

#Drop training labels from our training data so we can separate it

train.drop('label',axis=1,inplace = True)

# In[38]:

train.head()

# In[39]:

train.values.shape

# In[40]:

#Extract the image from data for each row in our csv,it's in rows of 784 coloumns

images = train.values
images = np.array([np.reshape(i,(28,28)) for i in images])
images = np.array([i.flatten() for i in images])

# In[41]:

#One hot encoding

from sklearn.preprocessing import LabelBinarizer
label_binrizer = LabelBinarizer()
labels = label_binrizer.fit_transform(labels)
labels

# In[42]:

#Inspect an image

index = 5
print(labels[index])
plt.imshow(images[index].reshape(28,28))

# In[43]:

# Use openCV to view 10 random images from our training data

import cv2
import numpy as np
for i in range(0,10):
    rand = np.random.randint(0,len(images))
    input_im = images[rand]
    sample = input_im.reshape(28,28).astype(np.uint8)
    sample = cv2.resize(sample,None,fx=10,fy=10,interpolation = cv2.INTER_CUBIC)
    cv2.imshow("sample image",sample)
    cv2.waitKey(0)

cv2.destroyAllWindows()

# In[44]:

#Split our data into x_train,x_test,y_traion,y_test

from sklearn.model_selection import train_test_split
x_train , x_test,y_train,y_test = train_test_split(images , labels , test_size = 0.3 , random_state = 101)

# In[45]:

#Start loading our tensorflow module and define batch size

from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import Dense,Conv2D,MaxPooling2D,Flatten,Dropout
batch_size = 128
num_classes = 24
epochs = 10

# In[46]:

#Scale our images

x_train = x_train/255
x_test = x_test/255

# In[47]:

#Reshape them into size required by tf,keras

x_train = x_train.reshape(x_train.shape[0],28,28,1)
x_test = x_test.reshape(x_test.shape[0],28,28,1)

plt.imshow(x_train[0].reshape(28,28))

# In[48]:

#CNN Model

from tensorflow.keras.layers import Conv2D,MaxPooling2D
from tensorflow.keras import backend as K
from tensorflow.keras.optimizers import Adam

model = Sequential()
model.add(Conv2D(64, kernel_size = (3,3),activation ='relu' , input_shape=(28,28,1)))
model.add(MaxPooling2D(pool_size = (2,2)))

model.add(Conv2D(64,kernel_size = (3,3), activation = 'relu'))
model.add(MaxPooling2D(pool_size = (2,2)))

model.add(Conv2D(64,kernel_size = (3,3), activation = 'relu'))
model.add(MaxPooling2D(pool_size = (2,2)))

model.add(Flatten())
model.add(Dense(128,activation = 'relu'))
model.add(Dropout(0.20))

model.add(Dense(num_classes, activation='softmax'))

# In[49]:

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

# In[50]:

print(model.summary())

# In[ ]:

#Train our model

history = model.fit(x_train,y_train,validation_data = (x_test,y_test),epochs=epochs,batch_size=batch_size)

# In[ ]:

#Save our model

model.save("sign_mist_cnn_50_Epochs.h5")
print("Model Saved")

# In[26]:

import warnings
warnings.filterwarnings("ignore")

# In[27]:

#View our training history graphically
#ACCURACY

plt.plot(history.history['accuracy'])
plt.plot(history.history['val_accuracy'])
plt.title("Accuracy Evaluation")
plt.xlabel('epoch')
plt.ylabel('accuracy')
plt.legend('train','test')

plt.show()

# In[28]:

#LOSS

plt.plot(history.history['loss'])
plt.plot(history.history['val_loss'])
plt.title("loss Evaluation")
plt.xlabel('epoch')
plt.ylabel('loss')
plt.legend('train','test')

plt.show()

# In[29]:

test = pd.read_csv(r"C:\Users\rohit\Downloads\dataset_mnist\sign_mnist_test.csv")

test.head()

# In[30]:

#Reshape our test data so that we evaluate it's performance on an unseen data

test_labels = test['label']
test.drop('label',axis=1,inplace = True)
test_images = test.values
test_images = np.array([np.reshape(i,(28,28)) for i in test_images])
test_images = np.array([i.flatten() for i in test_images])
test_labels = label_binrizer.fit_transform(test_labels)
test_images = test_images.reshape(test_images.shape[0],28,28,1)
test_images.shape
y_pred = model.predict(test_images)

# In[31]:

y_pred[:1]

# In[32]:

len(y_pred)

# In[33]:

len(test_labels)

# In[34]:

# label.shape
test_labels.shape

# In[35]:

test.shape

# In[36]:

test_labels= test_labels[:len(y_pred)]

# In[37]:

# Get our accuracy score

from sklearn.metrics import accuracy_score
accuracy_score(test_labels , y_pred.round())

# In[38]:

#Create function to match label to letter

def getLetter(result):
    classLabels = {0: 'A',
                  1:'B',
                  2:'C',
                  3:'D',
                  4:'E',
                  5:'F',
                  6:'G',
                  7:'H',
                  8:'I',
                  9:'K',
                  10:'L',
                  11:'M',
                  12:'N',
                  13:'O',
                  14:'P',
                  15:'Q',
                  16:'R',
                  17:'S',
                  18:'T',
                  19:'U',
                  20:'V',
                  21:'W',
                  22:'X',
                  23:'Y'}
    try:
        res = int(result)
        return classLabels[res]
    except:
        return "Error"

# In[39]:

plt.imshow(x_test[0].reshape(28,28))

# In[40]:

x = x_test[0].reshape(1,28,28,1)

# In[43]:

result = str(model.predict_classes(x))
print(f'The Predicted class for the random image is:{getLetter(result[1:2])}')

# # TEST OUR MODEL WITH WEBCAM

# In[ ]:

cap = cv2.VideoCapture(0)
while True:

    ret, frame = cap.read()
    roi = frame[100:400, 320:620]
    cv2.imshow('roi',roi)
    roi = cv2.cvtColor(roi,cv2.COLOR_BGR2GRAY)
    roi = cv2.resize(roi, (28,28), interpolation = cv2.INTER_AREA)

    cv2.imshow('roi sacled and gray', roi)
    copy = frame.copy()
    cv2.rectangle(copy,(320,100), (620,400),(250,0,0), 5)

    roi = roi.reshape(1,28,28,1)

    result = str(model.predict_classes(roi, 1, verbose = 0)[0])
    cv2.putText(copy,getLetter(result),(300,100),cv2.FONT_HERSHEY_COMPLEX,2,(0,255,0),2)
    cv2.imshow('frame', copy)

    if cv2.waitKey(1) == 13:
        break

cap.release()
cv2.destroyAllWindows()


#Coding Exercise 1
#Create a program that prompts the user to input their name once.
# Then, the program prints out the name once with the first letter capitalized.

name1=input("Enter Your Name :")
print(name1.title())

#CCreate a program that prompts the user to input their name repeatedly.
# Then, the program repeatedly prints out the name with the first letter capitalized.

i='y'
while i in ('yes' ,'y'):
    name2=input('Enter Your Name : ')
    print(name2.title())
    i=input('Do you want to enter your name again?(yes or no)')

countries = []

 #testing list
while True:
    country = input("Enter the country: ")
    countries.append(country)
    print(countries)


import pyaudio
import numpy as np
import matplotlib.pyplot as plt
from utils import CHUNK, RATE, CHANNELS, FORMAT

p = pyaudio.PyAudio()
stream = p.open(format=FORMAT, channels=CHANNELS, rate=RATE, input=True, frames_per_buffer=CHUNK)

# Create figure for subplots of signals
fig, axs = plt.subplots(3, 2, figsize=(6, 6))

fig.suptitle('Microphone array data')
plt.subplots_adjust(hspace=0.8, wspace=0.5)
lines = []

for i, ax in enumerate(axs.flat):
    ax.set_title(f'Microphone {i}')
    ax.set_ylim(-300, 300)
    ax.set_xlim(0, CHUNK)

    x = np.arange(0, 2 * CHUNK, 2)
    lines += ax.plot(x, np.random.rand(CHUNK))

i = 1
# Read signals forever
while True:
    try:
        data = np.frombuffer(stream.read(CHUNK), dtype=np.int16)

        # Update plots with data from the new frame
        for c in range(CHANNELS - 2):
            mic_data = data[c::CHANNELS]
            lines[c].set_ydata(mic_data)

        fig.canvas.draw()
        fig.canvas.flush_events()
        plt.pause(0.005)

        # Print out the first reading of each microphone from the new frame
        frmt = "{:>5}" * (CHANNELS - 1)
        print(f'Frame ' '{:>3}:    '.format(i) + '[' + frmt.format(*data[:(CHANNELS - 1)]) + '   ]')
        i += 1

    except KeyboardInterrupt:
        break

stream.stop_stream()
stream.close()
p.terminate()