Exemplo n.º 1
0
def invoke_script(config, name, args, fallback):
    """
    Invokes the script identified by it's string NAME.

    On default this command will attempt to invoke the most recent version of the script, which is subject to version
    control and came with the most recent CI build. To use the stable fallback version which comes with the
    ufotest software itself use the --fallback flag
    """
    ctitle('Invoking script')
    cparams({
        'script name': name,
        'additional args': args,
        'use fallback?': fallback,
    })

    try:
        result = config.sm.invoke(name, args, use_fallback=fallback)

        if result['exit_code'] == 0:
            cresult(f'script "{name}" exits with code 0')
            cprint('STDOUT:\n' + result['stdout'])

        elif result['exit_code'] != 0:
            cerror(f'script "{name}" exits with code 1')
            cerror('STDERR:\n' + result['stderr'])
            cprint('STDOUT:\n' + result['stdout'])
            sys.exit(1)

    except KeyError:
        cerror(f'A script with name "{name}" is not registered with ufotest!')
        sys.exit(1)

    sys.exit(0)
Exemplo n.º 2
0
    def run(self):
        # At first we request the frames from the camera and then we do the random picking to assemble them into as many
        # frame pairs as we need.
        for i in range(10):
            try:
                frame = self.camera.get_frame()
                self.frames.append(frame)
            # It does not matter if it is less than 10 frames by a few
            except:
                pass

        for i in range(self.MEASUREMENT_COUNT):
            frame1 = random.choice(self.frames)
            frame2 = random.choice(self.frames)
            self.tasks.append((0, frame1, frame2))

        cprint(f'Acquired {len(self.frames)} frames from the camera')

        self.calculate_sequential()
        self.calculate_parallel()

        improvement = (self.parallel_time / self.sequential_time) * 100
        average = self.parallel_time / self.MEASUREMENT_COUNT

        message = (
            f'Sequential processing of {self.MEASUREMENT_COUNT} noise calculations took <strong>'
            f'{self.sequential_time:0.2f}</strong> seconds and parallel processing took <strong>'
            f'{self.parallel_time:0.2f}</strong> seconds. That is {improvement:0.2f} percent of the time. For '
            f'a parallel processing with {self.WORKER_COUNT} this makes an average time of <strong>'
            f'{average:0.2f}</strong> seconds per noise calculation.'
        )

        return MessageTestResult(0, message)
Exemplo n.º 3
0
def test(config, suite, test_id):
    """
    Run the test "TEST_ID"

    TEST_ID is a string, which identifies a certain test procedure. To view all these possible identifiers consult the
    config file.

    This command executes one or multiple camera test cases. These test cases interface with the camera to perform
    various actions to confirm certain functionality of the camera and the fpga module. This process will most likely
    take a good amount of time to finish. After all test cases are done, a test report will be generated and saved in
    an according sub folder of the "archive"

    The --suite flag can be used to execute a whole test suite (consisting of multiple test cases) instead. In case
    this flag is passed, the TEST_ID argument will be interpreted as the string name identifier of the suite.

    NOTE: To quickly assess a test case without actually having to interface with the camera itself it is possible to
    use the --mock flag *on the ufotest base command* to use the mock camera to perform the test cases.
    """
    ctitle(f'RUNNING TEST{" SUITE" if suite else ""}')
    cparams({
        'test identifier': test_id,
        'is test suite': suite,
        'verbose': config.verbose()
    })

    try:
        with TestContext() as test_context:
            # 1 -- DYNAMICALLY LOADING THE TESTS
            test_runner = TestRunner(test_context)
            test_runner.load()

            # 2 -- EXECUTING THE TEST CASES
            if suite:
                click.secho('    Executing test suite: {}...'.format(test_id),
                            bold=True)
                test_runner.run_suite(test_id)
            else:
                click.secho('    Executing test: {}...'.format(test_id),
                            bold=True)
                test_runner.run_test(test_id)

            # 3 -- SAVING THE RESULTS AS A REPORT
            test_report = TestReport(test_context)
            test_report.save(test_context.folder_path)

    except Exception as e:
        click.secho('[!] {}'.format(e), fg='red', bold=True)
        sys.exit(1)

    if config.verbose():
        click.secho(test_report.to_string())

    cresult('Test report saved to: {}'.format(test_context.folder_path))
    cprint(
        'View the report at: http://localhost/archive/{}/report.html'.format(
            test_context.folder_name))

    sys.exit(0)
