from jumpscale.loader import j
from jumpscale.sals.chatflows.chatflows import GedisChatBot, chatflow_step

class MeetingPlan(GedisChatBot):

    # Chatflow steps
    steps = ["description_step", "scheduling_step", "documents_step", "confirmation"]

    # Chatflow title
    title = "Meeting Plan"

    @chatflow_step("Welcome")
    def description_step(self):
        if not j.core.config.get("MeetingLocation"):
            j.core.config.set("MeetingLocation", {"Belgium": [], "Spain": [], "Egypt": []})

        self.logged_in_user = self.user_info()
        self.username = self.logged_in_user["username"]
        email = self.logged_in_user["email"]
        self.md_show(
            f"Welcome {self.username} to the meeting planner! This platform will be used to plan and gather required information for the next meeting.",
            required=True,
        )

    @chatflow_step("Meeting Scheduling")
    def scheduling_step(self):
        form = self.new_form()
        chosen_time = form.datetime_picker(
            "When do you prefer the meeting to take place?", default=j.data.time.get().timestamp, required=True
        )
        place = form.drop_down_choice(
            "Where do you prefer the meeting to be held?", ["Belgium", "Spain", "Egypt"], required=True
        )
        form.ask()
        self.meeting_time = int(chosen_time.value)
        self.meeting_place = place.value

    @chatflow_step("Relevant Document")
    def documents_step(self):
        self.documents = self.upload_file(
            """Please add any relevant document or notes that will be used in the meeting
                    Just upload the file with the key""",
            required=True,
        )

    @chatflow_step(title="Confirmation", disable_previous=True, final_step=True)
    def confirmation(self):
        meetingLocationData = j.core.config.get("MeetingLocation")
        if self.username not in meetingLocationData[self.meeting_place]:
            meetingLocationData[self.meeting_place].append(self.username)
            j.core.config.set("MeetingLocation", meetingLocationData)
        bedata = ",".join(meetingLocationData["Belgium"])
        egdata = ",".join(meetingLocationData["Egypt"])
        spdata = ",".join(meetingLocationData["Spain"])
        report = f"""# Hello {self.username}
## You have successfully submitted a response \n\n
\n
- You prefer the meeting to be held on {j.data.time.get(self.meeting_time).format()} in {self.meeting_place}
- You have attached the following to be used as meeting notes:
{self.documents} \n\n
\n

## The votes for the locations are: \n\n
\n

- Belgium   : {bedata}
- Egypt     : {egdata}
- Spain     : {spdata}
        """

        self.md_show(report, md=True)

chat = MeetingPlan


"""
Different Methods to download data
"""
import logging
import os

import pandas as pd
from ..processing.workspace import DirectoryOperations
from datetime import datetime

logger = logging.getLogger(__name__)

class SaveData:
    @classmethod
    def write_file(cls, df: pd.DataFrame, tag: str,
                   date_tag: str, path: str = r'N:\DSI\ASI4\ASI42\Partilha\Data\sources\clean_data',
                   product: str = "master",
                   delimiter: str = ',', encoding: str = 'UTF-8-SIG', file_type: str = 'csv',
                   date_part: str = '') -> None:
        """
        Function to save file in csv
        :param date_part:
        :param file_type:
        :param df:
        :param path:
        :param tag:
        :param date_tag:
        :param delimiter:
        :param encoding:
        :param product:
        :return:
        """
        # Saving clean source
        logger.info('Saving file')
        assert type(df) == pd.DataFrame, "The df parameter must be a DataFrame"
        assert type(tag) == str, "The tag parameter must be a string"
        assert type(date_tag) == str, "The date_tag parameter must be a string"
        assert type(path) == str, "The path must be a string"
        assert type(delimiter) == str, "The delimiter must be a string"
        assert type(encoding) == str, "The encoding must be a string"
        assert type(file_type) == str, "The file_type must be a string"
        assert type(date_part) == str,  "The date_part must be a string"

        # Check directory path with product
        DirectoryOperations.check_dir(path, product)

        if date_part != '':
            complete_path = os.path.join(path, product, tag)
            DirectoryOperations.check_dir(complete_path, '{}_{}'.format(tag, date_part))
            complete_path = os.path.join(complete_path, '{}_{}'.format(tag, date_part))
            tag = '{}_{}'.format(tag, date_part)
        else:
            # Check directory path with product and source tag
            complete_path = os.path.join(path, product)
            DirectoryOperations.check_dir(complete_path, tag)
            complete_path = os.path.join(complete_path, tag)
        # Check date_tag
        try:
            datetime.strptime(date_tag, "%Y%m%d_%H%M%S")
        except ValueError as e:
            logger.error(e)
            raise ValueError("The given str_time must be on the format: {0}".format("%Y%m%d_%H%M%S"))

        df.to_csv(
            os.path.join(complete_path,
                         tag + '_' + date_tag + '.' + file_type),
            sep=delimiter,
            index=False,
            encoding=encoding)

        logger.info("Source updated")

    @classmethod
    def write_table(cls, df: pd.DataFrame, tag: str, date_tag: str) -> None:
        """
        Function to write a table
        :param df:
        :param tag:
        :param date_tag:
        :return:
        """
        assert type(df) == pd.DataFrame, "The df parameter must be a DataFrame"
        assert type(tag) == str, "The df parameter must be a string"
        assert type(date_tag) == str, "The df parameter must be a string"


arr = [12, 12]

def solution(arr):
    arr = list(set(arr))
    temp = []
    while(True):
        maxn = max(arr)
        flag = False
        for i in range(2, int(maxn)+1):
            flag = False
            for j in range(len(arr)):
                if(arr[j] % i == 0):
                    arr[j] = arr[j]/i
                    flag = True
            if(flag):
                temp.append(i)
                break
        if not flag:
            break

    answer = 1
    for t in temp:
        answer *= t
    print(answer)
    return answer

solution(arr)

N개의 최소공배수.py


#!/usr/bin/python3
"""this module contains the class square"""

from models.rectangle import Rectangle

class Square(Rectangle):
    """base class of the project"""

    def __init__(self, size, x=0, y=0, id=None):
        """class contructor"""
        super().__init__(size, size, x, y, id)

    def __str__(self):
        """overwrite things dooo"""
        return ("[Square] ({}) {}/{} - {}".format(self.id, self.x,
                                                  self.y, self.width))

    @property
    def size(self):
        """size prperty setter"""
        return self.width

    @size.setter
    def size(self, v):
        """size property ge"""
        self.width = v
        self.heiht = v

    def update(self, *args, **kwargs):
        """updates values of the Square instance from args or kwargs"""
        if args:
            l = len(args)
            self.id = args[0]
            i = 1
            if i < l:
                self.size = args[i]
                i += 1
            if i < l:
                self.x = args[i]
                i += 1
            if i < l:
                self.y = args[i]
        elif kwargs:
            if 'id' in kwargs:
                self.id = kwargs['id']
            if 'size' in kwargs:
                self.size = kwargs['size']
            if 'x' in kwargs:
                self.x = kwargs['x']
            if 'y' in kwargs:
                self.y = kwargs['y']

    def to_dictionary(self):
        """to ionar ydicto"""
        dic = {}
        dic['id'] = self.id
        dic['size'] = self.size
        dic['x'] = self.x
        dic['y'] = self.y
        return dic


from django.conf.urls.defaults import *
from django.views.generic.simple import direct_to_template
from django.views.generic.list_detail import object_list
from django.contrib.auth.views import login, logout
from django.contrib import admin

from models import Chat
from views import chat, post, create, register, ping, filterchat, username_available

# These options dicts set up the front page (index) and public chat
# list views, which use Django's generic views and therefore do not
# have their own dedicated view functions in views.py.
index_options = {
    'template': 'index.html',
    'extra_context': {
        # Show the five most recently-updated chats on the front page
        'public_chats': lambda: Chat.objects.public()[:5],
    },
}
chat_list_options = {
    'queryset': Chat.objects.public().order_by('name'),
    'template_object_name': 'chat',
    'template_name': 'chat/chat_list.html',
}

# The URL patterns for Watercooler.  The names given to some of the
# patterns makes it easy to generate those URLs via the reverse()
# function in views or the {% url %} template tag in templates (to
# avoid hardcoding URLs).
urlpatterns = patterns('',
    # Essentially static pages
    url(r'^$', direct_to_template, index_options, name='index'),
    url(r'^about/$', direct_to_template, {'template': 'about.html'}, name='about'),
    url(r'^contact/$', direct_to_template, {'template': 'contact.html'}, name='contact'),

    # Create a new chat
    url(r'^create/$', create, name='create'),

    # The list of public chats
    url(r'^chat/$', object_list, chat_list_options, name='public_chats'),

    # Views for individual chats, identified by their slug
    url(r'^chat/(?P<slug>[-\w]+)/$', chat, name='chat'),
    url(r'^chat/(?P<slug>[-\w]+)/post/', post, name='post'),
    url(r'^chat/(?P<slug>[-\w]+)/ping/', ping, name='ping'),
    url(r'^chat/(?P<slug>[-\w]+)/filter/', filterchat, name='filter'),

    # Authentication views (login and logout are provided by the
    # Django auth framework, register is provided in views.py)
    (r'^accounts/login/$',  login),
    (r'^accounts/logout/$', logout),
    (r'^accounts/register/$', register),
    (r'^accounts/register/usernameAvailable/(?P<username>.*)/$', username_available),

    # The Django admin site
    (r'^admin/(.*)', admin.site.root),
)

# Automatically discover and install any admin sites (in this case,
# just the one admin.py)
admin.autodiscover()


"""
Requires:
    none

Provides:
    instance     -> families ([])
"""
import pyblish.api

from openpype.lib import filter_profiles

class CollectFtrackFamily(pyblish.api.InstancePlugin):
    """Adds explicitly 'ftrack' to families to upload instance to FTrack.

    Uses selection by combination of hosts/families/tasks names via
    profiles resolution.

    Triggered everywhere, checks instance against configured.

    Checks advanced filtering which works on 'families' not on main
    'family', as some variants dynamically resolves addition of ftrack
    based on 'families' (editorial drives it by presence of 'review')
    """

    label = "Collect Ftrack Family"
    order = pyblish.api.CollectorOrder + 0.4990

    profiles = None

    def process(self, instance):
        if not self.profiles:
            self.log.warning("No profiles present for adding Ftrack family")
            return

        host_name = instance.context.data["hostName"]
        family = instance.data["family"]
        task_name = instance.data.get("task")

        filtering_criteria = {
            "hosts": host_name,
            "families": family,
            "tasks": task_name
        }
        profile = filter_profiles(
            self.profiles,
            filtering_criteria,
            logger=self.log
        )

        add_ftrack_family = False
        families = instance.data.setdefault("families", [])

        if profile:
            add_ftrack_family = profile["add_ftrack_family"]
            additional_filters = profile.get("advanced_filtering")
            if additional_filters:
                families_set = set(families) | {family}
                self.log.info(
                    "'{}' families used for additional filtering".format(
                        families_set))
                add_ftrack_family = self._get_add_ftrack_f_from_addit_filters(
                    additional_filters,
                    families_set,
                    add_ftrack_family
                )

        result_str = "Not adding"
        if add_ftrack_family:
            result_str = "Adding"
            if "ftrack" not in families:
                families.append("ftrack")

        self.log.debug("{} 'ftrack' family for instance with '{}'".format(
            result_str, family
        ))

    def _get_add_ftrack_f_from_addit_filters(
        self, additional_filters, families, add_ftrack_family
    ):
        """Compares additional filters - working on instance's families.

        Triggered for more detailed filtering when main family matches,
        but content of 'families' actually matter.
        (For example 'review' in 'families' should result in adding to
        Ftrack)

        Args:
            additional_filters (dict) - from Setting
            families (set[str]) - subfamilies
            add_ftrack_family (bool) - add ftrack to families if True
        """

        override_filter = None
        override_filter_value = -1
        for additional_filter in additional_filters:
            filter_families = set(additional_filter["families"])
            valid = filter_families <= set(families)  # issubset
            if not valid:
                continue

            value = len(filter_families)
            if value > override_filter_value:
                override_filter = additional_filter
                override_filter_value = value

        if override_filter:
            add_ftrack_family = override_filter["add_ftrack_family"]

        return add_ftrack_family


