コード例 #1
0
ファイル: run_modes.py プロジェクト: dmyersturnbull/sauronx
 def run(self, keep: bool, upload: bool, assume_clean: bool,
         sensor_plot: bool, plot_audio: bool) -> None:
     submission_hash = str(config["sauron.test_submission_hash"])
     output_dir = config.get_output_dir(submission_hash)
     args = SubmissionRunnerArgs(
         dark=None,
         local=not upload,
         overwrite=True,
         keep_frames=False,
         store_to=None,
         assume_clean=assume_clean,
         sensor_plot=sensor_plot,
         plot_audio=plot_audio,
         halt_after_acquisition=False,
         ignore_prior=True,
     )
     with Valar() as db:
         SauronxLock().lock(None)
         try:
             # these will get shut down afterward
             audio = SauronxAudio()
             audio.start()
             b = Board()
             b.init()
             ss = SubmissionRunner(args, db, audio, b)
             ss.run(submission_hash, True)
             if not keep:
                 shutil.rmtree(output_dir, ignore_errors=True)
         finally:
             SauronxLock().unlock(ignore_warning=True)
コード例 #2
0
ファイル: main.py プロジェクト: dmyersturnbull/sauronx
 def log(self) -> None:
     parser = argparse.ArgumentParser(
         description="Show the recent history of submissions on this Sauron."
     )
     self._parse_args(parser)
     lookup = LookupTools()
     with Valar():
         lookup.history()
コード例 #3
0
 def __init__(
     self,
     submission_hash: Optional[str],
     db: Optional[Valar] = None,
     acquisition_start: bool = False,
     ignore_warnings: bool = False,
 ) -> None:
     """If submission_hash is None, don't try to access submission_obj or status_obj or call update_status."""
     self.db = None  # type: Valar
     self._internal_db = None  # type: bool
     self.submission_hash = None  # type: Optional[str]
     self.submission_obj = None  # type: Submissions
     self.sauron_obj = None  # type: Saurons
     self.status_obj = None  # type: Optional[SubmissionRecords]
     self._internal_db = db is None  # don't close if it was built outside
     self.ignore_warnings = ignore_warnings
     self.acquisition_start = acquisition_start
     self.submission_hash = submission_hash
     if self.submission_hash is not None:
         processing_list = ProcessingList.now()
         if self.submission_hash in processing_list:
             warn_user(
                 "Refusing to touch {}:".format(submission_hash),
                 "Another SauronX process appears to be handling it.",
                 "If this is wrong, run 'sauronx clear {}' to continue.".
                 format(submission_hash),
             )
             raise LockedError(
                 "Refusing to touch {}: Another SauronX process appears to be handling it."
                 .format(submission_hash))
     if db is None:
         self.db = Valar()
     else:
         self.db = db
     try:
         if "connection.notification.slack_info_file" in config:
             with open(
                     str(config["connection.notification.slack_info_file"]),
                     "r") as file:
                 self._slack_hook = file.readline()
                 self._slack_user_dict = json.loads(file.readline())
     except Exception:
         warn_user("Failed to call notification")
         logger.error("Failed to call notification", exc_info=True)
コード例 #4
0
ファイル: main.py プロジェクト: dmyersturnbull/sauronx
 def lookup(self) -> None:
     parser = argparse.ArgumentParser(
         description="List something in Valar",
         usage=
         "sauronx lookup [users|experiments|submissions|history|batteries|assays|templates|configs|stimuli|audio_files|sensors|plates|runs|plate_types|saurons|locations]",
     )
     parser.add_argument("what", help="What to list")
     args = self._parse_args(parser)
     with Valar():
         getattr(LookupTools(), args.what)()
コード例 #5
0
    def update(
        self, text: str,
        when: datetime.datetime = datetime.datetime.now()) -> None:
        with Valar() as valar:
            import valarpy.model as model

            conf = model.SauronConfigs(
                datetime_changed=when,
                description=text,
                sauron=config.sauron_id,
                created=datetime.datetime.now(),
            )
            conf.save()
        print(Fore.BLUE + "Created new sauron_config {}".format(conf.id))