Exemplo n.º 4
0
    def copy_repository(self):
        repository_source_path = self.context.repository_path
        repository_destination_path = os.path.join(
            self.context.folder_path, self.context.repository_name)

        shutil.copytree(repository_source_path, repository_destination_path)
        cprint(
            f'Copied source repository to path: "{repository_destination_path}"'
        )
Exemplo n.º 5
0
    def calculate_sequential(self):
        start_time = time.time()
        for exp, frame1, frame2 in self.tasks:
            variance = calculate_pair_variance(frame1, frame2)
            noise = np.sqrt(variance)

        end_time = time.time()
        self.sequential_time = end_time - start_time
        cprint(f'Sequential time: {self.sequential_time}')
Exemplo n.º 6
0
def recompile(config):
    """
    This command recompiles all the static HTML files of the test reports.

    All test reports are actually static HTML files, which are created from a template after the corresponding test run
    finishes. This presents an issue if any web interface related changes are made to the config or if the html
    templates are updated with a new release version. In such a case the test report html pages would not reflect the
    changes. In this case, the "recompile" command can be used to recreate the static html files using the current
    config / template versions.
    """
    # THE PROBLEM
    # So this is the problem: Test reports are actually static html files. These html templates for each test reports
    # are rendered once the test run finishes and then they are just html files. In contrary to dynamically creating
    # the html content whenever the corresponding request is made. And I would like to keep it like this, because the
    # it is easier to handle in other regards, but this poses the following problem: If certain changes are made to the
    # server these are not reflected within the rest report html pages. The most pressing problem is if the URL changes
    # This will break all links on the test report pages. Another issue is when a new version of ufotest introduces
    # changes to the report template, which wont be reflected in older reports.
    # THE IDEA
    # The "recompile" command should fix this by recreating all the test report static html files with the current
    # version of the report template based on the info in the report.json file

    ctitle('RECOMPILE TEST REPORT HTML')
    cparams({'archive folder': config.get_archive_path()})

    count = 0
    for root, folders, files in os.walk(config.get_archive_path()):

        for folder in folders:
            folder_path = os.path.join(root, folder)
            report_json_path = os.path.join(folder_path, 'report.json')
            report_html_path = os.path.join(folder_path, 'report.html')

            if not os.path.exists(report_json_path):
                continue

            with open(report_json_path, mode='r+') as file:
                report_data = json.load(file)

            with open(report_html_path, mode='w+') as file:
                # "report_data" is only the dict representation of the test report. This is problematic because we
                # cannot call the "to_html" method then. But the test report class and all the test result classes
                # implement the HTMLTemplateMixin class, which enables to render the html template from just this dict
                # representation using the static method "html_from_dict". This is possible because the dict itself
                # saves the entire template string in one of its fields.
                html = HTMLTemplateMixin.html_from_dict(report_data)
                file.write(html)
                cprint(f'Recompiled test report {folder}')
            count += 1

        break

    cresult(f'Recompiled {count} test reports!')

    sys.exit(0)
Exemplo n.º 7
0
def install_all(config, path, no_dependencies, save_json, skip):
    """
    Installing all dependencies for the ufo camera project into PATH

    PATH has to be the path to an existing folder. The command installs all the required repositories into this folder.
    Apart from the repositories, the command also installs the required system packages for the operation of the
    UFO camera. For this it relies on the package installation method and operation system defined within the
    ufotest config file.
    """
    # The path sting which is passed into this function could also be a relative path such as ".." which stands for
    # "the parent folder relative to the current one from which I am executing this". All functions expect an
    # absolute path and "realpath" converts these relative expressions into absolute paths
    path = os.path.realpath(path)

    operating_system = CONFIG['install']['os']
    ctitle('INSTALLING NECESSARY REQUIREMENTS')
    cparams({
        'operating system':
        operating_system,
        'package install command':
        CONFIG['install'][operating_system]['package_install'],
        'camera dimensions:':
        f'{CONFIG.get_sensor_width()} x {CONFIG.get_sensor_height()}'
    })

    results = {}
    # We will only actually execute the installation procedures if the skip flag is not set
    if not skip:
        if no_dependencies:
            cprint('Skipping system packages...')
        else:
            install_dependencies(verbose=config.verbose())

        for dependency_name, install_function in DEPENDENCY_INSTALL_FUNCTIONS.items(
        ):
            csubtitle(f'Installing "{dependency_name}"')
            result = install_function(path)
            results[dependency_name] = result

    else:
        results['mock'] = mock_install_repository(path)

    # Creating the JSON file if the flag was set.
    if save_json:
        try:
            json_path = os.path.join(os.getcwd(), 'install.json')
            with open(json_path, mode='w+') as file:
                json.dump(results, file, indent=4, sort_keys=True)
        except Exception as e:
            cerror(f'Could not save "install.json" because: {str(e)}')
            sys.exit(1)

    sys.exit(0)
Exemplo n.º 8
0
def init(force, update):
    """
    Initializes the installation folder for this application.

    This folder will be located at "$HOME/.ufotest". The init furthermore includes the creation of the necessary
    sub folder structures within the installation folder as well as the creation of the default config file and all the
    static assets for the web interface.

    The --force flag can be used to *overwrite* an existing installation. It will delete the existing installation,
    if one exists, and then create an entirely new installation.

    The --update flag can be used to perform an update rather than a complete installation. This can be invoked with an
    installation present and will not overwrite custom configuration files. The only thing which will be replaced
    during such an update will be the static files. The new static files from the current system package version of
    ufotest will be copied to the active installation folder.
    """

    installation_path = get_path()
    ctitle('INITIALIZING UFOTEST INSTALLATION')
    cparams({'installation path': installation_path})

    if update:
        if check_path(installation_path, is_dir=True):
            update_install(installation_path)
            cresult('Updated ufotest installation')
            sys.exit(0)
        else:
            cerror(
                'Cannot perform update without already existing installation!')
            sys.exit(1)

    if check_path(installation_path, is_dir=True):
        if force:
            shutil.rmtree(get_path())
            cprint('Deleted old installation folder')
        else:
            cerror('An installation folder already exists at the given path!')
            cerror(
                'Please use the --force flag if you wish to forcefully replace the existing installation'
            )
            sys.exit(1)
    else:
        cprint('    Installation folder does not yet exist')

    # This method actually executes the necessary steps.
    init_install()
    cresult(
        'UfoTest successfully initialized, use the --help option for further commands'
    )

    sys.exit(0)
Exemplo n.º 9
0
    def run(self):

        message_result = MessageTestResult(self.exit_code, self.INFO_MESSAGE)

        frame1 = self.camera.get_frame()
        cprint('Captured frame 1')
        frame2 = self.camera.get_frame()
        cprint('Captured frame 2')

        variance = calculate_pair_variance(frame1, frame2)
        rmsnoise = math.sqrt(variance)

        dict_result = DictTestResult(self.exit_code, {
            'variance': round(variance, ndigits=self.NDIGITS),
            'rmsnoise': round(rmsnoise, ndigits=self.NDIGITS)
        })
        cprint('Calculated noise')

        fig = self.create_figure(frame1, frame2)
        figure_description = (
            'This figure shows the two frames which were captured for the purpose of calculating the noise '
            'The first subplot shows the first frame, the second subplot shows the second frame and the third subplot '
            'shows the image which results when subtracting all the pixel values of the first frame from those of the '
            'second frame.'
        )
        figure_result = FigureTestResult(self.exit_code, self.context, fig, figure_description)
        cprint('Created figures')

        return CombinedTestResult(
            message_result,
            figure_result,
            dict_result
        )
Exemplo n.º 10
0
    def run(self):

        for script_name, script in self.script_manager.scripts.items():

            # For each script in the list of all scripts loaded by the script manager we are going to assemble a
            # script_info dict, which will be the basis for a DictListTestResult. On default we assume that the script
            # has no errors, then test all the possible ways an error could occur and override the default False in
            # those cases.
            script_info = {
                '_name': script_name,
                'has_error': False,
                'method': script.syntax_check_method,
                'error': 'none'
            }

            # ~ FILE ACCESSIBLE
            # A first source for an error could be that the file cannot be accessed at all. This is obviously much
            # worse than a syntax error, because it means there is something fundamentally wrong with the
            script_path = script.data['path']
            if not script_info['has_error'] and not os.path.exists(
                    script_path):
                script_info['has_error'] = True
                script_info[
                    'error'] = f'No script exists at the path "{script_path}"'

            # ~ SYNTAX ERROR
            # Conveniently the bash system itself offers a very easy way of checking script syntax, the script file
            # simply has to be invoked with "bash -n".
            syntax_ok, error_message = script.check_syntax()
            if not script_info['has_error'] and not syntax_ok:
                script_info['has_error'] = True
                script_info['error'] = f'{error_message}'

            # At the end we can determine the status of the script by whether or not the has_error flag has been set
            # during the previous checks. We have to negate the flag because format_boolean_span will format True as
            # green (=good) and False as red (=bad)
            script_info['_title'] = self.format_boolean_span(
                script_name, not script_info['has_error'])
            self.script_infos[script_name] = script_info

            cprint(f'Checked syntax of {script_name}')

        # The test will be considered as failed if even a single script has a syntax error!
        exit_code = int(
            any([
                script_info['has_error']
                for script_info in self.script_infos.values()
            ]))
        return DictListTestResult(exit_code, self.script_infos)
Exemplo n.º 11
0
    def run(self):
        # The idea of the test is to set the exposure time to different values and then simple show all
        # the images which have been taken
        for exposure_time in self.EXPOSURE_TIME_VALUES:
            try:
                self.camera.set_prop('exposure_time', exposure_time)
                frame = self.camera.get_frame()
                self.frames[exposure_time] = frame
                cprint(f'Acquired frame for exp time: {exposure_time}')

            except (FrameDecodingError, PciError):
                self.frames[exposure_time] = None
                cprint(f'Failed for exp time: {exposure_time}')

        fig = self.create_frames_figure()
        description = 'none'

        return FigureTestResult(0, self.test_runner.context, fig, description)
Exemplo n.º 12
0
    def calculate_parallel(self):
        start_time = time.time()

        task_queue = mp.Queue()
        [task_queue.put(task) for task in self.tasks]
        result_queue = mp.Queue()

        workers = []
        for i in range(self.WORKER_COUNT):
            worker = CalculateNoiseWorker(task_queue, result_queue)
            worker.start()
            workers.append(worker)
            cprint(f'Started worker {i + 1}')

        [worker.join() for worker in workers]
        end_time = time.time()

        self.parallel_time = end_time - start_time
        cprint(f'Parallel time: {self.parallel_time}')
Exemplo n.º 13
0
    def run(self):
        # ~ Fetching all the images from the camera first
        error_count = 0
        for exposure_time in self.exposure_times:
            self.camera.set_prop('exposure_time', exposure_time)
            cprint(f'set exposure time: {exposure_time}')

            for i in range(self.reps):
                try:
                    frame1 = self.camera.get_frame()
                    frame2 = self.camera.get_frame()
                    self.tasks.append((exposure_time, frame1, frame2))
                    cprint(f'Acquired two frames for exp time: {exposure_time}')

                except (PciError, FrameDecodingError):
                    error_count += 1
                    cprint(f'Failed to acquire frames for exp time: {exposure_time}')

        # ~ Calculating the noises in parallel
        task_queue = mp.Queue()
        [task_queue.put(task) for task in self.tasks]
        result_queue = mp.Queue()

        worker_count = 4
        workers = []
        for i in range(worker_count):
            worker = CalculateNoiseWorker(task_queue, result_queue)
            worker.start()
            workers.append(worker)

        [worker.join() for worker in workers]

        while not result_queue.empty():
            exposure_time, noise = result_queue.get()
            self.noises[exposure_time].append(noise)

        cprint('Calculated noises in parallel')

        ptc_fig = self.create_ptc_figure(self.exposure_times, self.noises)

        description = (
            f'Mean noise measurements. The exposure time was varied from {self.start}ms to a maximum of {self.end} ms '
            f'in increments of {self.step}. For each exposure time, the noise was measured a total of {self.reps} '
            f'independent times. This implies that each exposure time required the capturing of {self.reps * 2} '
            f'independent frames.'
        )

        return CombinedTestResult(
            FigureTestResult(0, self.context, ptc_fig, description),
            MessageTestResult(0, f'A total of <strong>{error_count}</strong> noise measurements failed')
        )
Exemplo n.º 14
0
def script_details(config, name):
    """
    Shows the full details of the scripts identified by it's string NAME.

    This command prints the full details for the given script, including its source code.
    """
    # ~ Checking for eventual problems with the script
    # First we need to check if a script with the given name even exists
    if name in config.sm.scripts:
        script = config.sm.scripts[name]
    elif name in config.sm.fallback_scripts:
        script = config.sm.fallback_scripts[name]
    else:
        cerror(
            f'A script identified by "{name}" is nor registered with ufotest!')
        sys.exit(1)

    # Then we need to check if the file even exists / the content can be read, since we also want to display the
    # content of it
    try:
        with open(script.path, mode='r+') as file:
            content = file.read()
    except:
        cerror(
            f'The file {script.path} does not exists and/or is not readable!')
        sys.exit(1)

    # ~ Displaying the results to the user in case there are no problems

    ctitle(name)
    cparams({
        'type': script.data['class'],
        'path': script.path,
        'author': script.author
    })

    cprint('DESCRIPTION:\n' + script.description)

    print()
    cprint('CONTENT:\n' + content)
Exemplo n.º 15
0
    def test(self) -> None:
        """Executes the test suite with the new hardware version

        :return: void
        """
        # ~ LOAD THE TESTS
        # The test first have to be dynamically loaded and imported from their respective files. That is exactly
        # what the "load" method does. It always has to be called first before attempting to execute a test.
        self.test_runner.load()
        if self.config.verbose():
            cprint('Tests have been loaded from memory')

        # ~ EXECUTING THE TESTS
        self.test_runner.run_suite(self.context.test_suite)
        test_report = TestReport(self.test_context)
        test_report.save(self.test_context.folder_path)
        if self.config.verbose():
            cprint(f'Finished test suite: {self.context.test_suite}')
            cprint(f'Test report saved to: {self.test_context.folder_path}')

        # ~ STORE THE TEST REPORT
        # The test report later also needs to be referenced to produce the build report, so it needs to be saved as
        # part of the build context object as well. The context object already has the attribute 'test_report', which
        # was initialized to none in the constructor. Now we supply the actual value
        self.context.test_report = test_report
Exemplo n.º 16
0
    def run(self):
        try:
            while self.running:
                time.sleep(1)

                if not BuildQueue.is_empty() and not BuildLock.is_locked():
                    build_request = BuildQueue.pop()

                    try:
                        # ~ RUN THE BUILD PROCESS
                        build_report = self.run_build(
                            build_request)  # raises: BuildError

                        # ~ SEND REPORT MAILS
                        # After the build process has terminated we want to inform the relevant people of the outcome.
                        # This is mainly Two people: The maintainer of the repository is getting an email and pusher
                        self.send_report_mails(build_request, build_report)

                    except BuildError as error:
                        cerror(
                            'The build process was terminated due to a build error!'
                        )
                        cerror(str(error))

                    except smtplib.SMTPAuthenticationError as error:
                        cerror(
                            'The email credentials provided in the config file were not accepted by the server!'
                        )
                        cerror(str(error))

                    except OSError as error:
                        cerror(
                            'Report mails could not be sent because there is no network connection'
                        )
                        cerror(str(error))

        except KeyboardInterrupt:
            cprint('\n...Stopping BuildWorker')
Exemplo n.º 17
0
    def poll(self) -> bool:
        """
        Returns whether or not the camera can be used.

        :return: bool
        """
        result = self.config.sm.invoke('status')
        output = result['stdout']

        if self.config.verbose():
            cprint(output)

        # This method will parse the string output of the status script and return a dict, whose keys are the register
        # identifiers and the values are lists with 4 items which are the string versions of the 8 hex values each
        # that make up the register value.
        registers = self.parse_status_to_dict(output)

        # I am using the very simple method michele has taught me and I am only checking the 9050 register for the
        # occurrence of the very specific bit sequences ffff and 1111 which tell that frames should be able to be
        # taken!
        status = 'ffff' in registers['9050'][0] and '1111' in registers['9050'][2]

        return status
Exemplo n.º 18
0
def decode_frame(data_path: str) -> str:
    """Decodes the frame data given at *data_path* and returns the path to the .raw image file

    This function can only be called, after the actual frame data has been received from the camera. The file with the
    raw data has to already exist.
    This function is based on one of Michele's scripts called "frame.sh"

    :param data_path: The string path of the file which contains the raw frame data.

    :raises FrameDecodingError: When the decode command fails. The error message contains some part of the commands
        output.

    :returns: The string path to the decoded .raw image file
    """
    frame_path = f'{data_path}.raw'

    if CONFIG.verbose():
        cprint('Decoding the image, with the following settings:')
        cparams({
            'camera sensor width': CONFIG.get_sensor_width(),
            'camera sensor height': CONFIG.get_sensor_height(),
            'input data path': data_path,
            'output frame path': frame_path
        })

    decode_command = 'ipedec -r {height} --num-columns {width} {path} {verbose}'.format(
        height=CONFIG.get_sensor_height(),
        width=CONFIG.get_sensor_width(),
        path=data_path,
        verbose='-v' if CONFIG.verbose() else ''
    )

    exit_code, stdout = run_command(decode_command)
    if exit_code:
        raise FrameDecodingError(stdout[:stdout.find('\n')])

    return frame_path
Exemplo n.º 19
0
    def run(self):

        for exposure_time in [3, 7, 9, 22, 45, 53]:
            self.camera.set_prop('exposure_time', exposure_time)
            frames = []
            for i in range(30):
                try:
                    frames.append(self.camera.get_frame())
                except (PciError, FrameDecodingError) as e:
                    print(e.__class__)

            frame_array = np.zeros(shape=(
                self.config.get_sensor_height(),
                self.config.get_sensor_width(),
                len(frames)
            ))
            for index, frame in enumerate(frames):
                frame_array[:, :, index] = frame

            variance = np.mean(np.var(frame_array, axis=2) / len(frames))
            noise = np.sqrt(variance)
            cprint(f'{variance} - {noise}')

        return MessageTestResult(0, "a")
Exemplo n.º 20
0
    def run(self):
        # ~ Getting the frames from the camera
        for i in range(self.FRAME_COUNT):
            try:
                frame = self.camera.get_frame()
                self.frames.append(frame)
            except (FrameDecodingError, PciError) as e:
                cprint(f'Failed to acquire frame {i + 1}')

        # ~ Calculating the noise
        self.frame_array = np.zeros(shape=(
            self.config.get_sensor_height(),
            self.config.get_sensor_width(),
            len(self.frames)
        ))
        for index, frame in enumerate(self.frames):
            self.frame_array[:, :, index] = frame

        cprint(f'Assembled {len(self.frames)} frames into a 3d numpy array')
        cprint(f'max: {np.max(self.frame_array)} - min: {np.min(self.frame_array)}')

        self.variance_frame = np.var(self.frame_array, axis=2)
        self.noise_frame = np.sqrt(self.variance_frame)

        variance = np.mean(self.variance_frame)
        noise = np.sqrt(variance)

        fig = self.create_variance_figure()

        return CombinedTestResult(
            FigureTestResult(0, self.test_runner.context, fig, ''),
            DictTestResult(0, {
                'variance': f'{variance:0.2f}',
                'noise': f'{noise:0.2f}'
            })
        )
Exemplo n.º 21
0
    def run(self):
        # -- SETTING UP ENV VARIABLES
        setup_environment()

        # -- REQUESTING FRAME FROM CAMERA
        # The "capture_frame" method will query the camera object to get a frame in the format of a numpy 2D array and
        # then save this into the self.frame property and a flattened 1D version of the frame to the self.frame_flat
        # variable
        self.capture_frame()
        cprint(f'captured frame with {len(self.frame_flat)} pixels')

        # -- CREATING THE HISTOGRAM
        self.calculate_histogram()
        cprint(f'created histogram')

        fig_frame = self.create_frame_figure()
        fig_frame_description = (
            f'A single frame acquired from the camera. (left) The image as it was taken from the camera. The image '
            f'itself is not colored, but the pixel range is converted into a color map, where 0 corresponds to dark '
            f'blue colors and the maximum pixel value {self.MAX_PIXEL_VALUE} to bright yellow. (right) The frame with '
            f'a contrast increasing algorithm applied to it, which will stretch the histogram to take up all the '
            f'available space up to the max pixel value.')

        fig_hist = self.create_histogram_figure()
        fig_hist_description = (
            f'The histogram of the frame. (left) Shows the histogram, where the bounds of the plot are set to the  '
            f'minimum and maximum possible pixel values with the currently used detector. (right) Shows the zoomed '
            f'version of the histogram, where the borders of the plot are adjusted to start at the 1st percentile and '
            f'end at the 99th percentile.')

        cprint('saved final figures')

        return CombinedTestResult(
            FigureTestResult(0, self.context, fig_frame,
                             fig_frame_description),
            FigureTestResult(0, self.context, fig_hist, fig_hist_description))
Exemplo n.º 22
0
def frame(config, output, display):
    """
    Capture a single frame from the camera.

    If this command is invoked without any additional options, the frame will be captured from the camera and then
    saved to the location "/tmp/frame.png".

    The output location for the image file can be overwritten by using the --output option to specify another path.

    The --display flag can be used to additionally display the image to the user after the frame has been captured.
    This feature requires a graphical interface to be available to the system. The frame will be opened in a seperate
    matplotlib figure window.
    """
    config.pm.do_action('pre_command_frame',
                        config=config,
                        namespace=globals(),
                        output=output,
                        display=display)

    ctitle('CAPTURING FRAME')
    cparams({
        'output path':
        output,
        'display frame':
        display,
        'sensor_dimensions':
        f'{CONFIG.get_sensor_width()} x {CONFIG.get_sensor_height()}'
    })

    # Setup all the important environment variables and stuff
    setup_environment()
    exit_code, _ = run_command('rm /tmp/frame*')
    if not exit_code:
        cprint('Removed previous frame buffer')

    # ~ GET THE FRAME FROM THE CAMERA
    # "get_frame" handles the whole communication process with the camera, it requests the frame, saves the raw data,
    # decodes it into an image and then returns the string path of the final image file.
    try:
        camera_class = config.pm.apply_filter('camera_class', UfoCamera)
        camera = camera_class(config)
        frame = camera.get_frame()
    except PciError as error:
        cerror('PCI communication with the camera failed!')
        cerror(f'PciError: {str(error)}')
        sys.exit(1)
    except FrameDecodingError as error:
        cerror('Decoding of the frame failed!')
        cerror(f'FrameDecodingError: {str(error)}')
        sys.exit(1)

    # ~ Saving the frame as a file
    _, file_extension = output.split('.')
    if file_extension == 'raw':
        cerror('Currently the frame cannot be saved as .raw format!')
    else:
        from PIL import Image
        image = Image.fromarray(frame)
        image.save(output)

    # ~ Displaying the image if the flag is set
    if display:
        # An interesting thing is that matplotlib is imported in-time here and not at the top of the file. This actually
        # had to be changed due to a bug. When using ufotest in a headless environment such as a SSH terminal session
        # it would crash immediately, because a headless session does not work with the graphical matplotlib. Since
        # it really only is needed for this small section here, it makes more sense to just import it in-time.
        import matplotlib.pyplot as plt
        matplotlib.use('TkAgg')

        plt.imshow(frame)
        plt.show()

    sys.exit(0)