class Solution(object):
    def longestCommonPrefix(self, strs):
        if not strs:
            return ""

        short = min(strs, key=len)
        for i, ch in enumerate(short):
            for others in strs:
                if others[i] != ch:
                    return short[:i]
        return short

print(Solution().longestCommonPrefix(["flower", "flow", "flight"]))


# standard libraries
import cv2
import numpy as np
from typing import List, Dict
from PIL import ImageGrab, ImageTk, Image
from pytesseract import Output, pytesseract
from mvc.custom_dataclass import TextPosition

def filter_text_points(dict_of_words: Dict[str, str], list_of_text_and_position: List[TextPosition]) -> List[TextPosition]:
    word_positions = {text_and_position.text: text_and_position for text_and_position in list_of_text_and_position}
    func_result = []

    for key, word in dict_of_words.items():
        if key in word_positions:
            text_position = word_positions[key]
            text_position.text = word
            func_result.append(text_position)

    return func_result

def image_to_text_position(img):
    """"""
    res_list = []
    ocr_data = pytesseract.image_to_data(img, output_type=Output.DICT)
    for index, text in enumerate(ocr_data["text"]):
        if text != "":
            """"""
            text = text
            x1 = int(ocr_data["left"][index])
            y1 = int(ocr_data["top"][index])
            x2 = x1 + int(ocr_data["width"][index])
            y2 = y1 + int(ocr_data["height"][index])
            new_text_position_point = TextPosition(text, x1, y1, x2, y2)
            res_list.append(new_text_position_point)
    return res_list

def grab_screen_shot():
        try:
            # Get the screenshot from the clipboard
            image = ImageGrab.grabclipboard()
            # if image = NONE ( no value ) then if statement doesn't get executed
            if image:
                return image
            else:
                print("No screenshot available")
        except Exception as e:
            print(e)

def image_transform(image_path: str) -> np.ndarray:
    # Load the image
    img = cv2.imread(image_path)
    # Convert the image to grayscale
    gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)

    # Threshold the image
    thresh = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY_INV + cv2.THRESH_OTSU)[1]
    # Remove noise
    thresh = cv2.medianBlur(thresh, 3)
    # Perform OCR on the image
    return img, gray

def draw_rects_on_img(img: np.ndarray, text_positions: TextPosition) -> Image:
    # font
    font = cv2.FONT_HERSHEY_SIMPLEX
    fontScale = 0.5
    color = (255, 0, 0)
    thickness = 1
    for tp_point in text_positions:
    # aliases for textbox position variables
        x1 = tp_point.x1
        y1 = tp_point.y1
        x2 = tp_point.x2
        y2 = tp_point.y2
        text_y_offset = 5
        # Draw rectangles and text on the given positions
        cv2.putText(img, tp_point.text, (x1,y1-text_y_offset), font, fontScale, color, thickness)
        # 2 is the offset, so the rectangle doesn't get drawn over the words
        cv2.rectangle(img, (x1-2, y1-2), (x2+2, y2+2), (0, 255, 0), 1)
        # return the completed image
    return img

def show_image(img:np.ndarray):
    """"""
    cv2.imshow('Originial', img)
    cv2.waitKey(0)
    cv2.destroyAllWindows()


import errno
from glob import glob
import os
import re
import shlex
import sqlite3
import sys
from tempfile import mkdtemp
from time import gmtime, strftime, time

from metomi.rose.app_run import (
    BuiltinApp,
    CompulsoryConfigValueError,
    ConfigValueError,
)
from metomi.rose.checksum import get_checksum, get_checksum_func
from metomi.rose.env import UnboundEnvironmentVariableError, env_var_process
from metomi.rose.popen import RosePopenError
from metomi.rose.reporter import Event, Reporter
from metomi.rose.scheme_handler import SchemeHandlersManager

class RoseArchDuplicateError(ConfigValueError):

    """An exception raised if duplicate archive targets are provided."""

    ERROR_FORMAT = '%s: duplicate archive target%s: "%s"'

class RoseArchValueError(KeyError):

    """An error raised on a bad value."""

    ERROR_FORMAT = "%s: bad %s: %s: %s: %s"

    def __str__(self):
        return self.ERROR_FORMAT % self.args

class RoseArchEvent(Event):

    """Event raised on an archiving target."""

    def __str__(self):
        target = self.args[0]
        t_info = ""
        if len(self.args) > 1:
            times = self.args[1]
            t_init, t_tran, t_arch = times
            t_info = ", t(init)=%s, dt(tran)=%ds, dt(arch)=%ds" % (
                strftime("%Y-%m-%dT%H:%M:%SZ", gmtime(t_init)),
                t_tran - t_init,
                t_arch - t_tran,
            )
        ret_code_str = ""
        if len(self.args) > 2 and self.args[2] is not None:
            ret_code_str = ", ret-code=%d" % self.args[2]
        ret = "%s %s [compress=%s%s%s]" % (
            target.status,
            target.name,
            target.compress_scheme,
            t_info,
            ret_code_str,
        )
        if target.status != target.ST_OLD:
            for source in sorted(
                target.sources.values(), key=lambda s: s.name
            ):
                ret += "\n%s\t%s (%s)" % (
                    target.status,
                    source.name,
                    source.orig_name,
                )
        return ret

class RoseArchApp(BuiltinApp):

    """Transform and archive files generated by suite tasks."""

    SCHEME = "rose_arch"
    SECTION = "arch"

    def run(self, app_runner, conf_tree, opts, args, uuid, work_files):
        """Transform and archive suite files.

        This application is designed to work under "rose task-run" in a suite.

        """
        dao = RoseArchDAO()
        suite_name = os.getenv("ROSE_SUITE_NAME")
        if not suite_name:
            app_runner.handle_event(
                'rose_arch must be run under rose task-run.',
                level=Reporter.FAIL,
                kind=Reporter.KIND_ERR,
            )
            return 1
        suite_dir = app_runner.suite_engine_proc.get_suite_dir(suite_name)
        cwd = os.getcwd()
        app_runner.fs_util.chdir(suite_dir)
        try:
            return self._run(dao, app_runner, conf_tree.node)
        finally:
            app_runner.fs_util.chdir(cwd)
            dao.close()

    def _run(self, dao, app_runner, config):
        """Transform and archive suite files.

        This application is designed to work under "rose task-run" in a suite.

        """
        compress_manager = SchemeHandlersManager(
            [os.path.dirname(os.path.dirname(sys.modules["rose"].__file__))],
            "rose.apps.rose_arch_compressions",
            ["compress_sources"],
            None,
            app_runner,
        )

        # Set up the targets
        s_key_tails = set()
        targets = []
        for t_key, t_node in sorted(config.value.items()):
            if t_node.is_ignored() or ":" not in t_key:
                continue
            s_key_head, s_key_tail = t_key.split(":", 1)
            if s_key_head != self.SECTION or not s_key_tail:
                continue

            # Determine target path.
            s_key_tail = t_key.split(":", 1)[1]
            try:
                s_key_tail = env_var_process(s_key_tail)
            except UnboundEnvironmentVariableError as exc:
                raise ConfigValueError([t_key, ""], "", exc)

            # If parenthesised target is optional.
            is_compulsory_target = True
            if s_key_tail.startswith("(") and s_key_tail.endswith(")"):
                s_key_tail = s_key_tail[1:-1]
                is_compulsory_target = False

            # Don't permit duplicate targets.
            if s_key_tail in s_key_tails:
                raise RoseArchDuplicateError([t_key], '', s_key_tail)
            else:
                s_key_tails.add(s_key_tail)

            target = self._run_target_setup(
                app_runner,
                compress_manager,
                config,
                t_key,
                s_key_tail,
                t_node,
                is_compulsory_target,
            )
            old_target = dao.select(target.name)
            if old_target is None or old_target != target:
                dao.delete(target)
            else:
                target.status = target.ST_OLD
            targets.append(target)
        targets.sort(key=lambda target: target.name)
        # Delete from database items that are no longer relevant
        dao.delete_all(filter_targets=targets)
        # Update the targets
        for target in targets:
            self._run_target_update(dao, app_runner, compress_manager, target)
        return [target.status for target in targets].count(
            RoseArchTarget.ST_BAD
        )

    def _run_target_setup(
        self,
        app_runner,
        compress_manager,
        config,
        t_key,
        s_key_tail,
        t_node,
        is_compulsory_target=True,
    ):
        """Helper for _run. Set up a target."""
        target_prefix = self._get_conf(
            config, t_node, "target-prefix", default=""
        )
        target = RoseArchTarget(target_prefix + s_key_tail)
        target.command_format = self._get_conf(
            config, t_node, "command-format", compulsory=True
        )
        try:
            target.command_format % {"sources": "", "target": ""}
        except KeyError as exc:
            target.status = target.ST_BAD
            app_runner.handle_event(
                RoseArchValueError(
                    target.name,
                    "command-format",
                    target.command_format,
                    type(exc).__name__,
                    exc,
                )
            )
        target.source_edit_format = self._get_conf(
            config, t_node, "source-edit-format", default=""
        )
        try:
            target.source_edit_format % {"in": "", "out": ""}
        except KeyError as exc:
            target.status = target.ST_BAD
            app_runner.handle_event(
                RoseArchValueError(
                    target.name,
                    "source-edit-format",
                    target.source_edit_format,
                    type(exc).__name__,
                    exc,
                )
            )
        update_check_str = self._get_conf(config, t_node, "update-check")
        try:
            checksum_func = get_checksum_func(update_check_str)
        except ValueError as exc:
            raise RoseArchValueError(
                target.name,
                "update-check",
                update_check_str,
                type(exc).__name__,
                exc,
            )
        source_prefix = self._get_conf(
            config, t_node, "source-prefix", default=""
        )
        for source_glob in shlex.split(
            self._get_conf(config, t_node, "source", compulsory=True)
        ):
            is_compulsory_source = is_compulsory_target
            if source_glob.startswith("(") and source_glob.endswith(")"):
                source_glob = source_glob[1:-1]
                is_compulsory_source = False
            paths = glob(source_prefix + source_glob)
            if not paths:
                app_runner.handle_event(
                    ConfigValueError(
                        [t_key, "source"],
                        source_glob,
                        OSError(
                            errno.ENOENT,
                            os.strerror(errno.ENOENT),
                            source_prefix + source_glob,
                        ),
                    )
                )
                if is_compulsory_source:
                    target.status = target.ST_BAD
                continue
            for path in paths:
                # N.B. source_prefix may not be a directory
                name = path[len(source_prefix) :]
                for path_, checksum, _ in get_checksum(path, checksum_func):
                    if checksum is None:  # is directory
                        continue
                    if path_:
                        target.sources[checksum] = RoseArchSource(
                            checksum,
                            os.path.join(name, path_),
                            os.path.join(path, path_),
                        )
                    else:  # path is a file
                        target.sources[checksum] = RoseArchSource(
                            checksum, name, path
                        )
        if not target.sources:
            if is_compulsory_target:
                target.status = target.ST_BAD
            else:
                target.status = target.ST_NULL
        target.compress_scheme = self._get_conf(config, t_node, "compress")
        if not target.compress_scheme:
            target_base = target.name
            if "/" in target.name:
                target_base = target.name.rsplit("/", 1)[1]
            if "." in target_base:
                tail = target_base.split(".", 1)[1]
                if compress_manager.get_handler(tail):
                    target.compress_scheme = tail
        elif compress_manager.get_handler(target.compress_scheme) is None:
            app_runner.handle_event(
                ConfigValueError(
                    [t_key, "compress"],
                    target.compress_scheme,
                    KeyError(target.compress_scheme),
                )
            )
            target.status = target.ST_BAD
        rename_format = self._get_conf(config, t_node, "rename-format")
        if rename_format:
            rename_parser_str = self._get_conf(config, t_node, "rename-parser")
            if rename_parser_str:
                try:
                    rename_parser = re.compile(rename_parser_str)
                except re.error as exc:
                    raise RoseArchValueError(
                        target.name,
                        "rename-parser",
                        rename_parser_str,
                        type(exc).__name__,
                        exc,
                    )
            else:
                rename_parser = None
            for source in target.sources.values():
                dict_ = {
                    "cycle": os.getenv("CYLC_TASK_CYCLE_TIME"),
                    "name": source.name,
                }
                if rename_parser:
                    match = rename_parser.match(source.name)
                    if match:
                        dict_.update(match.groupdict())
                try:
                    source.name = rename_format % dict_
                except (KeyError, ValueError) as exc:
                    raise RoseArchValueError(
                        target.name,
                        "rename-format",
                        rename_format,
                        type(exc).__name__,
                        exc,
                    )
        return target

    @classmethod
    def _run_target_update(cls, dao, app_runner, compress_manager, target):
        """Helper for _run. Update a target."""
        if target.status == target.ST_OLD:
            app_runner.handle_event(RoseArchEvent(target))
            return
        if target.status in (target.ST_BAD, target.ST_NULL):
            # boolean to int
            target.command_rc = int(target.status == target.ST_BAD)
            if target.status == target.ST_BAD:
                level = Event.FAIL
            else:
                level = Event.DEFAULT
            event = RoseArchEvent(target)
            app_runner.handle_event(event)
            app_runner.handle_event(event, kind=Event.KIND_ERR, level=level)
            return
        target.command_rc = 1
        dao.insert(target)
        work_dir = mkdtemp()
        times = [time()] * 3  # init, transformed, archived
        ret_code = None
        try:
            # Rename/edit sources
            target.status = target.ST_BAD
            rename_required = False
            for source in target.sources.values():
                if source.name != source.orig_name:
                    rename_required = True
                    break
            if rename_required or target.source_edit_format:
                for source in target.sources.values():
                    source.path = os.path.join(work_dir, source.name)
                    app_runner.fs_util.makedirs(os.path.dirname(source.path))
                    if target.source_edit_format:
                        command = target.source_edit_format % {
                            "in": source.orig_path,
                            "out": source.path,
                        }
                        app_runner.popen.run_ok(command, shell=True)
                    else:
                        app_runner.fs_util.symlink(
                            source.orig_path, source.path
                        )
            # Compress sources
            if target.compress_scheme:
                handler = compress_manager.get_handler(target.compress_scheme)
                handler.compress_sources(target, work_dir)
            times[1] = time()  # transformed time
            # Run archive command
            sources = []
            if target.work_source_path:
                sources = [target.work_source_path]
            else:
                for source in target.sources.values():
                    sources.append(source.path)
            command = target.command_format % {
                "sources": app_runner.popen.list_to_shell_str(sources),
                "target": app_runner.popen.list_to_shell_str([target.name]),
            }
            ret_code, out, err = app_runner.popen.run(command, shell=True)
            times[2] = time()  # archived time
            if ret_code:
                app_runner.handle_event(
                    RosePopenError([command], ret_code, out, err)
                )
            else:
                target.status = target.ST_NEW
                app_runner.handle_event(err, kind=Event.KIND_ERR)
            app_runner.handle_event(out)
            target.command_rc = ret_code
            dao.update_command_rc(target)
        finally:
            app_runner.fs_util.delete(work_dir)
            event = RoseArchEvent(target, times, ret_code)
            app_runner.handle_event(event)
            if target.status in (target.ST_BAD, target.ST_NULL):
                app_runner.handle_event(
                    event, kind=Event.KIND_ERR, level=Event.FAIL
                )

    def _get_conf(self, r_node, t_node, key, compulsory=False, default=None):
        """Return the value of a configuration."""
        value = t_node.get_value(
            [key], r_node.get_value([self.SECTION, key], default=default)
        )
        if compulsory and not value:
            raise CompulsoryConfigValueError([key], None, KeyError(key))
        if value:
            try:
                value = env_var_process(value)
            except UnboundEnvironmentVariableError as exc:
                raise ConfigValueError([key], value, exc)
        return value

class RoseArchTarget:

    """An archive target."""

    ST_OLD = "="
    ST_NEW = "+"
    ST_BAD = "!"
    ST_NULL = "0"

    def __init__(self, name):
        self.name = name
        self.compress_scheme = None
        self.command_format = None
        self.command_rc = 0
        self.sources = {}  # checksum: RoseArchSource
        self.source_edit_format = None
        self.status = None
        self.work_source_path = None

    def __eq__(self, other):
        if id(self) != id(other):
            for key in [
                "name",
                "compress_scheme",
                "command_format",
                "command_rc",
                "sources",
                "source_edit_format",
            ]:
                if getattr(self, key) != getattr(other, key, None):
                    return False
        return True

    def __ne__(self, other):
        return not self.__eq__(other)

class RoseArchSource:

    """An archive source."""

    def __init__(self, checksum, orig_name, orig_path=None):
        self.checksum = checksum
        self.orig_name = orig_name
        self.orig_path = orig_path
        self.name = self.orig_name
        self.path = self.orig_path

    def __eq__(self, other):
        if id(self) != id(other):
            for key in ["checksum", "name"]:
                if getattr(self, key) != getattr(other, key, None):
                    return False
        return True

    def __ne__(self, other):
        return not self.__eq__(other)

class RoseArchDAO:

    """Data access object for incremental mode."""

    FILE_NAME = ".rose-arch.db"
    T_SOURCES = "sources"
    T_TARGETS = "targets"

    def __init__(self):
        self.file_name = os.path.abspath(self.FILE_NAME)
        self.conn = None
        self.create()

    def close(self):
        """Close connection to the SQLite database file."""
        if self.conn is not None:
            self.conn.close()
            self.conn = None

    def get_conn(self):
        """Connect to the SQLite database file."""
        if self.conn is None:
            self.conn = sqlite3.connect(self.file_name)
        return self.conn

    def create(self):
        """Create the database file if it does not exist."""
        if not os.path.exists(self.file_name):
            conn = self.get_conn()
            conn.execute(
                """CREATE TABLE """
                + self.T_TARGETS
                + """ (
                            target_name TEXT,
                            compress_scheme TEXT,
                            command_format TEXT,
                            command_rc INT,
                            source_edit_format TEXT,
                            PRIMARY KEY(target_name))"""
            )
            conn.execute(
                """CREATE TABLE """
                + self.T_SOURCES
                + """ (
                            target_name TEXT,
                            source_name TEXT,
                            checksum TEXT,
                            UNIQUE(target_name, checksum))"""
            )
            conn.commit()

    def delete(self, target):
        """Remove target from the database."""
        conn = self.get_conn()
        for name in [self.T_TARGETS, self.T_SOURCES]:
            conn.execute(
                "DELETE FROM " + name + " WHERE target_name==?", [target.name]
            )
        conn.commit()

    def delete_all(self, filter_targets):
        """Remove all but those matching filter_targets from the database."""
        conn = self.get_conn()
        where = ""
        stmt_args = []
        if filter_targets:
            stmt_fragments = []
            for filter_target in filter_targets:
                stmt_fragments.append("target_name != ?")
                stmt_args.append(filter_target.name)
            where += " WHERE " + " AND ".join(stmt_fragments)
        for name in [self.T_TARGETS, self.T_SOURCES]:
            conn.execute("DELETE FROM " + name + where, stmt_args)
        conn.commit()

    def insert(self, target):
        """Insert a target in the database."""
        conn = self.get_conn()
        t_stmt = "INSERT INTO " + self.T_TARGETS + " VALUES (?, ?, ?, ?, ?)"
        t_stmt_args = [
            target.name,
            target.compress_scheme,
            target.command_format,
            target.command_rc,
            target.source_edit_format,
        ]
        conn.execute(t_stmt, t_stmt_args)
        sh_stmt = r"INSERT INTO " + self.T_SOURCES + " VALUES (?, ?, ?)"
        sh_stmt_args = [target.name]
        for checksum, source in target.sources.items():
            conn.execute(sh_stmt, sh_stmt_args + [source.name, checksum])
        conn.commit()

    def select(self, target_name):
        """Query database for target_name.

        On success, reconstruct the target as an instance of RoseArchTarget
        and return it.

        Return None on failure.

        """
        conn = self.get_conn()
        t_stmt = (
            "SELECT "
            + "compress_scheme,command_format,command_rc,source_edit_format "
            + "FROM "
            + self.T_TARGETS
            + " WHERE target_name==?"
        )
        t_stmt_args = [target_name]
        for row in conn.execute(t_stmt, t_stmt_args):
            target = RoseArchTarget(target_name)
            (
                target.compress_scheme,
                target.command_format,
                target.command_rc,
                target.source_edit_format,
            ) = row
            break
        else:
            return None
        s_stmt = (
            "SELECT source_name,checksum FROM "
            + self.T_SOURCES
            + " WHERE target_name==?"
        )
        s_stmt_args = [target_name]
        for s_row in conn.execute(s_stmt, s_stmt_args):
            source_name, checksum = s_row
            target.sources[checksum] = RoseArchSource(checksum, source_name)
        return target

    def update_command_rc(self, target):
        """Update the command return code of a target in the database."""
        conn = self.get_conn()
        conn.execute(
            "UPDATE "
            + self.T_TARGETS
            + " SET command_rc=?"
            + " WHERE target_name==?",
            [target.command_rc, target.name],
        )
        conn.commit()


import random

import numpy as np

from Environment.Client import Client
from Environment.Items import Items

# Cas stationnaire : Tout est regenere, pas de modif au cours du temps apart regeneration de nouveaux items et client
class BanditEnvironment :
    # N_client : Nombre de client
    # N item : Nombre d'item dans la liste de recommandation
    # Nb color :Nombre de couleur par item
    # Range price : 0 a range price pour les prix,
    # Catalog_size : Nombre d'item au total
    def __init__(self, n_client = 10, \
                 n_item = 10, \
                 nb_color = 5, \
                 range_price = 5, \
                 catalog_size = 50) :
        self.list_items = []
        self.catalog = []
        self.list_client = []
        self.n_client = n_client
        self.n_item = n_item
        self.catalog_size = catalog_size
        for client_id in range(n_client) :
            self.list_client.append(Client(client_id, n_item = n_item, nb_color = nb_color, range_price = range_price))
        for item_id in range(catalog_size) :
            self.catalog.append(Items(item_id, interval_price = range_price, interval_color = nb_color, catalog_size = catalog_size))
        self.current_client = -1
        self.range_price = range_price
        self.nb_color = nb_color

    # Recupere item le plus apprecie et faire histogramme des recos ?
    # Imposer un client ?

    # Action is a simple item
    def step_mono_recommendation(self, action) :
        item_selected = self.list_items[action]
        reward = self.list_client[self.current_client].offer(item_selected)
        self.current_client = np.random.randint(self.n_client)
        random.shuffle(self.list_items)
        return self.current_client, self.list_items, reward

    # Action is a list of item
    def step_list_recommendation(self, action) :
        return 0

    def reset(self) :
        self.current_client = np.random.randint(self.n_client)
        indices = np.random.randint(0, self.catalog_size, self.n_item)  # Genere une nouvelle liste
        self.list_items = [self.catalog[idx] for idx in indices]
        # Non content based
        return self.current_client, self.list_items

    # Attention a verifier, vue qu'on regenere les items, peut être change pour les items le one hot

    # Recuperer l'esperance de gain en recuperant le max esperance max item par client , moyenne et non moyenne sur les autres items
    # Reward / Client = Max proba item * prix (= 1 pour l'instant, juste proba d'acaht sans reward sur l'achat)
    def get_indicator(self) :
        max_reward_probs = []
        for client in self.list_client :
            # Calcul appetence de l'user pour la liste d'item
            taste_price = client.get_taste_price
            taste_color = client.get_taste_color
            max_item_score = -1
            for counter, item in enumerate(self.list_items) :
                score_price = taste_price[item.get_Price]  # Esperance achat selon prix
                score_color = taste_color[item.get_color]  # Esperance achat selon couleur
                expected_reward_proba = score_price * score_color * item.get_Price
                if expected_reward_proba > max_item_score :
                    max_item_score = expected_reward_proba
            max_reward_probs.append(max_item_score)  # Item le plus rentable d'achat par client
        return max_reward_probs


"""Extraction de segments de textes avec CRF"""

import csv
import itertools
import re
from pathlib import Path
from typing import Any, Iterable, Iterator, Sequence

import joblib  # type: ignore

from alexi.analyse import group_iob
from alexi.convert import FIELDNAMES, Converteur
from alexi.segment import Segmenteur, T_obj, page2features, page2labels, split_pages

FEATNAMES = [name for name in FIELDNAMES if name != "sequence"]
DEFAULT_MODEL = Path(__file__).parent / "models" / "crfseq.joblib.gz"
NUMDASH = re.compile(r"[\d-]+")

def features(page: Sequence[T_obj]) -> Iterator[list[str]]:
    for word in page:
        features = ["bias"]
        features.append("lower=%s" % word["text"].lower())
        features.append("reg=%s" % bool(word["text"].lower() == "règlement"))
        features.append("n=%s" % bool(word["text"][0] in "Nn"))
        features.append("alpha=%s" % bool(word["text"].isalpha()))
        features.append("numdash=%s" % bool(NUMDASH.match(word["text"])))
        features.append("bold=%s" % bool("bold" in word["fontname"].lower()))
        features.append("tag=%s" % word["segment"].partition("-")[2])
        features.append("size=%d" % (int(word["bottom"]) - int(word["top"])))
        yield features

def labels(_, word):
    return word.get("sequence", "O")

def load(paths):
    for p in paths:
        with open(Path(p), "rt") as infh:
            reader = csv.DictReader(infh)
            last_page = None
            for page in split_pages(reader):
                if last_page is None:
                    yield page
                last_page = page
            yield last_page

def make_data(dataset, n=2):
    return zip(
        *((page2features(s, features, n), page2labels(s, labels)) for s in dataset)
    )

class Extracteur:
    def __init__(self, model=DEFAULT_MODEL):
        self.crf = joblib.load(model)

    def predict(self, words: Iterable[dict[str, Any]]) -> Iterable[dict[str, Any]]:
        first_page = last_page = None
        for page in split_pages(words):
            if last_page is None:
                first_page = page
            else:
                if last_page is first_page:
                    yield from self.crf.predict_single(
                        page2features(last_page, feature_func=features, n=2)
                    )
                else:
                    for _ in last_page:
                        yield "O"
            last_page = page
        if last_page:
            yield from self.crf.predict_single(
                page2features(last_page, feature_func=features, n=2)
            )

    def __call__(self, words: Iterable[dict[str, Any]]) -> Iterable[dict[str, Any]]:
        c1, c2 = itertools.tee(words)
        pred = self.predict(c1)
        for label, word in zip(pred, c2):
            word["sequence"] = label
            yield word

def main():
    import argparse

    parser = argparse.ArgumentParser(description=__doc__)
    parser.add_argument("pdfpath", type=Path, help="Fichier PDF")
    args = parser.parse_args()
    conv = Converteur(args.pdfpath)
    seg = Segmenteur()
    ex = Extracteur()
    pages = conv.extract_words()
    segmented = seg(pages)
    tagged = ex(segmented)
    for bloc in group_iob(tagged, "sequence"):
        print(f"{bloc.type}: {bloc.texte}")

if __name__ == "__main__":
    main()


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

"""
This script filters streamlines in a tractogram according to their geometrical
properties (i.e. limiting their length and looping angle) and their anatomical
ending properties (i.e. the anatomical tissue or region their endpoints lie
in). The filtering is performed sequentially in four steps, each step
processing the data on the output of the previous step:

    Step 1 - Remove streamlines below the minimum length and above the
             maximum length. These thresholds must be set with the ``--minL``
             and ``--maxL`` options.
    Step 2 - Ensure that no streamlines end in the cerebrospinal fluid
             according to the provided parcellation. A binary mask can be used
             alternatively through the ``--csf_bin`` option.
    Step 3 - Ensure that no streamlines end in white matter by ensuring that
             they reach the cortical regions according to the provided
             parcellation. The cortical regions of the parcellation can be
             dilated using the ``--ctx_dilation_radius``.
    Step 4 - Remove streamlines if they make a loop with an angle above a
             certain threshold. It's possible to change this angle with the
             ``-a`` option.

Length and loop-based filtering (steps 1 and 2) will not have practical effects
if no specific thresholds are provided (but will be still executed), since
default values are 0 for the minimum allowed length and infinite for the
maximum allowed length and angle.

The anatomical region endings filtering requires a parcellation or label image
file including the cerebrospinal fluid and gray matter (cortical) regions
according to the Desikan-Killiany atlas. Intermediate tractograms (results of
each step and outliers) and volumes can be saved throughout the process.

Example usages:

# Filter length, looping angle and anatomical ending region
>>> scil_filter_tractogram_anatomically.py tractogram.trk wmparc.nii.gz
    path/to/output/directory --minL 20 --maxL 200 -a 300
# Filter only anatomical ending region, with WM dilation and provided csf mask
>>> scil_filter_tractogram_anatomically.py tractogram.trk wmparc.nii.gz
    path/to/output/directory --csf_bin csf_bin.nii.gz --ctx_dilation_radius 2
"""

import argparse
from copy import deepcopy
import json
import logging
import os
import pkg_resources

from dipy.io.streamline import save_tractogram
from dipy.io.utils import is_header_compatible
import nibabel as nib
import numpy as np
from scipy.spatial import cKDTree

from scilpy.io.streamlines import load_tractogram_with_reference
from scilpy.io.image import get_data_as_mask
from scilpy.io.utils import (add_json_args,
                             add_overwrite_arg,
                             add_processes_arg,
                             add_reference_arg,
                             add_verbose_arg,
                             assert_inputs_exist,
                             assert_output_dirs_exist_and_empty,
                             validate_nbr_processes)
from scilpy.image.labels import get_data_as_labels
from scilpy.segment.streamlines import filter_grid_roi
from scilpy.tractanalysis.features import remove_loops_and_sharp_turns
from scilpy.tractograms.streamline_operations import \
    filter_streamlines_by_length
from scilpy.tractograms.tractogram_operations import \
    perform_tractogram_operation_on_sft
from scilpy.utils.streamlines import filter_tractogram_data

EPILOG = """
    References:
        [1] Jörgens, D., Descoteaux, M., Moreno, R., 2021. Challenges for
        tractogram ﬁltering. In: Özarslan, E., Schultz, T., Zhang, E., Fuster,
        A. (Eds.), Anisotropy Across Fields and Scales. Springer. Mathematics
        and Visualization.
        [2] Legarreta, J., Petit, L., Rheault, F., Theaud, G., Lemaire, C.,
        Descoteaux, M., Jodoin, P.M. Filtering in tractography using
        autoencoders (FINTA). Medical Image Analysis. 2021
    """

def _build_arg_parser():
    p = argparse.ArgumentParser(formatter_class=argparse.RawTextHelpFormatter,
                                epilog=EPILOG, description=__doc__)

    p.add_argument('in_tractogram',
                   help='Path of the input tractogram file.')
    p.add_argument('in_wmparc',
                   help='Path of the white matter parcellation atlas\n' +
                        '(.nii or .nii.gz)')
    p.add_argument('out_path',
                   help='Path to the output files.')

    p.add_argument('--minL', default=0., type=float,
                   help='Minimum length of streamlines, in mm. [%(default)s]')
    p.add_argument('--maxL', default=np.inf, type=float,
                   help='Maximum length of streamlines, in mm. [%(default)s]')
    p.add_argument('-a', dest='angle', default=np.inf, type=float,
                   help='Maximum looping (or turning) angle of\n' +
                        'a streamline, in degrees. [%(default)s]')

    p.add_argument('--csf_bin',
                   help='Allow CSF endings filtering with this binary\n' +
                        'mask instead of using the atlas (.nii or .nii.gz)')
    p.add_argument('--ctx_dilation_radius', type=float, default=0.,
                   help='Cortical labels dilation radius, in mm.\n' +
                        ' [%(default)s]')
    p.add_argument('--save_intermediate_tractograms', action='store_true',
                   help='Save accepted and discarded streamlines\n' +
                        ' after each step.')
    p.add_argument('--save_volumes', action='store_true',
                   help='Save volumetric images (e.g. binarised label\n' +
                        ' images, etc) in the filtering process.')
    p.add_argument('--save_counts', action='store_true',
                   help='Save the streamline counts to a file (.json)')
    p.add_argument('--save_rejected', action='store_true',
                   help='Save rejected streamlines to output tractogram.')
    p.add_argument('--no_empty', action='store_true',
                   help='Do not write file if there is no streamlines.')

    add_processes_arg(p)
    add_reference_arg(p)
    add_verbose_arg(p)
    add_overwrite_arg(p)
    add_json_args(p)
    return p

def load_wmparc_labels():
    """
    Load labels dictionary of different parcellations from the
    Desikan-Killiany atlas
    """
    resource_package = pkg_resources.get_distribution('scilpy').location
    labels_path = os.path.join(
        resource_package, 'data/LUT/dk_aggregate_structures.json')
    with open(labels_path) as labels_file:
        labels = json.load(labels_file)
    return labels

def binarize_labels(atlas, label_list):
    """
    Create a binary mask from specific labels in an atlas (numpy array)
    """
    mask = np.zeros(atlas.shape, dtype=np.uint16)
    for label in label_list:
        is_label = atlas == label
        mask[is_label] = 1

    return mask

def dilate_mask(mask, mask_shape, vox_size, radius):
    """
    Dilate the foreground in a binary mask according to a radius (in mm)
    """
    is_to_dilate = mask == 1
    is_background = mask == 0

    # Get the list of indices
    background_pos = np.argwhere(is_background) * vox_size
    label_pos = np.argwhere(is_to_dilate) * vox_size
    ckd_tree = cKDTree(label_pos)

    # Compute the nearest labels for each voxel of the background
    dist, indices = ckd_tree.query(
        background_pos, k=1, distance_upper_bound=radius,
        workers=-1)

    # Associate indices to the nearest label (in distance)
    valid_nearest = np.squeeze(np.isfinite(dist))
    id_background = np.flatnonzero(is_background)[valid_nearest]
    id_label = np.flatnonzero(is_to_dilate)[indices[valid_nearest]]

    # Change values of those background
    mask = mask.flatten()
    mask[id_background.T] = mask[id_label.T]
    mask = mask.reshape(mask_shape)

    return mask

def create_dir(out_path, dir_name):
    """
    Create a directory named ``dir_name`` at ``out_path``
    """
    new_path = os.path.join(out_path, dir_name)
    if not os.path.exists(new_path):
        os.mkdir(new_path)
    return new_path

def save_intermediate_sft(sft, outliers_sft, new_path, in_sft_name,
                          step_name, steps_combined, ext, no_empty):
    """
    Save the provided stateful tractograms.
    """
    sft_name = os.path.join(new_path, in_sft_name + "_" + steps_combined + ext)
    outliers_sft_name = os.path.join(
        new_path, in_sft_name + "_" + steps_combined + "_outliers" + ext)

    if len(sft.streamlines) == 0:
        if no_empty:
            logging.debug("The file" + sft_name +
                          " won't be written (0 streamlines)")
        save_tractogram(sft, sft_name)
    else:
        save_tractogram(sft, sft_name)

    if len(outliers_sft.streamlines):
        if no_empty:
            logging.debug("The file" + outliers_sft_name +
                          " won't be written (0 streamlines)")
        save_tractogram(outliers_sft, outliers_sft_name)
    else:
        save_tractogram(outliers_sft, outliers_sft_name)

def compute_outliers(sft, new_sft):
    """
    Return a stateful tractogram whose streamlines are the difference of the
    two input stateful tractograms
    """
    outliers_sft, _ = perform_tractogram_operation_on_sft('difference_robust',
                                                          [sft, new_sft],
                                                          precision=3,
                                                          no_metadata=True,
                                                          fake_metadata=False)
    return outliers_sft

def save_rejected(sft, new_sft, rejected_sft_name, no_empty):
    """
    Save rejected streamlines
    """
    rejected_sft = compute_outliers(sft, new_sft)

    if len(rejected_sft.streamlines) == 0:
        if no_empty:
            logging.debug("The file" + rejected_sft_name +
                          " won't be written (0 streamlines)")
            return

    save_tractogram(rejected_sft, rejected_sft_name)

def display_count(o_dict, indent, sort_keys):
    """
    Display the streamline count.
    """
    o_dict_str = json.dumps(o_dict, indent=indent, sort_keys=sort_keys)
    logging.debug("Streamline count:\n{}".format(o_dict_str))

def save_count(o_dict, out_path, indent, sort_keys):
    """
    Save the streamline count to a JSON file.
    """
    fname = os.path.join(out_path, "streamline_count.json")
    with open(fname, 'w') as outfile:
        json.dump(o_dict, outfile, indent=indent, sort_keys=sort_keys)

def main():
    parser = _build_arg_parser()
    args = parser.parse_args()

    assert_inputs_exist(parser, args.in_tractogram)
    assert_inputs_exist(parser, args.in_wmparc)
    assert_output_dirs_exist_and_empty(parser, args,
                                       args.out_path,
                                       create_dir=True)

    nbr_cpu = validate_nbr_processes(parser, args)

    if args.verbose:
        logging.getLogger().setLevel(logging.DEBUG)

    if args.angle <= 0:
        parser.error('Angle "{}" '.format(args.angle) +
                     'must be greater than or equal to 0')
    if args.ctx_dilation_radius < 0:
        parser.error('Cortex dilation radius "{}" '.format(
                     args.ctx_dilation_radius) + 'must be greater than 0')
    sft = load_tractogram_with_reference(parser, args, args.in_tractogram)
    sft.to_vox()
    sft.to_corner()

    img_wmparc = nib.load(args.in_wmparc)
    if not is_header_compatible(img_wmparc, sft):
        parser.error('Headers from the tractogram and the wmparc are '
                     'not compatible.')
    if args.csf_bin:
        img_csf = nib.load(args.csf_bin)
        if not is_header_compatible(img_csf, sft):
            parser.error('Headers from the tractogram and the CSF mask are '
                         'not compatible.')

    if args.minL == 0 and np.isinf(args.maxL):
        logging.debug("You have not specified minL nor maxL. Output will "
                      "not be filtered according to length!")
    if np.isinf(args.angle):
        logging.debug("You have not specified the angle. Loops will "
                      "not be filtered!")
    if args.ctx_dilation_radius == 0:
        logging.debug("You have not specified the cortex dilation radius. "
                      "The wmparc atlas will not be dilated!")

    o_dict = {}
    step_dict = ['length', 'no_csf', 'end_in_atlas', 'no_loops']
    wm_labels = load_wmparc_labels()

    in_sft_name = os.path.splitext(os.path.basename(args.in_tractogram))[0]
    out_sft_rootname = in_sft_name + "_filtered"
    _, ext = os.path.splitext(args.in_tractogram)
    out_sft_name = os.path.join(args.out_path,
                                out_sft_rootname + ext)

    if args.save_rejected:
        initial_sft = deepcopy(sft)
        rejected_sft_name = os.path.join(args.out_path,
                                         in_sft_name +
                                         "_rejected" + ext)

    # STEP 1 - Filter length
    step = step_dict[0]
    steps_combined = step
    new_sft = filter_streamlines_by_length(sft, args.minL, args.maxL)
    # Streamline count before and after filtering lengths
    o_dict[in_sft_name + ext] =\
        dict({'streamline_count': len(sft.streamlines)})
    o_dict[in_sft_name + '_' + steps_combined + ext] =\
        dict({'streamline_count': len(new_sft.streamlines)})

    if args.save_intermediate_tractograms:
        outliers_sft = compute_outliers(sft, new_sft)
        new_path = create_dir(args.out_path, '01-' + step)
        save_intermediate_sft(new_sft, outliers_sft, new_path, in_sft_name,
                              step, steps_combined, ext, args.no_empty)
        o_dict[in_sft_name + '_' + steps_combined + '_outliers' + ext] =\
            dict({'streamline_count': len(outliers_sft.streamlines)})

    if len(new_sft.streamlines) == 0:
        if args.no_empty:
            logging.debug("The file {} won't be written".format(
                          out_sft_name) + "(0 streamlines after "
                          + step + " filtering).")

            if args.verbose:
                display_count(o_dict, args.indent, args.sort_keys)
            if args.save_counts:
                save_count(o_dict, args.out_path, args.indent, args.sort_keys)
            if args.save_rejected:
                save_tractogram(initial_sft, rejected_sft_name)
            return

        logging.debug('The file {} contains 0 streamlines after '.format(
                      out_sft_name) + step + ' filtering')
        save_tractogram(new_sft, out_sft_name)

        if args.save_rejected:
            save_rejected(initial_sft, new_sft,
                          rejected_sft_name, args.no_empty)
        if args.verbose:
            display_count(o_dict, args.indent, args.sort_keys)
        if args.save_counts:
            save_count(o_dict, args.out_path, args.indent, args.sort_keys)
        return

    sft = new_sft

    # STEP 2 - Filter CSF
    step = step_dict[1]
    steps_combined += "_" + step

    # Mask creation
    if args.csf_bin:
        mask = get_data_as_mask(img_csf)
    else:
        atlas = get_data_as_labels(img_wmparc)
        mask = binarize_labels(atlas, wm_labels["csf_labels"])

    # Filter tractogram
    new_sft, _ = filter_grid_roi(sft, mask, 'any', True)
    # Streamline count after filtering CSF endings
    o_dict[in_sft_name + '_' + steps_combined + ext] =\
        dict({'streamline_count': len(new_sft.streamlines)})

    if args.save_volumes:
        new_path = create_dir(args.out_path, '02-' + step)
        if not args.csf_bin:
            nib.save(nib.Nifti1Image(mask, img_wmparc.affine,
                                     img_wmparc.header),
                     os.path.join(new_path, 'csf_bin' + '.nii.gz'))

    if args.save_intermediate_tractograms:
        outliers_sft = compute_outliers(sft, new_sft)
        new_path = create_dir(args.out_path, '02-' + step)
        save_intermediate_sft(new_sft, outliers_sft, new_path, in_sft_name,
                              step, steps_combined, ext, args.no_empty)
        o_dict[in_sft_name + '_' + steps_combined + '_outliers' + ext] =\
            dict({'streamline_count': len(outliers_sft.streamlines)})

    if len(new_sft.streamlines) == 0:
        if args.no_empty:
            logging.debug("The file {} won't be written".format(
                          out_sft_name) + "(0 streamlines after "
                          + step + " filtering).")

            if args.verbose:
                display_count(o_dict, args.indent, args.sort_keys)
            if args.save_counts:
                save_count(o_dict, args.out_path, args.indent, args.sort_keys)
            if args.save_rejected:
                save_tractogram(sft, rejected_sft_name)
            return

        logging.debug('The file {} contains 0 streamlines after '.format(
                      out_sft_name) + step + ' filtering')
        save_tractogram(new_sft, out_sft_name)

        if args.save_rejected:
            save_rejected(initial_sft, new_sft,
                          rejected_sft_name, args.no_empty)
        if args.verbose:
            display_count(o_dict, args.indent, args.sort_keys)
        if args.save_counts:
            save_count(o_dict, args.out_path, args.indent, args.sort_keys)
        return

    sft = new_sft

    # STEP 3 - Filter WM endings
    step = step_dict[2]
    steps_combined += "_" + step

    # Mask creation
    ctx_fs_labels = wm_labels["ctx_lh_fs_labels"] + \
        wm_labels["ctx_rh_fs_labels"]
    vox_size = np.reshape(img_wmparc.header.get_zooms(), (1, 3))
    atlas_wm = get_data_as_labels(img_wmparc)
    atlas_shape = atlas_wm.shape
    wmparc_ctx = binarize_labels(atlas_wm, ctx_fs_labels)
    wmparc_nuclei = binarize_labels(atlas_wm, wm_labels["nuclei_fs_labels"])

    # Dilation of cortex
    if args.ctx_dilation_radius:
        ctx_mask = dilate_mask(wmparc_ctx, atlas_shape, vox_size,
                               args.ctx_dilation_radius)
    else:
        ctx_mask = wmparc_ctx

    freesurfer_mask = np.zeros(atlas_shape, dtype=np.uint16)
    freesurfer_mask[np.logical_or(wmparc_nuclei, ctx_mask)] = 1

    # Filter tractogram
    new_sft, _ = filter_grid_roi(sft, freesurfer_mask, 'both_ends', False)

    # Streamline count after final filtering
    o_dict[in_sft_name + '_' + steps_combined + ext] =\
        dict({'streamline_count': len(new_sft.streamlines)})

    if args.save_volumes:
        new_path = create_dir(args.out_path, '03-' + step)
        nib.save(nib.Nifti1Image(freesurfer_mask, img_wmparc.affine,
                                 img_wmparc.header),
                 os.path.join(new_path, 'atlas_bin' + '.nii.gz'))

    if args.save_intermediate_tractograms:
        outliers_sft = compute_outliers(sft, new_sft)
        new_path = create_dir(args.out_path, '03-' + step)
        save_intermediate_sft(new_sft, outliers_sft, new_path, in_sft_name,
                              step, steps_combined, ext, args.no_empty)
        o_dict[in_sft_name + '_' + steps_combined + '_outliers' + ext] =\
            dict({'streamline_count': len(outliers_sft.streamlines)})

    if len(new_sft.streamlines) == 0:
        if args.no_empty:
            logging.debug("The file {} won't be written".format(
                          out_sft_name) + "(0 streamlines after "
                          + step + " filtering).")

            if args.verbose:
                display_count(o_dict, args.indent, args.sort_keys)
            if args.save_counts:
                save_count(o_dict, args.out_path, args.indent, args.sort_keys)
            if args.save_rejected:
                save_tractogram(sft, rejected_sft_name)
            return

        logging.debug('The file {} contains 0 streamlines after '.format(
                      out_sft_name) + step + ' filtering')
        save_tractogram(new_sft, out_sft_name)

        if args.save_rejected:
            save_rejected(initial_sft, new_sft,
                          rejected_sft_name, args.no_empty)
        if args.verbose:
            display_count(o_dict, args.indent, args.sort_keys)
        if args.save_counts:
            save_count(o_dict, args.out_path, args.indent, args.sort_keys)
        return

    sft = new_sft

    # STEP 4 - Filter loops
    step = step_dict[3]
    steps_combined += "_" + step

    if args.angle != np.inf:
        ids_c = remove_loops_and_sharp_turns(sft.streamlines, args.angle,
                                             num_processes=nbr_cpu)
        new_sft = filter_tractogram_data(sft, ids_c)
    else:
        new_sft = deepcopy(sft)

    # Streamline count after filtering loops
    o_dict[in_sft_name + '_' + steps_combined + ext] =\
        dict({'streamline_count': len(new_sft.streamlines)})

    if args.save_intermediate_tractograms:
        outliers_sft = compute_outliers(sft, new_sft)
        new_path = create_dir(args.out_path, '04-' + step)
        save_intermediate_sft(new_sft, outliers_sft, new_path, in_sft_name,
                              step, steps_combined, ext, args.no_empty)
        o_dict[in_sft_name + '_' + steps_combined + '_outliers' + ext] =\
            dict({'streamline_count': len(outliers_sft.streamlines)})

    if len(new_sft.streamlines) == 0:
        if args.no_empty:
            logging.debug("The file {} won't be written".format(
                          out_sft_name) + "(0 streamlines after "
                          + step + " filtering).")

            if args.verbose:
                display_count(o_dict, args.indent, args.sort_keys)
            if args.save_counts:
                save_count(o_dict, args.out_path, args.indent, args.sort_keys)
            if args.save_rejected:
                save_tractogram(sft, rejected_sft_name)
            return

        logging.debug('The file {} contains 0 streamlines after '.format(
                          out_sft_name) + step + ' filtering')
        save_tractogram(new_sft, out_sft_name)

    if args.verbose:
        display_count(o_dict, args.indent, args.sort_keys)
    if args.save_counts:
        save_count(o_dict, args.out_path, args.indent, args.sort_keys)

    sft = new_sft
    save_tractogram(sft, out_sft_name)
    if args.save_rejected:
        save_rejected(initial_sft, sft, rejected_sft_name, args.no_empty)

if __name__ == "__main__":
    main()


# SAFESYS 0 - Camera Management Module
# Capturing video from webcam w/ opencv
# Written by Kyle Christie xx/7/2020

#library imports
import numpy as np                 # needed for opencv to work
import cv2                         # opencv module for camera manipulation/usage
from datetime import datetime      # datetime used when naming files
import winsound                    # winsound to allow beeping
import os                          # os module used for file management

cap = cv2.VideoCapture(0)

# Capture frame-by-frame
ret, frame = cap.read()
font = cv2.FONT_HERSHEY_SIMPLEX

#write text and place a red circle on the camera GUI
gray = cv2.putText(frame ,'You have been caught!',(10,100), font, 1,(0,0,0),2,cv2.LINE_AA)
cv2.circle(frame,(575,75), 63, (0,0,255), -1)

# try to create folder - handle error (implemented to clean up working directory/have a dedicated area for safesys alerts)
try:
    os.mkdir("Safesys Captures")
except:
    print("Folder already exists")

#use date/time to generate part of the filename
now = datetime.now()
current_time = now.strftime("%H_%M_%S")

winsound.Beep(500,200)

#concat date/time into the below text to create the filename
file_name = "Safesys Captures/safesys_alert_" + current_time + ".png"
print("Saving... " + file_name)

winsound.Beep(300,200)

#save the image to current dir
cv2.imwrite(file_name, frame)

winsound.Beep(200,200)

#Show the UI & img to the user
cv2.imshow('frame', gray)

# When everythings done, release the capture
cap.release()
cv2.destroyAllWindows()


import requests
from requests.exceptions import HTTPError
import re
from bs4 import BeautifulSoup
import itertools
import time

# See https://stackoverflow.com/a/8991553/51685
def chunker(n, iterable):
    it = iter(iterable)
    while True:
        chunk = tuple(itertools.islice(it, n))
        if not chunk:
            return
        yield chunk

def get_ultra_miner_floor() -> list:
    url_headers = {
        "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/39.0.2171.95 Safari/537.36"
    }
    buy_now_url = "https://opensea.io/collection/ultra-miners?search[sortAscending]=true&search[sortBy]=PRICE&search[toggles][0]=BUY_NOW"
    status_code = 999
    while status_code != 200:
        try:
            response = requests.get(url=buy_now_url, headers=url_headers)
            status_code = response.status_code
            # If the response was successful, no Exception will be raised
            response.raise_for_status()
        except HTTPError as http_err:
            print(f"HTTP error occurred: {http_err}")
            print("sleep for 1 second")
            time.sleep(1)
        except Exception as err:
            print(f"Other error occurred: {err}")
            break

    html = str(response.content)

    """
    Use regex to match ULTRA MINER #XXXX
    """
    regex = r"ULTRA MINER \#[0-9]{2,5}"
    pattern = re.compile(regex)
    matches_list = re.findall(pattern, html)
    # print(f"matches_list: {matches_list}")
    """
    Strip "ULTRA MINER #XXXX" to Int
    """
    nft_token_id = list()
    for id in matches_list:
        int_val = int(id.strip("ULTRA MINER #"))
        nft_token_id.append(int_val)
    # print(f"nft_token_id:{nft_token_id}")
    """Remove duplicates"""
    nft_token_id = list(set(nft_token_id))
    return nft_token_id

def get_ultra_price(miner_nft_id: str) -> str:
    BASE_URL = "https://opensea.io/assets"
    NFT_CONTRACT_ADDR = "0x0c6822ca73de6871f27acd9ca05a05b99294b805"

    custom_headers = {
        "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/39.0.2171.95 Safari/537.36"
    }

    REQ_URL = BASE_URL + "/" + NFT_CONTRACT_ADDR + "/" + str(miner_nft_id)
    status_code = 999
    while status_code != 200:
        try:
            response = requests.get(REQ_URL, headers=custom_headers)
            status_code = response.status_code
            # If the response was successful, no Exception will be raised
            response.raise_for_status()
        except HTTPError as http_err:
            print(f"HTTP error occurred: {http_err}")
            print("sleep for 1 second")
            time.sleep(1)
        except Exception as err:
            print(f"Other error occurred: {err}")
            break
    soup = BeautifulSoup(response.text, "html.parser")
    soup_val = soup.find(class_="Price--amount")

    return soup_val.get_text().strip() if soup_val else ""


import tkinter as tk

def calculate_coefficient():
    # Получаем выбранную юзером формулу
    formula = formula_choice.get()

    # Выполняем расчет в соответствии с выбранной формулой
    if formula == 1:
        # Расчет коэффициента по формуле 1
        nд = float(nд_entry.get())
        Sд = float(Sд_entry.get())
        Sл = float(Sл_entry.get())
        Kб = 1.06
        K = (nд * Sд) / (Sл * Kб)
        result_label.config(text="Рассчитанный коэффициент: {:.2f}".format(K))
    elif formula == 2:
        # Расчет коэффициента по формуле 2
        nл = float(nл_entry.get())
        Sл = float(Sл_entry.get())
        Kб = 1.06
        Sзак = float(Sзак_entry.get())
        Kарх = (nл * Sл * Kб) / Sзак
        result_label.config(text="Рассчитанный коэффициент: {:.2f}".format(Kарх))
    elif formula == 3:
        # Расчет коэффициента по формуле 3
        nл = float(nл_entry.get())
        Sл = float(Sл_entry.get())
        Sост = float(Sост_entry.get())
        Sзак = float(Sзак_entry.get())
        Kб = 1.06
        Kарх_ост = (nл * Sл - Sост * 0.75) / (Sзак * Kб)
        result_label.config(text="Рассчитанный коэффициент: {:.2f}".format(Kарх_ост))
    else:
        # Выводим сообщение об ошибке при некорректном выборе формулы
        result_label.config(text="Некорректный выбор формулы.")

def handle_formula_selection():
    # Получаем выбранную юзером формулу
    formula = formula_choice.get()

    if formula == 1:
        # Для формулы 1 отключаем ненужные поля и включаем необходимые
        nд_entry.config(state=tk.NORMAL)
        Sд_entry.config(state=tk.NORMAL)
        Sл_entry.config(state=tk.NORMAL)
        Sзак_entry.config(state=tk.DISABLED)
        nл_entry.config(state=tk.DISABLED)
        Sост_entry.config(state=tk.DISABLED)
    elif formula == 2:
        # Для формулы 2 отключаем ненужные поля и включаем необходимые
        nд_entry.config(state=tk.DISABLED)
        Sд_entry.config(state=tk.DISABLED)
        Sл_entry.config(state=tk.NORMAL)
        Sзак_entry.config(state=tk.NORMAL)
        nл_entry.config(state=tk.NORMAL)
        Sост_entry.config(state=tk.DISABLED)
    elif formula == 3:
        # Для формулы 3 отключаем ненужные поля и включаем необходимые
        nд_entry.config(state=tk.DISABLED)
        Sд_entry.config(state=tk.DISABLED)
        Sл_entry.config(state=tk.NORMAL)
        Sзак_entry.config(state=tk.NORMAL)
        nл_entry.config(state=tk.NORMAL)
        Sост_entry.config(state=tk.NORMAL)