コード例 #6
0
ファイル: run_modes.py プロジェクト: dmyersturnbull/sauronx
 def run(
     self,
     hashes: List[str],
     dark: Optional[int],
     local: bool,
     overwrite: bool,
     keep_frames: bool,
     store_to: Optional[str],
     assume_clean: bool,
     sensor_plot: bool,
     plot_audio: bool,
     halt_after_acquisition: bool,
     ignore_prior: bool,
     keep_lock: bool,
 ) -> None:
     args = SubmissionRunnerArgs(
         dark=dark,
         local=local,
         overwrite=overwrite,
         keep_frames=keep_frames,
         store_to=store_to,
         assume_clean=assume_clean,
         sensor_plot=sensor_plot,
         plot_audio=plot_audio,
         halt_after_acquisition=halt_after_acquisition,
         ignore_prior=ignore_prior,
     )
     SauronxLock().lock(None)
     try:
         b = Board()
         b.init()
         audio = SauronxAudio()
         audio.start()
         with Valar() as db:
             ss = SubmissionRunner(args, db, audio, b)
             for i, h in enumerate(hashes):
                 # TODO datetime_started only applies to the first hash. Is this really what we want?
                 self._log_start(args.local, h)
                 # we don't need a new process for the last run
                 ss.run(h, i == len(hashes) - 1)
                 self._log_finish(args.local, h, halt_after_acquisition)
     finally:
         if not keep_lock:
             SauronxLock().unlock(ignore_warning=True)
コード例 #7
0
ファイル: main.py プロジェクト: dmyersturnbull/sauronx
 def clean(self) -> None:
     parser = argparse.ArgumentParser(
         description=
         "List SauronX output on this machine and decide whether to delete it"
     )
     parser.add_argument(
         "--auto",
         action="store_true",
         help="Auto-accept each recommendation without prompting")
     parser.add_argument(
         "--skip-ignores",
         action="store_true",
         help="Don’t prompt when the recommendation is to ignore.",
     )
     args, restrictions, options, base_dir = self._data_args(parser)
     with Valar() as db:
         if args.auto:
             DataManager(db).auto_clean(restrictions, options, base_dir)
         else:
             DataManager(db).clean(restrictions, options, base_dir,
                                   args.skip_ignores)
コード例 #8
0
ファイル: main.py プロジェクト: dmyersturnbull/sauronx
 def connect(self) -> None:
     """Hidden connection test."""
     with Valar():
         pass
コード例 #9
0
ファイル: main.py プロジェクト: dmyersturnbull/sauronx
 def data(self) -> None:
     parser = argparse.ArgumentParser(
         description="List SauronX output on this machine")
     args, restrictions, options, base_dir = self._data_args(parser)
     with Valar() as db:
         DataManager(db).data(restrictions, options, base_dir)
コード例 #10
0
 def print_info(self, extended: bool = False) -> None:
     with Valar() as valar:
         print()
         self._bannered(
             Style.BRIGHT + "Version information...",
             "Version ".ljust(20, ".") + " " + sauronx_version,
             "Hash ".ljust(20, ".") + " " + git_commit_hash(sauronx_home),
         )
         obj = config.get_sauron_config()
         self._bannered(
             Style.BRIGHT + "Hardware config information...",
             "Date/time changed ".ljust(20, ".") + " " + "{}Z".format(
                 config.sauron_number,
                 obj.datetime_changed.strftime("%Y-%m-%d_%H-%M-%S")),
             "Description ".ljust(20, ".") + " " + obj.description,
         )
         self._bannered(
             Style.BRIGHT + "Video information...",
             "QP ".ljust(20, ".") + " " +
             str(config["sauron.data.video.qp"]),
             "Keyframe interval ".ljust(20, ".") + " " +
             str(config["sauron.data.video.keyframe_interval"]),
             "Preset ".ljust(20, ".") + " " +
             config.get_str("sauron.data.video.preset"),
             "Custom params ".ljust(20, ".") + " " + "; ".join([
                 str(k) + "=" + str(v) for k, v in
                 config["sauron.data.video.extra_x265_params"].items()
             ]),
         )
         self._bannered(
             Style.BRIGHT + "Hardware information...",
             "FPS ".ljust(20, ".") + " " +
             str(config["sauron.hardware.camera.frames_per_second"]),
             "Exposure ".ljust(20, ".") + " " +
             str(config["sauron.hardware.camera.exposure"]),
             "Gain ".ljust(20, ".") + " " +
             str(config["sauron.hardware.camera.gain"]),
             "Gamma ".ljust(20, ".") + " " +
             str(config["sauron.hardware.camera.gamma"]),
             "Black level ".ljust(20, ".") + " " +
             str(config["sauron.hardware.camera.black_level"]),
             "Pre-padding ".ljust(20, ".") + " " + str(config[
                 "sauron.hardware.camera.padding_before_milliseconds"]),
             "Post-padding ".ljust(20, ".") + " " +
             str(config["sauron.hardware.camera.padding_after_milliseconds"]
                 ),
             "Arduino chipset ".ljust(20, ".") + " " +
             config.get_str("sauron.hardware.arduino.chipset"),
             "Sample rate (ms) ".ljust(20, ".") + " " + str(config[
                 "sauron.hardware.sensors.sampling_interval_milliseconds"]),
             "Audio floor (dB) ".ljust(20, ".") + " " +
             str(config["sauron.hardware.stimuli.audio.audio_floor"]),
             "Registered sensors ".ljust(20, ".") + " " +
             "; ".join(config["sauron.hardware.sensors.registry"]),
         )
         self._bannered(
             Style.BRIGHT + "Sauronx config information...",
             "Sauron ".ljust(20, ".") + " " + str(config.sauron_name),
             "Raw frames dir ".ljust(20, ".") + " " +
             config.raw_frames_root,
             "Output dir ".ljust(20, ".") + " " + config.output_dir_root,
             "Trash dir ".ljust(20, ".") + " " + config.trash_dir(),
             "Temp dir ".ljust(20, ".") + " " + config.temp_dir(),
             "Incubation dir ".ljust(20, ".") + " " +
             config.get_incubation_dir(),
             "Prototyping dir ".ljust(20, ".") + " " +
             config.get_prototyping_dir(),
             "Plate types ".ljust(20, ".") + " " +
             "; ".join(config.list_plate_types()),
         )
         if extended:
             self._bannered(
                 Style.BRIGHT + "Environment information...",
                 *[((key + " ").ljust(40, ".") + " " +
                    escape_for_properties(value))
                   for key, value in config.environment_info.items()],
             )