Exemplo n.º 23
0
def flash(config, file: str, type: str) -> None:
    """
    Uses the given FILE to flash a new firmware configuration to the internal memory of the FPGA board, where FILE is
    the string absolute path of the file which contains a valid specification of the fpga configuration.

    The --type option can be used to select which kind of configuration file to be flashed. currently only BIT files
    are supported.
    """

    # -- ECHO CONFIGURATION
    ctitle('FLASHING BITFILE TO FPGA')
    click.secho('--| bitfile path: {}\n'.format(file))

    # ~ SKIP FOR MOCK
    # If the "--mock" option is active then we completely skip this command since presumably there is not actual
    # camera connected to do the flashing.
    # TODO: In the future we could add an action hook here to be able to inject come actual code for the case of
    #       mock camera + flash command.
    if config['context']['mock']:
        cresult('Skip flash when due to --mock option being enabled')
        sys.exit(0)

    # ~ CHECKING IF THE GIVEN FILE EVEN EXISTS
    file_path = os.path.abspath(file)
    file_exists = check_path(file_path, is_dir=False)
    if not file_exists:
        cerror('The given path for the bitfile does not exist')
        sys.exit(1)

    # ~ CHECKING IF THE FILE EVEN IS A BIT FILE
    file_extension = file_path.split('.')[-1].lower()
    is_bit_file = file_extension == 'bit'
    if not is_bit_file:
        cerror('The given path does not refer to file of the type ".bit"')
        sys.exit(1)

    # ~ CHECKING IF VIVADO IS INSTALLED
    vivado_installed = check_vivado()
    if not vivado_installed:
        cerror(
            'Vivado is not installed, please install Vivado to be able to flash the fpga!'
        )
        sys.exit(1)
    if CONFIG.verbose():
        cprint('Vivado installation found')

    # ~ STARTING VIVADO SETTINGS
    vivado_command = CONFIG['install']['vivado_settings']
    run_command(vivado_command, cwd=os.getcwd())
    if CONFIG.verbose():
        cprint('Vivado setup completed')

    # ~ FLASHING THE BIT FILE
    flash_command = "{setup} ; {command} -nolog -nojournal -mode batch -source {tcl} -tclargs {file}".format(
        setup=CONFIG['install']['vivado_settings'],
        command=CONFIG['install']['vivado_command'],
        tcl=CONFIG.sm.scripts['fpga_bitprog'].path,
        file=file_path)
    exit_code, _ = run_command(flash_command)
    if not exit_code:
        cresult('Flashed FPGA with: {}'.format(file_path))
        sys.exit(0)
    else:
        cerror('There was an error during the flashing of the FPGA')
        sys.exit(1)
Exemplo n.º 24
0
    def run(self):
        for script_name, script in self.script_manager.scripts.items():

            file_name = os.path.basename(script.data['path'])

            script_info = {
                '_name':
                script_name,
                '_title':
                '<span style="{}">{}</span>'.format(
                    'color: lightcoral;' if script.data['fallback'] else
                    'color: lightgreen;', script_name),
                'is_fallback':
                script.data['fallback'],
                'type':
                script.data['class'],
                'file':
                file_name,
                'is_readable':
                os.path.isfile(script.data['path']),
            }

            # If the file is indeed readable then we are going ro read it to get more information about it.
            if script_info['is_readable']:

                # First additional nice-to-have bit of information is the length of the file. This will help with
                # spotting if somehow an empty file has been loaded and that is causing issues
                with open(script.data['path'], mode='r') as file:
                    script_lines = file.readlines()

                script_info['length'] = f'{len(script_lines)} lines'

                # Another interesting one is to also load the fallback file and compute how many lines are
                # different between the fallback version and the build version. This can also help spotting problems
                # with perhaps not having committed any changes.
                fallback_script = self.script_manager.fallback_scripts[
                    script_name]
                with open(fallback_script.data['path'], mode='r') as file:
                    fallback_script_lines = file.readlines()

                fallback_diff = len([
                    line for line in script_lines
                    if line not in fallback_script_lines
                ])
                script_info['diff'] = f'{fallback_diff} lines'

            # If the script is not a fallback we can do another nifty thing for the report: We can provide the url to
            # download this file from the web server.
            if not script_info[
                    'is_fallback'] and 'relative_path' in script.data:
                # TODO: This is not a good thing. I am hacking the url together from implicit assumptions but I should
                #       really be having a dedicated variable or method to wrap this
                repository_path = script.data['path'].rstrip(
                    script.data['relative_path'])
                repository_name = os.path.basename(repository_path)

                build_path = os.path.dirname(repository_path)
                build_name = os.path.basename(build_path)

                relative_url = os.path.join('builds', build_name,
                                            repository_name,
                                            script.data['relative_path'])
                absolute_url = self.config.url(relative_url)

                # The "file" field already exists and provides the filename for that script. In this case we are going
                # to replace this simple string with a hyperlink
                script_info[
                    'file'] = '<a class="link" style="opacity: 0.6;" href="{}">{}</a>'.format(
                        absolute_url, file_name)
                # A hidden field to indicate the
                script_info['_file_url'] = absolute_url

            self.script_infos[script_name] = script_info
            cprint(f'processed script {script_name}')

        # The imperative of this test case could be formulated as "Every script should be part of the version control
        # system!". Thus we declare this test as failed if even any one of the scripts is not the build but the fallback
        # version.
        exit_code = any(
            [info['is_fallback'] for info in self.script_infos.values()])

        return DictListTestResult(exit_code, self.script_infos)