# Основное окно
window = tk.Tk()
window.title("Калькулятор коэффициента")

# Список выбора формулы
formula_choice = tk.IntVar()
formula_label = tk.Label(window, text="Выберите формулу для расчета коэффициента:")
formula_label.pack()

formula1_radio = tk.Radiobutton(window, text="1) K = (nд * Sд) / Sл * Kб", variable=formula_choice, value=1,
                                command=handle_formula_selection)
formula1_radio.pack()

formula2_radio = tk.Radiobutton(window, text="2) Kарх = nл * Sл * Kб / Sзак", variable=formula_choice, value=2,
                                command=handle_formula_selection)
formula2_radio.pack()

formula3_radio = tk.Radiobutton(window, text="3) Kарх(ост) = (nл * Sл - Sост * 0.75) / Sзак * Kб",
                                variable=formula_choice, value=3, command=handle_formula_selection)
formula3_radio.pack()

# Поля ввода
nд_label = tk.Label(window, text="nд - Количество изделий на листе:")
nд_label.pack()
nд_entry = tk.Entry(window, state=tk.DISABLED)
nд_entry.pack()

Sд_label = tk.Label(window, text="Sд - Площадь изделия:")
Sд_label.pack()
Sд_entry = tk.Entry(window, state=tk.DISABLED)
Sд_entry.pack()

Sл_label = tk.Label(window, text="Sл - Площадь листа:")
Sл_label.pack()
Sл_entry = tk.Entry(window, state=tk.DISABLED)
Sл_entry.pack()

Sзак_label = tk.Label(window, text="Sзак - Площадь заказа:")
Sзак_label.pack()
Sзак_entry = tk.Entry(window, state=tk.DISABLED)
Sзак_entry.pack()

nл_label = tk.Label(window, text="nл - Количество листов:")
nл_label.pack()
nл_entry = tk.Entry(window, state=tk.DISABLED)
nл_entry.pack()

Sост_label = tk.Label(window, text="Sост - Площадь остатка:")
Sост_label.pack()
Sост_entry = tk.Entry(window, state=tk.DISABLED)
Sост_entry.pack()

# Кнопка "Рассчитать"
calculate_button = tk.Button(window, text="Рассчитать", command=calculate_coefficient)
calculate_button.pack()

# Для вывода результата
result_label = tk.Label(window, text="")
result_label.pack()

# Главный цикл
window.mainloop()


a = "Prince !"
n = 5
tempstrings = []
#for i in range(len(a)):
    #for j in range(n):
    #print(a[i:n+i])

# determine string length
#s_len = len(a)

# number of substring ex.apple n=3 app, ppl, ple
#subnum = s_len - n + 1

# put substring into list
#for i in range(subnum):
    #tempstrings.append(a[i:i + n])

for i in range(len(a) - n + 1):
    tempstrings.append(a[i:n+i])

print(tempstrings)


#coding=utf-8
#tf里队列的使用

import tensorflow as tf
#声明一个先进先出的队列，指明有两个元素，类型是Int32
q = tf.FIFOQueue(capacity=2,dtypes=tf.int32)
#声明一个随机队列，参数：容量，出对列后的最小值，类型
#q = tf.RandomShuffleQueue(2,1,'int32')
#初始化队列
#里面放两个数：10，0
#通过enqueue_many()函数初始化队列中的元素
#定义初始化操作
#注意，如果一次性入列超过Queue Size的数据，enqueue操作会卡住，直到有数据（被其他线程）从队列取出。对一个已经取空的队列使用dequeue操作也会卡住，直到有新的数据（从其他线程）写入。
init = q.enqueue_many(([10,0],))
#通过dequeue()函数将队列中的第一个元素送出队列，并将这个元素的值存在变量x中，出队列
x=q.dequeue()
y=x+1
#将更新后的元素值重新加入队列中 ,入队列
q_inc = q.enqueue([y])
with tf.Session() as sess:
    init.run()#执行初始化操作
    for _ in range(5):
        #运行q_inc将执行数据出对列，加一，入队列的过程
        v,_= sess.run([x,q_inc])
        print(v)

    quelen = sess.run(q.size())
    for i in range(quelen):
        print(sess.run(q.dequeue()))


import os

# SBATCH fields assumed if the particular field isn't passed to get_script
# can contain unused fields

DEFAULT_QSUB_FIELDS = {
    'sge_memory': 64,
    'sge_memory_unit': 'GB',
    'sge_num_nodes': 1,
    'sge_num_cpus': 10,
    'sge_num_gpus': 1,
    'sge_time_d': 0,
    'sge_time_h': 10,
    'sge_time_m': 0,
    'sge_time_s': 0,
    'sge_job_name': 'myjob',
    #'sge_cluster_name': 'mycluster',
    'sge_entry_point': 'train.py',
    'sge_logdir': 'logs/',
    'sge_gpu_type': 'volta',
    'sge_root_directory': '~/incubator',
}

# a template for the entire submit script
# (bash braces must be escaped by doubling: $var = ${{var}})
# num_jobs, param_arr_init, param_val_assign and param_list are special fields

TEMPLATE = '''
#!/bin/sh
#$ -cwd
#$ -pe smp {sge_num_cpus}
#$ -l h_vmem={sge_memory}{sge_memory_unit}
#$ -N {sge_job_name}
#$ -l gpu={sge_num_gpus}
#$ -l gpu_type={sge_gpu_type}
#$ -l h_rt={sge_time_h}:{sge_time_m}:{sge_time_s}
#$ -t 1-{num_jobs}
#$ -o {sge_logdir}
#$ -e {sge_logdir}

{param_arr_init}
trial=${{SGE_TASK_ID}}
{param_val_assign}

module purge
module load cuda anaconda3 vulkan-sdk
conda activate escape

export PYTHONUNBUFFERED=1

cd {sge_root_directory}

python {sge_entry_point} {script_args}

'''.strip()

# functions for making bash expressions
# bash braces are escaped by doubling

def _mth(exp):
    return '$(( %s ))' % exp

def _len(arr):
    return '${{#%s[@]}}' % arr

def _get(arr, elem):
    return '"${{%s[%s]}}"' % (arr, elem)

def _eq(var, val):
    return '%s=%s' % (var, val)

def _op(a, op, b):
    return _mth('%s %s %s' % (a, op, b))

def _arr(arr):
    return '( %s )' % ' '.join(map(str, arr))

def _seq(a, b, step):
    return '($( seq %d %d %d ))' % (a, step, b)

def _var(var):
    return '${%s}' % var

# templates for param array construction and element access

PARAM_ARR = '{param}_values'
PARAM_EXPRS = {
    'param_arr_init':
        _eq(PARAM_ARR, '{values}'),
    'param_val_assign': {
        'assign':
            _eq('{param}', _get(PARAM_ARR, _op('trial', '%', _len(PARAM_ARR)))),
        'increment':
            _eq('trial', _op('trial', '/', _len(PARAM_ARR)))
    }
}

def _to_bash(obj):
    if isinstance(obj, range):
        return _seq(obj.start, obj.stop - 1, obj.step)
    if isinstance(obj, list) or isinstance(obj, tuple):
        return _arr(obj)
    raise ValueError('Unknown object type %s' % type(obj).__name__)

def _get_params_bash(params, values):
    # builds bash code to perform the equivalent of
    '''
    def get_inds(params, ind):
        inds = []
        for length in map(len, params):
            inds.append(ind % length)
            ind //= length
        return inds[::-1]
    '''

    # get lines of bash code for creating/accessing param arrays
    init_lines = []
    assign_lines = []
    init_temp = PARAM_EXPRS['param_arr_init']
    assign_temps = PARAM_EXPRS['param_val_assign']

    for param, vals in zip(params, values):
        param = param.replace('-', '_')
        init_lines.append(
            init_temp.format(param=param, values=_to_bash(vals)))
        assign_lines.append(
            assign_temps['assign'].format(param=param))
        assign_lines.append(
            assign_temps['increment'].format(param=param))

    # remove superfluous final trial reassign
    assign_lines.pop()

    return init_lines, assign_lines

def get_script(fields, params, param_order=None):
    '''
    returns a string of a SLURM submission script using the passed fields
    and which creates an array of jobs which sweep the given params
    fields:      dict of SLURM field names to their values. type is ignored
    params:      a dict of (param names, param value list) pairs.
                 The param name is the name of the bash variable created in
                 the submission script which will contain the param's current
                 value (for that SLURM job instance). param value list is
                 a list (or range instance) of the values the param should take,
                 to be run once against every other possible configuration of all params.
    param_order: a list containing all param names which indicates the ordering
                 of the params in the sweep. The last param changes every
                 job number. If not supplied, uses an arbitrary order
    '''

    # check arguments have correct type
    assert isinstance(fields, dict)
    assert isinstance(params, dict)
    assert (isinstance(param_order, list) or
            isinstance(param_order, tuple) or
            param_order == None)
    if param_order == None:
        param_order = list(params.keys())

    # check each field appears in the template
    for field in fields:
        if ('{%s}' % field) not in TEMPLATE:
            raise ValueError('passed field %s unused in template' % field)

    # calculate total number of jobs (minus 1; SLURM is inclusive)
    num_jobs = 1
    for vals in params.values():
        num_jobs *= len(vals)

    # get bash code for param sweeping
    init_lines, assign_lines = _get_params_bash(
        param_order, [params[key] for key in param_order])

    # build args
    script_args = ''
    for key in param_order:
        script_args += f' --{key}="${{{key.replace("-", "_")}}}"'

    # build template substitutions (overriding defaults)
    subs = {
        'param_arr_init': '\n'.join(init_lines),
        'param_val_assign': '\n'.join(assign_lines),
        'param_list': ', '.join(map(_var, param_order)),
        'num_jobs': num_jobs,
        'script_args': script_args
    }
    for key, val in DEFAULT_QSUB_FIELDS.items():
        subs[key] = val
    for key, val in fields.items():
        subs[key] = val

    return TEMPLATE.format(**subs)

def save_script(filename, fields, params, param_order=None):
    '''
    creates and writes to file a SLURM submission script using the passed
    fields and which creates an array of jobs which sweep the given params
    fields:      dict of SLURM field names to their values. type is ignored
    params:      a dict of (param names, param value list) pairs.
                 The param name is the name of the bash variable created in
                 the submission script which will contain the param's current
                 value (for that SLURM job instance). param value list is
                 a list (or range instance) of the values the param should take,
                 to be run once against every other possible configuration of all params.
    param_order: a list containing all param names which indicates the ordering
                 of the params in the sweep. The last param changes every
                 job number. If not supplied, uses an arbitrary order
    '''

    script_str = get_script(fields, params, param_order)
    if ('/' in filename) or ('\\' in filename):
        os.makedirs(os.path.dirname(filename), exist_ok=True)
    with open(filename, 'w') as file:
        file.write(script_str)

if __name__ == '__main__':
    # A simple example for griddly clusters environment
    script = get_script(
        {
            'sge_time_h': 10,
            'sge_num_gpus': 1,
            'sge_num_cpus': 10,
            'sge_job_name': 'sge_test'
        },
        {
            'gym-id': ['GDY-Clusters-0'],
            'exp-name': ['griddly-clusters-sge-sweep'],
            'track': ['True'],
            'total-timesteps': [50000000],
            'processes': [8],
            'num-envs': [128],
            'learning-rate': [0.005],
            'ent-coef': [0.2],
            'eval-interval': [50000],
            'eval-steps': [300],
            'eval-num-env': [4]
        })

    with open(f'submit-array.sh', 'w') as f:
        f.write(script)


""" Library of functions to build the CCD calibration files for image processing to reduce data from the Tull coude spectrograph

Created by DMK on 9/24/2023

Last updated by DMK on 10/14/2023
"""

##### Imports

import numpy as np
import matplotlib.pyplot as plt

from astropy.io import fits
from os import path

##### Functions

def build_super_bias( bias_file_names, read_noise ):
    """ Function to read in the bias images and median combine them into a super bias to apply to other frames

    Parameters
    ----------
    bias_file_names : list
        List of file names (type str) for the raw bias frames
    read_noise : float
        The read noise (in ADU, converted from the header value which is given in electrons)

    Returns
    -------
    super_bias : HDUList
        Multi-extension FITS ImageHDU list with the median combined super bias and associated error
    """

    # Initialize an array to hold the bias images
    bias_cube = []

    for i_file, file_name in enumerate( bias_file_names ):

        # Read in the bias file
        bias_image = fits.getdata( file_name )

        # Append to the cube of all bias images
        bias_cube.append( bias_image )

    # Get the super bias -- median combine the bias images, and add in read noise to bias error
    super_bias_flux  = np.nanmedian( bias_cube, axis = 0 )
    super_bias_error = np.sqrt( super_bias_flux + read_noise ** 2.0 )

    ### Output -- into FITS object

    # Put the super bias data into FITS ImageHDU objects
    super_bias_flux_hdu  = fits.ImageHDU( super_bias_flux, name = 'bias flux' )
    super_bias_error_hdu = fits.ImageHDU( super_bias_error, name = 'bias error' )

    # Output to a FITS multi-extension HDU object (with an empty Primary HDU to hold some header information)
    super_bias = fits.HDUList( [ fits.PrimaryHDU(), super_bias_flux_hdu, super_bias_error_hdu ] )

    # Add some keywords to the output super bias object
    super_bias[0].header['nbiases'] = ( len( bias_file_names ), 'Number of bias frames combined' )
    super_bias[0].header['rdnoise'] = ( read_noise, 'Read noise added to error' )
    super_bias[0].header['history'] = 'Median combined super bias frame'

    return super_bias

def build_flat_field( flat_file_names, read_noise, super_bias ):
    """ Function to read in flat field images and median combine them into the flat field for image processing

    Parameters
    ----------
    flat_file_names : list
        List of file names (type str) for the raw flat field frames
    read_noise : float
        The read noise (in ADU, converted from the header value which is given in electrons)
    super_bias : HDUList
        Multi-extension FITS ImageHDU list with the median combined super bias and associated error created with build_super_bias.

    Returns
    -------
    flat_field : HDUList
        Multi-extension FITS ImageHDU list with the median combined flat field and associated error.
    """

    # Initialize an array to hold the flat field images
    flat_cube = []

    for i_file, file_name in enumerate( flat_file_names ):

        # Read in the bias file
        flat_image = fits.getdata( file_name )

        # Append to the cube of all bias images
        flat_cube.append( flat_image )

    # Get the flat field -- median combine the flat field images and subtract the super bias
    flat_field_median = np.nanmedian( flat_cube, axis = 0 )
    flat_field_flux   = flat_field_median - super_bias['bias flux'].data

    # Get the flat field errors (flat field, read noise, and super bias)
    flat_field_error = np.sqrt( flat_field_median + read_noise ** 2.0 + super_bias['bias error'].data ** 2.0 )

    ### Output

    # Put the flat field flux and error into ImageHDU objects, with minor math to make the flat field scale between 0 and 1
    flat_field_flux_hdu  = fits.ImageHDU( ( flat_field_flux - np.nanmin( flat_field_flux ) ) / np.nanmax( flat_field_flux - np.nanmin( flat_field_flux ) ), name = 'flat flux' )
    flat_field_error_hdu = fits.ImageHDU( flat_field_error / np.nanmax( flat_field_flux - np.nanmin( flat_field_flux ) ), name = 'flat error' )

    # Put into an HDUList
    flat_field = fits.HDUList( [ fits.PrimaryHDU(), flat_field_flux_hdu, flat_field_error_hdu ] )

    # Add some keywords to the output super bias object
    flat_field[0].header['nflats']  = ( len( flat_file_names ), 'Number of flat field frames combined' )
    flat_field[0].header['rdnoise'] = ( read_noise, 'Read noise added to error' )
    flat_field[0].header['history'] = 'Median combined flat field frame'
    flat_field[0].header['history'] = 'Super bias subtracted'

    return flat_field

def make_bad_pixel_mask( super_bias, flat_field, bias_bpm_percentile, flat_field_bpm_limit ):
    """ Function to create a bad pixel mask using the super bias and flat field.

    Parameters
    ----------
    super_bias : HDUList
        Multi-extension FITS ImageHDU list with the median combined super bias and associated error created with build_super_bias.
    flat_field : HDUList
        Multi-extension FITS ImageHDU list with the median combined flat field and associated error created with build_flat_field.
    bias_bpm_percentile : float
        The percentile above which super bias counts are marked as bad pixels (hot pixels). This is defined in the config file.
    flat_field_bpm_limit : float
        The flat field value below which a pixel is marked as bad. This is defined in the config file.

    Returns
    -------
    bad_pixel_mask : FITSHDU
        The bad pixel mask created using the super bias and flat field. Bad pixels are valued 0, good pixels are valued 1.
    """

    # Get the super bias flux value for the percentile above which to label bad pixels, as defined in the top level config
    bias_percentile_cut_value = np.nanpercentile( super_bias['bias flux'].data, bias_bpm_percentile )

    # Define the bad pixel mask as indices where the super bias flux is above the percentile limit, or the flat field is very small
    bad_pixel_mask_indices = np.where( ( super_bias['bias flux'].data > bias_percentile_cut_value ) | ( flat_field['flat flux'].data < flat_field_bpm_limit ) )

    ### Output the bad pixel mask as a fits file: 0s are bad pixels, 1s are good pixels

    bad_pixel_mask = np.ones( super_bias['bias flux'].data.shape )

    bad_pixel_mask[bad_pixel_mask_indices] = 0

    # FITS HDU
    bad_pixel_mask_hdu = fits.PrimaryHDU( bad_pixel_mask )

    bad_pixel_mask_hdu.header['history'] = 'Bias above {:.2f}'.format( bias_percentile_cut_value )
    bad_pixel_mask_hdu.header['history'] = 'Flat field below {:.5f}'.format( flat_field_bpm_limit )

    return bad_pixel_mask_hdu

def cal_image_2d_plot( image, figure_title, file_name, bpm = None ):
    """ Function to make a 2D image plot of an input 2D data array. Generalized to plot any input image.

    Parameters
    ----------
    image : array
        The 2D array to show.
    title : str
        The title of the plot (here to mark what type of image is plotted).
    file_name : str
        The file name to save the plot to.
    bpm : array, optional
        The 2D array defining the bad pixel mask. This can be input and overplotted the input data image. The default is None.

    Returns
    -------
    None.
    """

    plt.clf()

    # Plot the image
    plt.imshow( np.log10( image ), cmap = 'gray', aspect = 'auto', interpolation = 'none' )

    # If the bad pixel mask is given, plot the bad pixels on top!
    if bpm is not None:
        bpm_pixels = np.where( bpm == 0 )

        # Invert the x and y BPM for imshow
        plt.plot( bpm_pixels[1], bpm_pixels[0], '.', ms = 1, c = '#db6d1b', label = 'Bad pixel mask' )

        plt.legend( fontsize = 'small' )

    # Add colorbar
    plt.colorbar()

    # Add title passed to the function
    plt.title( figure_title )

    # Save the figure
    plt.savefig( file_name, bbox_inches = 'tight', pad_inches = 0.05 )
    plt.clf()

    return None

##### Wrapper for building all calibration files

def build_calibrations( header_df, bias_frame_indices, flat_frame_indices, config ):
    """ Main function to build all of the CCD calibrations, using the above functions for the individual calibrations.

    Parameters
    ----------
    header_df : pandas DataFrame
        The compiled information from the file headers.
    bias_frame_indices : list
        List of indices of the bias frames for the header dataframe.
    flat_frame_indices : list
        List of indices of the flat frames for the header dataframe.
    config : dict
        The overall config file defined using YAML with all parameters for running the reduction and analysis pipeline.

    Returns
    -------
    None.
    """

    ### Build the files

    # First the super bias
    print( 'Reading bias files and creating super bias' )

    super_bias = build_super_bias( header_df['file_name'].values[bias_frame_indices], ( header_df['read_noise'] / header_df['gain'] ).values[bias_frame_indices][0] )
    super_bias.writeto( path.join( config['general']['reduction_dir'], 'cals', 'super_bias.fits' ), overwrite = True, checksum = True )

    # Next the flat field
    print( 'Reading flat files and creating flat field' )

    flat_field = build_flat_field( header_df['file_name'].values[flat_frame_indices], ( header_df['read_noise'] / header_df['gain'] ).values[flat_frame_indices][0], super_bias )
    flat_field.writeto( path.join( config['general']['reduction_dir'], 'cals', 'flat_field.fits' ), overwrite = True, checksum = True )

    # Last the bad pixel mask
    print( 'Building the bad pixel mask from the super bias and flat field' )

    bad_pixel_mask = make_bad_pixel_mask( super_bias, flat_field, config['calibrations']['bias_bpm_percentile'], config['calibrations']['flat_field_bpm_limit'] )
    bad_pixel_mask.writeto( path.join( config['general']['reduction_dir'], 'cals', 'bad_pixel_mask.fits' ), overwrite = True, checksum = True )

    ### Make quick check plots for the calibration files!

    # First for the super bias
    super_bias_title = 'Super Bias, Median Value = {0:.2f}'.format( np.nanmedian( super_bias['bias flux'].data ) )
    cal_image_2d_plot( super_bias['bias flux'].data, super_bias_title, path.join( config['general']['reduction_dir'], 'cals', 'super_bias.pdf' ) )

    # Next for the flat fiel
    flat_field_title = 'Flat Field'
    cal_image_2d_plot( flat_field['flat flux'].data, flat_field_title, path.join( config['general']['reduction_dir'], 'cals', 'flat_field.pdf' ) )

    # Last for the bad pixel mask
    bpm_title = 'Bad Pixel Mask, plotted on Super Bias'
    cal_image_2d_plot( super_bias['bias flux'].data, bpm_title, path.join( config['general']['reduction_dir'], 'cals', 'bad_pixel_mask.pdf' ), bpm = bad_pixel_mask.data )

    return None