コード例 #11
0
class SauronxAlive:
    """Handles a database connection from VALARPY_CONFIG and processing (submission) locks.
    For example, you can't prototype and submit at the same time.
    This should be reserved for commands that need the camera or Arduino board.
    """
    def __init__(
        self,
        submission_hash: Optional[str],
        db: Optional[Valar] = None,
        acquisition_start: bool = False,
        ignore_warnings: bool = False,
    ) -> None:
        """If submission_hash is None, don't try to access submission_obj or status_obj or call update_status."""
        self.db = None  # type: Valar
        self._internal_db = None  # type: bool
        self.submission_hash = None  # type: Optional[str]
        self.submission_obj = None  # type: Submissions
        self.sauron_obj = None  # type: Saurons
        self.status_obj = None  # type: Optional[SubmissionRecords]
        self._internal_db = db is None  # don't close if it was built outside
        self.ignore_warnings = ignore_warnings
        self.acquisition_start = acquisition_start
        self.submission_hash = submission_hash
        if self.submission_hash is not None:
            processing_list = ProcessingList.now()
            if self.submission_hash in processing_list:
                warn_user(
                    "Refusing to touch {}:".format(submission_hash),
                    "Another SauronX process appears to be handling it.",
                    "If this is wrong, run 'sauronx clear {}' to continue.".
                    format(submission_hash),
                )
                raise LockedError(
                    "Refusing to touch {}: Another SauronX process appears to be handling it."
                    .format(submission_hash))
        if db is None:
            self.db = Valar()
        else:
            self.db = db
        try:
            if "connection.notification.slack_info_file" in config:
                with open(
                        str(config["connection.notification.slack_info_file"]),
                        "r") as file:
                    self._slack_hook = file.readline()
                    self._slack_user_dict = json.loads(file.readline())
        except Exception:
            warn_user("Failed to call notification")
            logger.error("Failed to call notification", exc_info=True)

    def __enter__(self):
        self.start()
        return self

    def __exit__(self, t, value, traceback):
        self.finish()

    def start(self):
        if self._internal_db:
            self.db.open()
        self.sauron_obj = self._fetch_sauron()
        if self.submission_hash is not None:
            ProcessingSubmission.from_hash(self.submission_hash).create()
            try:
                self.submission_obj = self._fetch_submission()
                self._init_status()
            except:
                ProcessingSubmission.from_hash(self.submission_hash).destroy()
                raise

    @property
    def is_test(self):
        return self.submission_hash == "_" * 12

    def finish(self):
        if self.submission_hash is not None:
            ProcessingSubmission.from_hash(self.submission_hash).destroy()
        if self._internal_db:
            self.db.close()

    def update_status(self, stat: StatusValue) -> None:
        if self.submission_hash is not None:
            self.status_obj = SubmissionRecords(
                submission=self.submission_obj,
                created=datetime_started_raw,
                datetime_modified=datetime.datetime.now(),
                sauron=self.sauron_obj.id,
                status=stat.name.lower(),
            )
            try:
                self.status_obj.save()
            except:
                warn_user("Failed to update status to {}".format(stat))
                logger.warning("Failed to update status to {}".format(stat))

    def notify_finished(self) -> None:
        pass

    def _fetch_sauron(self):
        import valarpy.model as model

        matches = list(
            model.Saurons.select().where(model.Saurons.id == config.sauron_id))
        if len(matches) != 1:
            raise ValueError("No Sauron with number {} exists!".format(
                config.sauron_name))
        return matches[0]

    def _fetch_submission(self):
        import valarpy.model as model

        sub = (model.Submissions.select(
            model.Submissions, model.Experiments, model.Users).join(
                model.Users,
                on=(model.Submissions.user_id == model.Users.id)).switch(
                    model.Submissions).join(model.Experiments).join(
                        model.TemplatePlates).join(model.PlateTypes).switch(
                            model.Experiments).join(model.Batteries).where(
                                model.Submissions.lookup_hash ==
                                self.submission_hash).first())
        if sub is None:
            raise ValarLookupError(
                "No SauronX submission exists in Valar with submission hash {}"
                .format(self.submission_hash))
        # make sure it wasn't already used
        matching_run = (model.Runs.select(model.Runs, model.Submissions).join(
            model.Submissions).where(model.Submissions.id == sub.id).first())
        if matching_run is not None:
            warn_user("Can't continue: {} was already used for run r{}".format(
                self.submission_hash, matching_run))
            raise ValueError(
                "Submission hash {} was already used for run r{} ".format(
                    self.submission_hash, matching_run))
        return sub

    def _init_status(self) -> None:
        matches = list(
            SubmissionRecords.select(SubmissionRecords).where(
                SubmissionRecords.submission ==
                self.submission_obj.id).order_by(
                    SubmissionRecords.created.desc())
        )  # type: List[SubmissionRecords]
        # warn about prior runs
        prev_run = Runs.select().where(
            Runs.submission == self.submission_obj).first()
        has_remote = any(m.status in REMOTE for m in matches)
        has_post_capture = any(m.status in POST_CAPTURE for m in matches)
        acquisition_starts = [
            m.created for m in matches if m.status is StatusValue.CAPTURING
        ]
        # TODO warn if n_acquisition_starts > 0
        if not self.ignore_warnings:
            if len(matches) > 0 and not SauronxLock().is_running_test():
                logging.warning(
                    "There are already {} submission record(s):\n{}\n".format(
                        len(matches),
                        _table(
                            ["id", "status", "inserted", "run_started"],
                            [[r.id, r.status, r.datetime_modified, r.created]
                             for r in matches],
                        ),
                    ))
            msg = None  # keep only the highest precedence warning, using elif
            if prev_run is not None:
                msg = "The submission is already attached to run r{}".format(
                    prev_run.id)
            elif has_remote:
                msg = "The submission was captured and already uploaded to Valinor."
            elif has_post_capture and self.acquisition_start:  # only worry if we're starting fresh
                msg = "The submission was already successfully captured."
            if msg is not None:
                warn_user("Refusing:", msg)
                print(Fore.RED + "Continue anyway? Not recommended. [yes/no]",
                      end="")
                try:
                    ans = Tools.prompt_yes_no("")
                except KeyboardInterrupt:
                    raise RefusingRequestError(msg)
                if ans:
                    logging.warning(msg)
                else:
                    raise RefusingRequestError(msg)
            if len(acquisition_starts) > 0:
                logging.warning(
                    "Acquisition started {} times before: {}".format(
                        len(acquisition_starts),
                        ", ".join(acquisition_starts)))
        self.update_status(StatusValue.STARTING)
コード例 #12
0
import datetime
import json
import logging
from enum import Enum
from typing import List, Optional

from pocketutils.full import Tools
from valarpy.Valar import Valar
from pocketutils.core.exceptions import LockedError, RefusingRequestError
from pocketutils.misc.fancy_console import ColorMessages

from loguru import logger

Valar().open()
from valarpy.model import *

from sauronx import datetime_started_raw

from .configuration import config
from .locks import ProcessingList, ProcessingSubmission, SauronxLock


class InterceptHandler(logging.Handler):
    """
    Redirects standard logging to loguru.
    """
    def emit(self, record):
        # Get corresponding Loguru level if it exists
        try:
            level = logger.level(record.levelname).name
        except ValueError: