Пример #1
0
class FixtureSettings(object):

    def __init__(self, name, dir_path=None):

        self._logger = Logger("Fixture {0}".format(name))

        path_name = Paths().get_fixture_path(name, only_name=True)

        if dir_path:
            conf_rel_path = Paths().fixture_conf_file_rel_pattern.format(path_name)
            self._conf_path = os.path.join(dir_path, conf_rel_path)
        else:
            self._conf_path = Paths().get_fixture_path(name)

        self.model = self._load_model(name)
        self.history = grid_history.GriddingHistory(self)

    def _load_model(self, name):
        """:rtype : scanomatic.models.fixture_models.FixtureModel"""

        try:
            val = FixtureFactory.serializer.load_first(self._conf_path)
        except (IndexError, ConfigParser.Error), e:
            if isinstance(e, ConfigParser.Error):
                self._logger.error("Trying to load an outdated fixture at {0}, this won't work".format(self._conf_path))
            return FixtureFactory.create(path=self._conf_path, name=name)
        else:
Пример #2
0
    def __init__(self, factory):
        """

        :type factory: AbstractModelFactory
        """
        self._factory = factory
        self._logger = Logger(factory.__name__)
Пример #3
0
 def __init__(self, fixture_settings):
     """
     :type fixture_settings: scanomatic.io.fixtures.FixtureSettings
     """
     self._logger = Logger("Gridding History {0}".format(
         fixture_settings.model.name))
     self._fixture_settings = fixture_settings
     self._models_per_plate_pinning = defaultdict(dict)
Пример #4
0
    def __init__(self, name, dir_path=None):

        self._logger = Logger("Fixture {0}".format(name))

        path_name = Paths().get_fixture_path(name, only_name=True)

        if dir_path:
            conf_rel_path = Paths().fixture_conf_file_rel_pattern.format(path_name)
            self._conf_path = os.path.join(dir_path, conf_rel_path)
        else:
            self._conf_path = Paths().get_fixture_path(name)

        self.model = self._load_model(name)
        self.history = grid_history.GriddingHistory(self)
Пример #5
0
        def logger(cls):
            """
            :rtype: scanomatic.io.logger.Logger
            """
            if cls._LOGGER is None:
                cls._LOGGER = Logger(cls.__name__)

            return cls._LOGGER
Пример #6
0
    def __init__(self, analysis_model, scanning_meta_data):
        """

        :param analysis_model: The model
         :type analysis_model : scanomatic.models.analysis_model.AnalysisModel
        :param scanning_meta_data: scanning info
        :type scanning_meta_data : scanomatic.models.scanning_model.ScanningModel
        :return:
        """
        self._analysis_model = analysis_model
        self._scanning_meta_data = scanning_meta_data
        self._logger = Logger("Analysis Image")

        self._im_loaded = False
        self.im = None

        self._grid_arrays = self._new_grid_arrays
        """:type : dict[int|scanomatic.image_analysis.grid_array.GridArray]"""
        self.features = _get_init_features(self._grid_arrays)
Пример #7
0
    def __init__(self, fixture=None):
        """

        :type fixture: scanomatic.io.fixtures.FixtureSettings
        """
        self._logger = Logger("Fixture Image")

        self._reference_fixture_settings = fixture
        self._current_fixture_settings = None
        """:type : scanomatic.io.fixtures.Fixture_Settings"""
        if fixture:
            self._history = GriddingHistory(fixture)
        else:
            self._history = None

        self.im = None
        self.im_path = None
        self._original_dpi = None

        self._name = "default"
    def __init__(self,
                 compilation_path=None,
                 compile_instructions_path=None,
                 scanner_instructions_path=None,
                 sort_mode=FIRST_PASS_SORTING.Time):

        self._logger = Logger("Compilation results")
        self._compilation_path = compilation_path
        self._compile_instructions = None
        self._scanner_instructions = None
        """:type : scanomatic.models.scanning_model.ScanningModel"""
        self.load_scanner_instructions(scanner_instructions_path)
        self._plates = None
        self._plate_position_keys = None
        self._image_models = []
        self._used_models = []
        self._current_model = None
        self._loading_length = 0
        if compile_instructions_path:
            self._load_compile_instructions(compile_instructions_path)
        if compilation_path:
            self._load_compilation(self._compilation_path, sort_mode=sort_mode)
Пример #9
0
import glob
import os
import numpy as np
from scanomatic.io.paths import Paths
from scanomatic.image_analysis.image_basics import load_image_to_numpy
from scanomatic.io.logger import Logger
from scanomatic.models.factories.compile_project_factory import CompileImageAnalysisFactory
from scanomatic.generics.purge_importing import ExpiringModule

_logger = Logger("Analysis Utils")


def produce_grid_images(path=".", image=None, mark_position=None):

    project_path = os.path.join(os.path.dirname(os.path.abspath(path)))
    compilations = glob.glob(os.path.join(os.path.dirname(os.path.abspath(path)),
                                          Paths().project_compilation_pattern.format("*")))

    if not compilations:
        raise ValueError("There are no compilations in the parent directory")

    compilation = compilations[0]
    _logger.info("Using {0}".format(os.path.basename(compilation)))
    compilation = CompileImageAnalysisFactory.serializer.load(compilation)

    image_path = compilation[-1].image.path
    plates = compilation[-1].fixture.plates
    if image is not None:
        for c in compilation:
            if os.path.basename(c.image.path) == os.path.basename(image):
                image_path = c.image.path
Пример #10
0
import numpy as np
from enum import Enum

from scanomatic.data_processing import growth_phenotypes
from scanomatic.io.logger import Logger
from scanomatic.data_processing.phases.analysis import CurvePhasePhenotypes
from scanomatic.data_processing.phases.segmentation import CurvePhases, is_detected_non_linear

_l = Logger("Curve Phase Meta Phenotyping")


class CurvePhaseMetaPhenotypes(Enum):
    """Phenotypes of an entire growth-curve based on the phase segmentation.

    Attributes:
        CurvePhaseMetaPhenotypes.MajorImpulseYieldContribution:
            The fraction of the total yield (in population doublings) that the
            `CurvePhases.Impulse` that contribute most to the total yield is
            responsible for (`CurvePhasePhenotypes.FractionYield`).

        CurvePhaseMetaPhenotypes.FirstMinorImpulseYieldContribution:
            As with `CurvePhaseMetaPhenotypes.MajorImpulseYieldContribution`
            but for the second most important `CurvePhases.Impulse`

        CurvePhaseMetaPhenotypes.MajorImpulseAveragePopulationDoublingTime:
            The `CurvePhases.Impulse` that contribute most to the
            total yield, its average population doubling time
            (`CurvePhasePhenotypes.PopulationDoublingTime`).

        CurvePhaseMetaPhenotypes.FirstMinorImpulseAveragePopulationDoublingTime:
            The average population doubling time of
Пример #11
0
from itertools import chain
from flask import send_file
import numpy as np

from scanomatic.io.app_config import Config
from scanomatic.io.paths import Paths
from scanomatic.io.logger import Logger
from scanomatic.models.factories.scanning_factory import ScanningModelFactory
from scipy.misc import toimage
from scanomatic.image_analysis.first_pass_image import FixtureImage
from scanomatic.models.fixture_models import GrayScaleAreaModel, FixturePlateModel
from scanomatic.image_analysis.image_grayscale import is_valid_grayscale

_safe_dir = re.compile(r"^[A-Za-z_0-9./]*$")
_no_super = re.compile(r"/?\.{2}/")
_logger = Logger("UI API helpers")
_ALLOWED_EXTENSIONS = {'.png', '.jpg', '.jpeg', '.gif', '.tiff'}
_TOO_LARGE_GRAYSCALE_AREA = 300000


def image_is_allowed(ext):
    """Validates that the image extension is allowed

    :param ext: The image file's extension
    :type ext: str
    :returns bool
    """
    global _ALLOWED_EXTENSIONS
    return ext.lower() in _ALLOWED_EXTENSIONS

Пример #12
0
class FixtureImage(object):

    MARKER_DETECTION_DPI = 150
    EXPECTED_IM_SIZE = (6000, 4800)
    EXPECTED_IM_DPI = 600

    def __init__(self, fixture=None):
        """

        :type fixture: scanomatic.io.fixtures.FixtureSettings
        """
        self._logger = Logger("Fixture Image")

        self._reference_fixture_settings = fixture
        self._current_fixture_settings = None
        """:type : scanomatic.io.fixtures.Fixture_Settings"""
        if fixture:
            self._history = GriddingHistory(fixture)
        else:
            self._history = None

        self.im = None
        self.im_path = None
        self._original_dpi = None

        self._name = "default"

    def __getitem__(self, key):

        if key in ['current']:
            if self._current_fixture_settings is None:
                self._current_fixture_settings = FixtureSettings(self.name)
            return self._current_fixture_settings

        elif key in ['fixture', 'reference']:
            if self._reference_fixture_settings is None:
                self._reference_fixture_settings = FixtureSettings(self.name)
                self._name = self.name

            return self._reference_fixture_settings

        else:

            raise KeyError(key)

    @property
    def name(self):

        if self._reference_fixture_settings:
            return self._reference_fixture_settings.model.name
        else:
            return self._name

    @name.setter
    def name(self, value):

        if self._reference_fixture_settings:
            self._reference_fixture_settings.model.name = value
        else:
            self._name = value

    def get_dpi_factor_to_target(self, target_scale):

        return target_scale / float(self._original_dpi)

    @staticmethod
    def coordinate_to_original_dpi(coordinate, as_ints=False, scale=1.0):

        rtype = type(coordinate)

        if as_ints:
            return rtype(int(round(val / scale)) for val in coordinate)

        return rtype(val / scale for val in coordinate)

    @staticmethod
    def coordinate_to_local_dpi(coordinate, as_ints=False, scale=1.0):

        rtype = type(coordinate)

        if as_ints:
            return rtype(int(round(val * scale)) for val in coordinate)

        return rtype(val * scale for val in coordinate)

    def set_image(self, image=None, image_path=None, dpi=None):

        self.im_path = image_path

        if image is not None:

            self.im = image

        elif image_path is not None:

            try:
                self.im = load_image_to_numpy(image_path)
            except IOError:
                self.im = None

            if self.im is None:
                self._logger.error("Could not load image")

            else:
                self._logger.info("Loaded image {0} with shape {1}".format(
                    image_path, self.im.shape))
        else:

            self._logger.warning(
                "No information supplied about how to load image, thus none loaded"
            )

            self.im = None

        if self.im is None:
            self._original_dpi = None
        else:
            self._original_dpi = dpi if dpi else self.guess_dpi()

    def guess_dpi(self):

        dpi = ((a / float(b)) * self.EXPECTED_IM_DPI
               for a, b in zip(self.im.shape, self.EXPECTED_IM_SIZE))
        if dpi:
            guess = 0
            for val in dpi:
                if guess > 0 and guess != val:

                    self._logger.warning(
                        "Image dimensions don't agree with expected size " +
                        "X {1} != Y {2} on image '{3}', can't guess DPI, using {0}"
                        .format(self.EXPECTED_IM_DPI, guess, val, self.im_path)
                    )

                    return self.EXPECTED_IM_DPI

                guess = val

            if guess > 0:
                return guess

        return self.EXPECTED_IM_DPI

    def analyse_current(self):

        logger = self._logger

        t = time.time()
        logger.debug("Threading invokes marker analysis")

        self.run_marker_analysis()

        logger.debug("Threading marker detection complete," +
                     "invokes setting area positions (acc-time {0} s)".format(
                         time.time() - t))

        self.set_current_areas(issues={})

        logger.debug(
            "Threading areas set(acc-time: {0} s)".format(time.time() - t))

        self.analyse_grayscale()

        logger.debug("Grayscale ({0}) analysed (acc-time: {1} s)".format(
            self['grayscale_type'],
            time.time() - t))

        logger.debug("Threading done (took: {0} s)".format(time.time() - t))

    def run_marker_analysis(self, markings=None):

        _logger = self._logger

        t = time.time()
        if markings is None:
            markings = len(self["fixture"].model.orientation_marks_x)

        analysis_img = self._get_image_in_correct_scale(
            self.MARKER_DETECTION_DPI)
        scale_factor = self.get_dpi_factor_to_target(self.MARKER_DETECTION_DPI)

        _logger.info(
            "Running marker detection ({0} markers on {1} ({2}) using {3}, scale {4})"
            .format(markings, self.im_path, analysis_img.shape,
                    self["reference"].get_marker_path(), scale_factor))

        im_analysis = image_fixture.FixtureImage(
            image=analysis_img,
            pattern_image_path=self["reference"].get_marker_path(),
            scale=scale_factor)

        x_positions_correct_scale, y_positions_correct_scale = im_analysis.find_pattern(
            markings=markings)

        self["current"].model.orientation_marks_x = x_positions_correct_scale
        self["current"].model.orientation_marks_y = y_positions_correct_scale

        if x_positions_correct_scale is None or y_positions_correct_scale is None:

            _logger.error("No markers found")

        _logger.debug(
            "Marker Detection complete (acc {0} s)".format(time.time() - t))

    def _get_image_in_correct_scale(self, target_dpi):

        if self._original_dpi != target_dpi:

            return image_basics.Quick_Scale_To_im(
                im=self.im, scale=self.get_dpi_factor_to_target(target_dpi))

        return self.im

    def _set_current_mark_order(self):

        x_centered, y_centered = self._get_centered_mark_positions("current")
        x_centered_ref, y_centered_ref = self._get_centered_mark_positions(
            "reference")

        if all(o is not None and o.size > 0
               for o in (x_centered, y_centered, x_centered_ref,
                         y_centered_ref)):

            length = np.sqrt(x_centered**2 + y_centered**2)
            length_ref = np.sqrt(x_centered_ref**2 + y_centered_ref**2)

            sort_order, sort_error = self._get_sort_order(length, length_ref)

            self._logger.debug(
                "Found sort order that matches the reference {0} (error {1})".
                format(sort_order, sort_error))

            self.__set_current_mark_order(sort_order)

        else:

            self._logger.critical("Missmatch in number of markings!")

    def __set_current_mark_order(self, sort_order):

        current_model = self["current"].model
        current_model.orientation_marks_x = current_model.orientation_marks_x[
            sort_order]
        current_model.orientation_marks_y = current_model.orientation_marks_y[
            sort_order]

    def _get_centered_mark_positions(self, source="current"):

        x_positions = self[source].model.orientation_marks_x
        y_positions = self[source].model.orientation_marks_y

        if x_positions is None or y_positions is None:
            return None, None

        x_positions = np.array(x_positions)
        y_positions = np.array(y_positions)

        marking_center = np.array((x_positions.mean(), y_positions.mean()))

        x_centered = x_positions - marking_center[0]
        y_centered = y_positions - marking_center[1]

        return x_centered, y_centered

    @staticmethod
    def _get_sort_order(length, reference_length):

        s = range(len(length))

        length_deltas = []
        sort_orders = []
        for sort_order in itertools.permutations(s):

            length_deltas.append(
                (length[list(sort_order)] - reference_length)**2)
            sort_orders.append(sort_order)

        length_deltas = np.array(length_deltas).sum(1)
        return list(sort_orders[length_deltas.argmin()]), np.sqrt(
            length_deltas.min())

    def _get_rotation(self):

        x_centered, y_centered = self._get_centered_mark_positions("current")
        x_centered_ref, y_centered_ref = self._get_centered_mark_positions(
            "reference")
        length = np.sqrt(x_centered**2 + y_centered**2)
        length_ref = np.sqrt(x_centered_ref**2 + y_centered_ref**2)

        rotations = np.arccos(x_centered / length)
        rotations = rotations * (y_centered > 0) + -1 * rotations * (y_centered
                                                                     < 0)

        rotations_ref = np.arccos(x_centered_ref / length_ref)
        rotations_ref = rotations_ref * (
            y_centered_ref > 0) + -1 * rotations_ref * (y_centered_ref < 0)

        rotation = (rotations - rotations_ref).mean()
        """:type : float"""
        if np.abs(rotation) < 0.001:
            return 0
        else:
            return rotation

    def _get_offset(self):

        current_model = self['current'].model
        ref_model = self['reference'].model

        x_delta = current_model.orientation_marks_x - ref_model.orientation_marks_x
        y_delta = current_model.orientation_marks_y - ref_model.orientation_marks_y
        return x_delta.mean(), y_delta.mean()

    def get_plate_im_section(self, plate_model, scale=1.0):
        """

        :type plate_model: scanomatic.models.fixture_models.FixturePlateModel
        """
        im = self.im

        if im is not None and plate_model is not None:

            try:

                return im[max(plate_model.y1 *
                              scale, 0):min(plate_model.y2 *
                                            scale, im.shape[0]),
                          max(plate_model.x2 *
                              scale, 0):min(plate_model.x2 *
                                            scale, im.shape[1])]

            except (IndexError, TypeError):

                return None

    def get_grayscale_im_section(self, grayscale_model, scale=1.0):
        """

        :type grayscale_model: scanomatic.models.fixture_models.GrayScaleAreaModel
        """
        im = self.im

        if im is not None and grayscale_model is not None:

            try:

                return im[
                    int(round(max(grayscale_model.y1 * scale, 0))):int(
                        round(min(grayscale_model.y2 * scale, im.shape[0]))),
                    int(round(max(grayscale_model.x1 * scale, 0))):int(
                        round(min(grayscale_model.x2 * scale, im.shape[1])))]

            except (IndexError, TypeError):

                return None

    def analyse_grayscale(self):

        current_model = self["current"].model
        im = self.get_grayscale_im_section(current_model.grayscale, scale=1.0)

        if im is None or 0 in im.shape:
            err = "No valid grayscale area. "
            if self.im is None:
                self._logger.error(err + "No image loaded")
            elif current_model.grayscale is None:
                self._logger.error(err + "Grayscale area model not set")
            elif im is None:
                self._logger.error(
                    err +
                    "Image (shape {0}) could not be sliced according to {1}".
                    format(self.im.shape, dict(**current_model.grayscale)))
            elif 0 in im.shape:
                self._logger.error(
                    err +
                    "Grayscale area has bad shape ({0})".format(im.shape))

            return False

        current_model.grayscale.values = image_grayscale.get_grayscale(
            self, current_model.grayscale)[1]
        if current_model.grayscale.values is None:
            self._logger.error("Grayscale detection failed")
            return False

    def _set_area_relative(self,
                           area,
                           rotation=None,
                           offset=(0, 0),
                           issues={}):
        """

        :type area: scanomatic.models.fixture_models.FixturePlateModel |
            scanomatic.models.fixture_models.GrayScaleAreaModel
        :type issues: dict
        :type offset: tuple(int)
        """

        if rotation:
            self._logger.warning(
                "Not supporting rotations yet (got {0})".format(rotation))
            # area.x1, area.y1 = _get_rotated_vector(area.x1, area.y1, rotation)
            # area.x2, area.y2 = _get_rotated_vector(area.x2, area.y2, rotation)

        for dim, keys in {1: ('x1', 'x2'), 0: ('y1', 'y2')}.items():
            for key in keys:
                area[key] = round(area[key] + offset[dim])
                if area[key] > self.EXPECTED_IM_SIZE[dim]:
                    self._logger.warning(
                        "{0} value ({1}) outside image, setting to img border".
                        format(key, area[key]))
                    area[key] = self.EXPECTED_IM_SIZE[dim]
                    issues['overflow'] = area.index if hasattr(
                        area, "index") else "Grayscale"
                elif area[key] < 0:
                    self._logger.warning(
                        "{0} value ({1}) outside image, setting to img border".
                        format(key, area[key]))
                    area[key] = 0
                    issues['overflow'] = area.index if hasattr(
                        area, "index") else "Grayscale"

    def set_current_areas(self, issues):
        """

        :param issues: reported issues
         :type issues: dict
        :return:
        """

        self._set_current_mark_order()
        offset = self._get_offset()
        rotation = self._get_rotation()
        if abs(rotation) > 0.05:
            issues['rotation'] = rotation
        current_model = self["current"].model
        ref_model = self["fixture"].model

        self._logger.info(
            "Positions on current '{0}' will be moved {1} and rotated {2} due to diff to reference {3}"
            .format(current_model.name, offset, rotation, ref_model.name))

        current_model.plates = type(current_model.plates)(
            FixturePlateFactory.copy(plate) for plate in ref_model.plates)

        for plate in current_model.plates:
            self._set_area_relative(plate, rotation, offset, issues)

        self._set_area_relative(current_model.grayscale, rotation, offset,
                                issues)
Пример #13
0
from enum import Enum
import numpy as np
from scipy.optimize import leastsq
from itertools import izip
from scipy.stats import linregress
from scanomatic.io.logger import Logger

_logger = Logger("Growth Phenotypes")


def _linreg_helper(X, Y):
    return linregress(X, Y)[0::4]


def get_derivative(curve_strided, times_strided):

    linreg_values = []

    log2_strided_curve = np.log2(curve_strided)
    filters = np.isfinite(log2_strided_curve)
    min_size = curve_strided.shape[-1] - 1

    for times, value_segment, filt in izip(times_strided, log2_strided_curve,
                                           filters):

        if filt.sum() >= min_size:
            linreg_values.append(
                _linreg_helper(times[filt], value_segment[filt]))
        else:
            linreg_values.append((np.nan, np.nan))
Пример #14
0
import os
from multiprocessing import Process
from time import sleep
from subprocess import call, STDOUT

from scanomatic.io.logger import Logger

_logger = Logger("Daemonizer")


def _daemon_process(path_to_exec, std_out_path, args, shell):

    with open(std_out_path, 'w') as fh:
        args = list(str(a) for a in args)

        if shell:
            fh.write("*** LAUNCHING IN SHELL: {0} ***\n\n".format(" ".join([path_to_exec] + list(args))))
            retcode = call(" ".join([path_to_exec] + args), stderr=STDOUT, stdout=fh, shell=True)
        else:
            fh.write("*** LAUNCHING WITHOUT SHELL: {0} ***\n\n".format([path_to_exec] + list(args)))
            retcode = call([path_to_exec] + args, stderr=STDOUT, stdout=fh, shell=False)

        if retcode:
            fh.write("\n*** DAEMON EXITED WITH CODE {0} ***\n".format(retcode))
        else:
            fh.write("\n*** DAEMON DONE ***\n")


def daemon(path_to_executable, std_out=os.devnull, daemon_args=tuple(), shell=True):

    _logger.info("Launching daemon {0} (args={2}, {3}), outputting to {1} ".format(
Пример #15
0
class GriddingHistory(object):
    """This class keeps track of the gridding-histories of the fixture
    using the configuration-file in the fixtures-directory"""

    plate_pinning_pattern = "plate_{0}_pinning_{1}"
    pinning_formats = ((8, 12), (16, 24), (32, 48), (64, 96))
    plate_area_pattern = "plate_{0}_area"

    def __init__(self, fixture_settings):
        """
        :type fixture_settings: scanomatic.io.fixtures.FixtureSettings
        """
        self._logger = Logger("Gridding History {0}".format(
            fixture_settings.model.name))
        self._fixture_settings = fixture_settings
        self._models_per_plate_pinning = defaultdict(dict)

    def load(self):

        self._models_per_plate_pinning.clear()
        history = GridHistoryFactory.serializer.load(self.path)
        for model in history:
            """:type : scanomatic.models.analysis_model.GridHistoryModel"""
            self._models_per_plate_pinning[(model.plate,
                                            model.pinning)].append(model)

    @property
    def path(self):

        return Paths().fixture_grid_history_pattern.format(
            self._fixture_settings.path)

    @property
    def _name(self):
        return self._fixture_settings.model.name

    def _get_gridding_history(self, plate, pinning_format):

        return [(model.offset_x, model.offset_y, model.delta_x, model.delta_y)
                for model in self._models_per_plate_pinning[(
                    plate, pinning_format)].values()]

    def get_history_model(self, project_id, plate, pinning):

        models = [
            model for model in self._models_per_plate_pinning[(plate, pinning)]
        ]

        if project_id in models:
            return models[project_id]

        self._logger.warning(
            "No history exists for project {0} plate {1} pinnig {2}".format(
                project_id, plate, pinning))
        return None

    def get_gridding_history(self, plate, pinning_format):

        history = self._get_gridding_history(plate, pinning_format)

        if not history:
            self._logger.info(
                "No history in {2} on plate {0} format {1}".format(
                    plate, pinning_format, self._name))

            return None

        self._logger.info(
            "Returning history for {0} plate {1} format {2}".format(
                self._name, plate, pinning_format))
        return np.array(history)

    def set_gridding_parameters(self, project_id, pinning_format, plate,
                                center, spacings):

        model = GridHistoryFactory.create(project_id=project_id,
                                          pinnig=pinning_format,
                                          plate=plate,
                                          center=center,
                                          delta=spacings)

        if GridHistoryFactory.validate(model):

            if not GridHistoryFactory.serializer.dump(model, self.path):

                self._logger.warning("Could not write grid history")
                return False
        else:

            self._logger.warning("This is not a valid grid history")
            return False

        self._logger.info(
            "Setting history {0} on fixture {1} for {2} {3}".format(
                center + spacings, self._name, project_id, plate))

        self._models_per_plate_pinning[(
            model.plate, model.pinning)][model.project_id] = model
        return True

    def unset_gridding_parameters(self, project_id, pinning_format, plate):

        model = self.get_history_model(project_id, plate, pinning_format)
        if model:
            GridHistoryFactory.serializer.purge(model, self.path)

    def reset_gridding_history(self, plate):

        for plate_in_history, pin_format in self._models_per_plate_pinning:
            if plate == plate_in_history:
                for model in self._models_per_plate_pinning[(plate_in_history,
                                                             pin_format)]:
                    GridHistoryFactory.serializer.purge(model, self.path)
            del self._models_per_plate_pinning[(plate_in_history, pin_format)]

    def reset_all_gridding_histories(self):

        GridHistoryFactory.serializer.purge_all(self.path)
        self._models_per_plate_pinning.clear()
Пример #16
0
class GridCell(object):

    MAX_THRESHOLD = 4200
    MIN_THRESHOLD = 0
    _logger = Logger("Grid Cell")

    def __init__(self, identifier, polynomial_coeffs, save_extra_data=False):

        self._identifier = identifier
        self.position = tuple(identifier[-1])
        self.save_extra_data = save_extra_data
        self._polynomial_coeffs = polynomial_coeffs
        self._adjustment_warning = False
        self.xy1 = []
        self.xy2 = []
        self.source = None
        self.ready = False
        self._previous_image = None
        self.image_index = -1
        self.features = AnalysisFeaturesFactory.create(index=tuple(
            self.position),
                                                       data={})
        self._analysis_items = {}
        """:type: dict[scanomatic.models.analysis_model.COMPARTMENTS | scanomatic.image_analysis.grid_cell_extra.CellItem]"""
        self._set_empty_analysis_items()

    def _set_empty_analysis_items(self):

        for item_name in COMPARTMENTS:

            self._analysis_items[item_name] = None

    def __str__(self):

        s = "< {0}".format(self._identifier)

        if self.source is None:

            s += " No image set"

        else:

            s += " Image size: {0}".format(self.source.shape)

        s += " Layers: {0} >".format(self._analysis_items.keys())

        return s

    def __repr__(self):

        return self.__str__()

    def set_grid_coordinates(self, grid_cell_corners):

        flipped_long_axis_position = grid_cell_corners.shape[
            2] - self.position[0] - 1
        self.xy1 = grid_cell_corners[:, 0, flipped_long_axis_position,
                                     self.position[1]].astype(np.int)
        self.xy2 = grid_cell_corners[:, 1, flipped_long_axis_position,
                                     self.position[1]].astype(np.int)

    def set_new_data_source_space(self,
                                  space=VALUES.Cell_Estimates,
                                  bg_sub_source=None,
                                  polynomial_coeffs=None):

        if space is VALUES.Cell_Estimates:

            if bg_sub_source is not None:

                feature_array = self.source[np.where(bg_sub_source)]
                # bg_sub = tmean(feature_array,
                #                mquantiles(feature_array, prob=[0.25, 0.75]))
                bg_sub = iqr_mean(feature_array)
                if not np.isfinite(bg_sub):
                    bg_sub = np.mean(feature_array)
                    GridCell._logger.warning(
                        "{0} caused background mean ({1}) due to inf".format(
                            self._identifier, bg_sub))

                self.source -= bg_sub

            self.source[self.source < self.MIN_THRESHOLD] = self.MIN_THRESHOLD

            if polynomial_coeffs is not None:

                self.source = np.polyval(polynomial_coeffs, self.source)

            self._set_max_value_filter()

        self.push_source_data_to_cell_items()

    def _set_max_value_filter(self):

        max_detect_filter = self.source > self.MAX_THRESHOLD

        if self._adjustment_warning != max_detect_filter.any():
            self._adjustment_warning = not self._adjustment_warning
            if self._adjustment_warning:
                self._logger.warning(
                    "{0} got {1} pixel-values overshooting {2}.".format(
                        self._identifier, max_detect_filter.sum(),
                        self.MAX_THRESHOLD) +
                    " Further warnings for this colony suppressed.")
            else:
                self._logger.info(
                    "{0} no longer have pixels that reach {1} depth.".format(
                        self._identifier, self.MAX_THRESHOLD))

    def push_source_data_to_cell_items(self):
        for item_names in self._analysis_items.keys():
            self._analysis_items[item_names].grid_array = self.source

    def get_item(self, item_name):

        if item_name in self._analysis_items:

            return self._analysis_items[item_name]

        else:

            return None

    def analyse(self, detect=True, remember_filter=True):
        """get_analysis iterates through all possible cell items
        and runs their detect and do_analysis if they are attached.

        The cell items' features dictionaries are put inside a
        dictionary with the items' names as keys.

        If cell item is not attached, a None is put in the
        dictionary to avoid key errors..
        """

        background = self._analysis_items[COMPARTMENTS.Background]

        if detect:
            self.detect(remember_filter=remember_filter)

        if background.filter_array.sum() == 0:
            self.clear_features()
        else:
            self._analyse()

    def get_save_data_path(self, base_path):

        if base_path is None:
            base_path = Paths().log

        return os.path.join(
            base_path, "grid_cell_{0}_{1}_{2}".format(
                self.image_index, self._identifier[0][1],
                "_".join(map(str, self._identifier[-1][::-1]))))

    def save_data_image(self, suffix="", base_path=None):

        base_path = self.get_save_data_path(base_path)
        np.save(base_path + suffix + ".image.npy", self.source)

    def save_data_detections(self, base_path=None):

        base_path = self.get_save_data_path(base_path)

        blob = self._analysis_items[COMPARTMENTS.Blob]
        background = self._analysis_items[COMPARTMENTS.Background]

        np.save(base_path + ".background.filter.npy", background.filter_array)
        np.save(base_path + ".image.cells.npy", background.grid_array)
        np.save(base_path + ".blob.filter.npy", blob.filter_array)
        np.save(base_path + ".blob.trash.current.npy", blob.trash_array)
        np.save(base_path + ".blob.trash.old.npy", blob.old_trash)

    def clear_features(self):

        for item in self._analysis_items.itervalues():

            if item:

                item.features.data.clear()

    def _analyse(self):

        background = self._analysis_items[COMPARTMENTS.Background]

        self.set_new_data_source_space(
            space=VALUES.Cell_Estimates,
            bg_sub_source=background.filter_array,
            polynomial_coeffs=self._polynomial_coeffs)

        for item in self._analysis_items.itervalues():

            if item:

                item.set_data_source(self.source)
                item.do_analysis()

    def detect(self, remember_filter=True):

        blob = self._analysis_items[COMPARTMENTS.Blob]
        background = self._analysis_items[COMPARTMENTS.Background]

        blob.detect(remember_filter=remember_filter)
        background.detect()

    def attach_analysis(self,
                        blob=True,
                        background=True,
                        cell=True,
                        blob_detect=grid_cell_extra.BlobDetectionTypes.DEFAULT,
                        run_detect=False,
                        center=None,
                        radius=None):
        """attach_analysis connects the analysis modules to the Grid_Cell.

        Function has three optional boolean arguments:

        @blob           Attaches blob item (default)

        @background     Attaches background item (default)
                        Only possible if blob is attached

        @cell           Attaches cell item (default)

        @use_fallback_detection         Causes simple thresholding instead
                        of more sophisticated detection (default False)

        @run_detect     Causes the initiation to run detection

        @center         A manually set blob centrum (if set
                        radius must be set as well)
                        (if not supplied, blob will be detected
                        automatically)

       @radius          A manually set blob radus (if set
                        center must be set as well)
                        (if not supplied, blob will be detected
                        automatically)"""

        if cell:
            item = grid_cell_extra.Cell((self._identifier, COMPARTMENTS.Total),
                                        self.source,
                                        run_detect=run_detect)
            self.features.data[item.features.index] = item.features
            self._analysis_items[item.features.index] = item

        if blob:

            item = grid_cell_extra.Blob((self._identifier, COMPARTMENTS.Blob),
                                        self.source,
                                        blob_detect=blob_detect,
                                        run_detect=run_detect,
                                        center=center,
                                        radius=radius)

            self.features.data[item.features.index] = item.features
            self._analysis_items[item.features.index] = item

        if background and self._analysis_items[COMPARTMENTS.Blob]:

            item = grid_cell_extra.Background(
                (self._identifier, COMPARTMENTS.Background),
                self.source,
                self._analysis_items[COMPARTMENTS.Blob],
                run_detect=run_detect)

            self.features.data[item.features.index] = item.features
            self._analysis_items[item.features.index] = item

        self.features.shape = (len(self.features.data), )
        self.set_ready_state()

    def set_ready_state(self):

        self.ready = any(self._analysis_items.values())
Пример #17
0
class Serializer(object):
    def __init__(self, factory):
        """

        :type factory: AbstractModelFactory
        """
        self._factory = factory
        self._logger = Logger(factory.__name__)

    def dump(self, model, path, overwrite=False):

        if self._has_section_head_and_is_valid(model):

            if overwrite:

                conf = LinkerConfigParser(id=path, allow_no_value=True)
                section = self.get_section_name(model)
                self.serialize_into_conf(model, conf, section)
                return SerializationHelper.save_config(conf, path)

            else:

                with SerializationHelper.get_config(path) as conf:

                    self._purge_tree(conf, model)
                    section = self.get_section_name(model)
                    self.serialize_into_conf(model, conf, section)

                    return SerializationHelper.save_config(conf, path)

        return False

    def dump_to_filehandle(self, model, filehandle, as_if_appending=False):

        if self._has_section_head_and_is_valid(model):

            section = self.get_section_name(model)
            with LinkerConfigParser(id=id(filehandle),
                                    clear_links=False,
                                    allow_no_value=True) as conf:

                if 'r' in filehandle.mode:
                    fh_pos = filehandle.tell()
                    filehandle.seek(0)
                    conf.readfp(filehandle)
                    if as_if_appending:
                        filehandle.seek(0, 2)
                    else:
                        filehandle.seek(fh_pos)

                section = _SectionsLink.get_next_free_section(conf, section)
                _SectionsLink.add_section_for_non_link(conf, section)
                self.serialize_into_conf(model, conf, section)

                if 'r' in filehandle.mode:
                    filehandle.seek(0)
                    filehandle.truncate()

                conf.write(filehandle)
            return True
        return False

    def _has_section_head(self, model):

        head = self.get_section_name(model)
        return bool(len(head))

    def _has_section_head_and_is_valid(self, model):

        factory = self._factory
        valid = factory.validate(model)

        if self._has_section_head(model) and valid:
            return True

        if not self._has_section_head(model):
            self._logger.warning("Factory does not know head for sections")

        if not valid:
            self._logger.warning(
                "Model {0} does not have valid data".format(model))
            for invalid in factory.get_invalid_names(model):
                self._logger.error(
                    "Faulty value in model {0} for {1} as {2}".format(
                        model, invalid, model[invalid]))

        return False

    def purge(self, model, path):

        with SerializationHelper.get_config(path) as conf:

            if conf:
                self._purge_tree(conf, model)
                return SerializationHelper.save_config(conf, path)

        return False

    def _purge_tree(self, conf, model):
        def add_if_points_to_subsection():

            obj = SerializationHelper.unserialize(conf.get(section, key),
                                                  object)

            try:

                sections.append(obj.section)

            except AttributeError:

                try:
                    # TODO: Should really use datastructure
                    for item in obj:
                        sections.append(
                            SerializationHelper.unserialize(item,
                                                            object).section)
                except (AttributeError, TypeError):
                    pass

        sections = [self.get_section_name(model)]
        index = 0
        while index < len(sections):
            section = sections[index]
            if not conf.has_section(section):
                index += 1
                continue

            for key in conf.options(section):
                add_if_points_to_subsection()

            conf.remove_section(section)

    @staticmethod
    def purge_all(path):

        with SerializationHelper.get_config(None) as conf:
            return SerializationHelper.save_config(conf, path)

    def load(self, path):

        with SerializationHelper.get_config(path) as conf:

            if conf:
                return tuple(self._unserialize(conf))

        return tuple()

    def load_first(self, path):

        with SerializationHelper.get_config(path) as conf:

            if conf:
                try:
                    return self._unserialize(conf).next()
                except StopIteration:
                    self._logger.error("No model in file '{0}'".format(path))
            else:
                self._logger.error("No file named '{0}'".format(path))
        return None

    def _unserialize(self, conf):

        return (self.unserialize_section(conf, section)
                for section in conf.sections()
                if self._factory.all_keys_valid(conf.options(section)))

    def unserialize_section(self, conf, section):

        factory = self._factory

        try:
            if not factory.all_keys_valid(conf.options(section)):
                self._logger.warning(
                    "{1} Refused section {0} because keys {2}".format(
                        section, factory, conf.options(section)))
                return None

        except NoSectionError:
            self._logger.warning(
                "Refused section {0} because missing in file, though claimed to be there"
                .format(section))
            return None
        model = {}

        for key, dtype in factory.STORE_SECTION_SERIALIZERS.items():

            if key in conf.options(section):

                try:
                    value = conf.get(section, key)
                except ValueError:
                    self._logger.critical(
                        "Could not parse section {0}, key {1}".format(
                            section, key))
                    value = None

                if isinstance(dtype, tuple):

                    value = SerializationHelper.unserialize_structure(
                        value, dtype, conf)

                elif isinstance(dtype, types.FunctionType):

                    value = SerializationHelper.unserialize(value, dtype)

                elif isinstance(dtype, type) and issubclass(
                        dtype, Model) and value is not None:

                    obj = SerializationHelper.unserialize(value, _SectionsLink)
                    if isinstance(obj, _SectionsLink):
                        value = obj.retrieve_model(conf)
                    else:
                        # This handles backward compatibility when models were pickled
                        value = obj

                else:

                    value = SerializationHelper.unserialize(value, dtype)

                model[key] = value

        return factory.create(**model)

    def load_serialized_object(self, serialized_object):

        return tuple(self._unserialize(MockConfigParser(serialized_object)))

    def serialize(self, model):

        if not self._has_section_head(model):
            raise ValueError("Need a section head for serialization")

        with LinkerConfigParser(id=id(model)) as conf:

            conf = self.serialize_into_conf(model, conf,
                                            self.get_section_name(model))
            return tuple((section, {k: v
                                    for k, v in conf.items(section)})
                         for section in conf.sections())

    def serialize_into_conf(self, model, conf, section):

        # self._logger.info("Serializing {0} into '{1}' of {2}".format(model, section, conf))

        if conf.has_section(section):
            conf.remove_section(section)

        conf.add_section(section)

        factory = self._factory
        for key, dtype in factory.STORE_SECTION_SERIALIZERS.items():

            self._serialize_item(model, key, dtype, conf, section, factory)

        return conf

    @staticmethod
    def _serialize_item(model, key, dtype, conf, section, factory):

        obj = copy.deepcopy(model[key])

        if isinstance(dtype, tuple):

            obj = _toggleTuple(dtype, obj, False)
            dtype_leaf = dtype[-1]
            for coord, item in _get_coordinates_and_items_to_validate(
                    dtype, model[key]):
                if isinstance(dtype_leaf, type) and issubclass(
                        dtype_leaf, Model):
                    subfactory = factory.get_sub_factory(item)
                    link = _SectionsLink.set_link(subfactory, item, conf)
                    subfactory.serializer.serialize_into_conf(
                        item, conf, link.section)
                    _update_object_at(
                        obj, coord,
                        SerializationHelper.serialize(link, _SectionsLink))
                else:
                    _update_object_at(
                        obj, coord,
                        SerializationHelper.serialize(item, dtype_leaf))

            conf.set(section, key,
                     SerializationHelper.serialize_structure(obj, dtype))

        elif isinstance(dtype, type) and issubclass(dtype,
                                                    Model) and obj is not None:

            subfactory = factory.get_sub_factory(obj)

            conf.set(
                section, key,
                SerializationHelper.serialize(
                    _SectionsLink.set_link(subfactory, obj, conf),
                    _SectionsLink))
            subfactory.serializer.serialize_into_conf(
                obj, conf,
                _SectionsLink.get_link(obj).section)

        else:
            conf.set(section, key, SerializationHelper.serialize(obj, dtype))

    def get_section_name(self, model):

        if isinstance(self._factory.STORE_SECTION_HEAD, types.StringTypes):
            return self._factory.STORE_SECTION_HEAD
        elif isinstance(self._factory.STORE_SECTION_HEAD, list):
            heads = [(str(model[head]) if model[head] is not None else '')
                     for head in self._factory.STORE_SECTION_HEAD]
            if '' in heads:
                return ''
            else:
                return ", ".join(heads)
        elif isinstance(self._factory.STORE_SECTION_HEAD, tuple):
            for key in self._factory.STORE_SECTION_HEAD:
                try:
                    if key in model:
                        model = model[key]
                    else:
                        return ''
                except TypeError:
                    return ''

            return str(model) if model is not None else ''
        else:
            return ''
Пример #18
0
from scanomatic.models.factories.compile_project_factory import CompileProjectFactory
from scanomatic.models.factories.features_factory import FeaturesFactory
from scanomatic.models.factories.scanning_factory import ScanningModelFactory

from . import qc_api
from . import analysis_api
from . import compilation_api
from . import calibration_api
from . import scan_api
from . import management_api
from . import tools_api
from . import data_api
from .general import get_2d_list

_url = None
_logger = Logger("UI-server")


def launch_server(is_local=None, port=None, host=None, debug=False):

    global _url

    app = Flask("Scan-o-Matic UI", template_folder=Paths().ui_templates)

    rpc_client = get_client(admin=True)

    if rpc_client.local and rpc_client.online is False:
        rpc_client.launch_local()

    if port is None:
        port = Config().ui_server.port
Пример #19
0
from flask import request, Flask, jsonify
from itertools import product, chain
import os
import glob

from scanomatic.ui_server.general import safe_directory_name
from scanomatic.io.app_config import Config
from scanomatic.io.logger import Logger
from scanomatic.data_processing.phenotyper import path_has_saved_project_state

_logger = Logger("Tools API")


def valid_range(settings):
    return 'min' in settings and 'max' in settings


def add_routes(app):
    """

    Args:
        app (Flask): The flask app to decorate
    """
    @app.route("/api/tools/selection", methods=['POST'])
    @app.route("/api/tools/selection/<operation>", methods=['POST'])
    def tools_create_selection(operation='rect'):
        """Converts selection ranges to api-understood selections.

        _Note_ that the range-boundary uses inclusive min and
        exclusive max indices.
Пример #20
0
import numpy as np
from enum import Enum
import json
from itertools import izip
import os
import shutil
from scipy.optimize import curve_fit
import time
from datetime import datetime
from dateutil import tz
from scipy.stats import linregress

from scanomatic.io.logger import Logger
from scanomatic.io.paths import Paths

_logger = Logger("Calibration")


class CalibrationEntry(Enum):
    image = 0
    """:type : CalibrationEntry"""
    colony_name = 1
    """:type : CalibrationEntry"""
    target_value = 2
    """:type : CalibrationEntry"""
    source_values = (3, 0)
    """:type : CalibrationEntry"""
    source_value_counts = (3, 1)
    """:type : CalibrationEntry"""

Пример #21
0
class CompilationResults(object):
    def __init__(self,
                 compilation_path=None,
                 compile_instructions_path=None,
                 scanner_instructions_path=None,
                 sort_mode=FIRST_PASS_SORTING.Time):

        self._logger = Logger("Compilation results")
        self._compilation_path = compilation_path
        self._compile_instructions = None
        self._scanner_instructions = None
        """:type : scanomatic.models.scanning_model.ScanningModel"""
        self.load_scanner_instructions(scanner_instructions_path)
        self._plates = None
        self._plate_position_keys = None
        self._image_models = []
        self._used_models = []
        self._current_model = None
        self._loading_length = 0
        if compile_instructions_path:
            self._load_compile_instructions(compile_instructions_path)
        if compilation_path:
            self._load_compilation(self._compilation_path, sort_mode=sort_mode)

    @classmethod
    def create_from_data(cls,
                         path,
                         compile_instructions,
                         image_models,
                         used_models=None,
                         scan_instructions=None):

        if used_models is None:
            used_models = []

        new = cls()
        new._compilation_path = path
        new._compile_instructions = CompileProjectFactory.copy(
            compile_instructions)
        new._image_models = CompileImageAnalysisFactory.copy_iterable_of_model(
            list(image_models))
        new._used_models = CompileImageAnalysisFactory.copy_iterable_of_model(
            list(used_models))
        new._loading_length = len(new._image_models)
        new._scanner_instructions = scan_instructions
        return new

    def load_scanner_instructions(self, path=None):
        """

        Args:
            path:  Path to the instrucitons or None to infer it


        """
        if path is None:
            try:
                path = glob(
                    os.path.join(
                        os.path.dirname(self._compilation_path),
                        Paths().scan_project_file_pattern.format('*')))[0]
            except IndexError:
                self._logger.warning(
                    "No information of start time of project, can't safely be joined with others"
                )
                return

        self._scanner_instructions = ScanningModelFactory.serializer.load_first(
            path)

    def _load_compile_instructions(self, path):

        try:
            self._compile_instructions = CompileProjectFactory.serializer.load_first(
                path)
        except IndexError:
            self._logger.error("Could not load path {0}".format(path))
            self._compile_instructions = None

    def _load_compilation(self, path, sort_mode=FIRST_PASS_SORTING.Time):

        images = CompileImageAnalysisFactory.serializer.load(path)
        self._logger.info("Loaded {0} compiled images".format(len(images)))

        self._reindex_plates(images)

        if sort_mode is FIRST_PASS_SORTING.Time:
            self._image_models = list(
                CompileImageAnalysisFactory.
                copy_iterable_of_model_update_indices(images))
        else:
            self._image_models = list(
                CompileImageAnalysisFactory.copy_iterable_of_model_update_time(
                    images))

        self._loading_length = len(self._image_models)

    @staticmethod
    def _reindex_plates(images):

        for image in images:

            if image and image.fixture and image.fixture.plates:

                for plate in image.fixture.plates:
                    plate.index -= 1

    def __len__(self):

        return self._loading_length

    def __getitem__(self, item):
        """


        :rtype: scanomatic.models.compile_project_model.CompileImageAnalysisModel
        """
        if not self._image_models:
            return None

        if item < 0:
            item %= len(self._image_models)

        try:
            return sorted(self._image_models,
                          key=lambda x: x.image.time_stamp)[item]
        except (ValueError, IndexError):
            return None

    def __add__(self, other):
        """

        :type other: CompilationResults
        """

        start_time_difference = other.start_time - self.start_time

        other_start_index = len(self)
        other_image_models = []
        other_directory = os.path.dirname(other._compilation_path)
        for index in range(len(other)):
            model = CompileImageAnalysisFactory.copy(other[index])
            """:type : scanomatic.models.compile_project_model.CompileImageAnalysisModel"""

            model.image.time_stamp += start_time_difference
            model.image.index += other_start_index
            self._update_image_path_if_needed(model, other_directory)
            other_image_models.append(model)

        other_image_models += self._image_models
        other_image_models = sorted(other_image_models,
                                    key=lambda x: x.image.time_stamp)

        return CompilationResults.create_from_data(self._compilation_path,
                                                   self._compile_instructions,
                                                   other_image_models,
                                                   self._used_models,
                                                   self._scanner_instructions)

    def _update_image_path_if_needed(self, model, directory):
        if not os.path.isfile(model.image.path):
            image_name = os.path.basename(model.image.path)
            if os.path.isfile(os.path.join(directory, image_name)):
                model.image.path = os.path.join(directory, image_name)
                return
        self._logger.warning("Couldn't locate the file {0}".format(
            model.image.path))

    @property
    def start_time(self):

        if self._scanner_instructions:
            return self._scanner_instructions.start_time
        self._logger.warning(
            "No scanner instructions have been loaded, start time unknown")
        return 0

    @property
    def compile_instructions(self):
        """


        :rtype: scanomatic.models.compile_project_model.CompileInstructionsModel
        """

        return self._compile_instructions

    @property
    def plates(self):

        res = self[-1]
        if res:
            return res.fixture.plates
        return None

    @property
    def last_index(self):

        return len(self._image_models) - 1

    @property
    def total_number_of_images(self):

        return len(self._image_models) + len(self._used_models)

    @property
    def current_image(self):
        """

        :rtype : scanomatic.models.compile_project_model.CompileImageAnalysisModel
        """
        return self._current_model

    @property
    def current_absolute_time(self):

        return self.current_image.image.time_stamp + self.compile_instructions.start_time

    def recycle(self):

        self._image_models += self._used_models
        self._used_models = []
        self._current_model = None

    def get_next_image_model(self):
        """

        :rtype : scanomatic.models.compile_project_model.CompileImageAnalysisModel
        """
        model = self[-1]
        self._current_model = model
        if model:
            self._image_models.remove(model)
            self._used_models.append(model)
        return model

    def dump(self,
             directory,
             new_name=None,
             force_dump_scan_instructions=False):

        self._logger.warning(
            """This functionality has not fully been tested, if you test it and it works fine let Martin konw.
            If it doesn't work, let him know too.""")
        directory = os.path.abspath(directory)
        os.makedirs(directory)
        if new_name is None:
            new_name = os.path.basename(directory)

        try:
            with open(
                    os.path.join(
                        directory,
                        Paths().project_compilation_pattern.format(new_name)),
                    'w') as fh:
                while True:
                    model = CompileImageAnalysisFactory.copy(
                        self.get_next_image_model())
                    self._update_image_path_if_needed(model, directory)
                    if model is None:
                        break
                    CompileImageAnalysisFactory.serializer.dump_to_filehandle(
                        model, fh)
        except IOError:
            self._logger.error("Could not save to directory")
            return

        compile_instructions = os.path.join(
            directory,
            Paths().project_compilation_pattern.format(new_name))
        CompileProjectFactory.serializer.dump(self._compile_instructions,
                                              compile_instructions)

        if not glob(os.path.join(directory, Paths().scan_project_file_pattern.format('*'))) or \
                force_dump_scan_instructions:

            scan_instructions = os.path.join(
                directory,
                Paths().scan_project_file_pattern.format(new_name))
            ScanningModelFactory.serializer.dump(self._scanner_instructions,
                                                 scan_instructions)
Пример #22
0
from scanomatic.image_analysis.image_grayscale import get_grayscale
from scanomatic.io.logger import Logger
from scanomatic.models.factories.fixture_factories import FixtureFactory
from scanomatic.models.fixture_models import GrayScaleAreaModel
from scanomatic.image_analysis.image_basics import Image_Transpose
from scanomatic.image_analysis.grid_cell import GridCell
from scanomatic.image_analysis.grid_array import get_calibration_polynomial_coeffs
from scanomatic.models.analysis_model import COMPARTMENTS, VALUES
from scanomatic.models.factories.analysis_factories import AnalysisFeaturesFactory

from .general import get_fixture_image_by_name, usable_markers, split_areas_into_grayscale_and_plates, \
    get_area_too_large_for_grayscale, get_grayscale_is_valid, usable_plates, image_is_allowed, \
    get_fixture_image, convert_url_to_path


_logger = Logger("Data API")


def _depth(arr, lvl=1):

    if isinstance(arr, ListType) and len(arr) and isinstance(arr[0], ListType):
        _depth(arr[0], lvl + 1)
    else:
        return lvl


def _validate_depth(data):

    depth = _depth(data)

    while depth < 4:
Пример #23
0
class ProjectImage(object):
    def __init__(self, analysis_model, scanning_meta_data):
        """

        :param analysis_model: The model
         :type analysis_model : scanomatic.models.analysis_model.AnalysisModel
        :param scanning_meta_data: scanning info
        :type scanning_meta_data : scanomatic.models.scanning_model.ScanningModel
        :return:
        """
        self._analysis_model = analysis_model
        self._scanning_meta_data = scanning_meta_data
        self._logger = Logger("Analysis Image")

        self._im_loaded = False
        self.im = None

        self._grid_arrays = self._new_grid_arrays
        """:type : dict[int|scanomatic.image_analysis.grid_array.GridArray]"""
        self.features = _get_init_features(self._grid_arrays)

    @property
    def active_plates(self):
        return len(self._grid_arrays)

    def __getitem__(self, key):

        return self._grid_arrays[key]

    @property
    def _new_grid_arrays(self):
        """

        :rtype : dict[int|scanomatic.imageAnalysis.grid_array.GridArray]
        """
        grid_arrays = {}

        for index, pinning in enumerate(self._analysis_model.pinning_matrices):

            if pinning and self._plate_is_analysed(index):

                grid_arrays[index] = grid_array.GridArray(
                    index, pinning, self._analysis_model)

            else:

                if pinning:

                    self._logger.info(
                        "Skipping plate {0} because suppressing non-focal positions"
                        .format(index))

                else:

                    self._logger.info(
                        "Plate {0} not analysed because lacks pinning".format(
                            index))

        return grid_arrays

    def _plate_is_analysed(self, index):

        return not self._analysis_model.suppress_non_focal or index == self._analysis_model.focus_position[
            0]

    def set_grid(self, image_model):
        """

        :type image_model: scanomatic.models.compile_project_model.CompileImageAnalysisModel
        """
        if image_model is None:
            self._logger.critical("No image model to grid on")
            return False

        self.load_image(image_model.image.path)

        if self._im_loaded:

            threads = set()

            for index in self._grid_arrays:

                plate_models = [
                    plate_model for plate_model in image_model.fixture.plates
                    if plate_model.index == index
                ]
                if plate_models:
                    plate_model = plate_models[0]
                else:
                    self._logger.error(
                        "Expected to find a plate model with index {0}, but only have {1}"
                        .format(index, [
                            plate_model.index
                            for plate_model in image_model.fixture.plates
                        ]))
                    continue

                im = self.get_im_section(plate_model)

                if im is None:
                    self._logger.error(
                        "Plate model {0} could not be used to slice image".
                        format(plate_model))
                    continue

                if self._analysis_model.grid_model.gridding_offsets is not None and \
                        index < len(self._analysis_model.grid_model.gridding_offsets) \
                        and self._analysis_model.grid_model.gridding_offsets[index]:

                    reference_folder = self._analysis_model.grid_model.reference_grid_folder
                    if reference_folder:
                        reference_folder = os.path.join(
                            os.path.dirname(
                                self._analysis_model.output_directory),
                            reference_folder)
                    else:
                        reference_folder = self._analysis_model.output_directory

                    if not self._grid_arrays[index].set_grid(
                            im,
                            analysis_directory=self._analysis_model.
                            output_directory,
                            offset=self._analysis_model.grid_model.
                            gridding_offsets[index],
                            grid=os.path.join(
                                reference_folder,
                                Paths().grid_pattern.format(index + 1))):

                        self._logger.error(
                            "Could not use previous gridding with offset")

                else:

                    t = Thread(target=self._grid_arrays[index].detect_grid,
                               args=(im, ),
                               kwargs=dict(analysis_directory=self.
                                           _analysis_model.output_directory))
                    t.start()
                    threads.add(t)

            while threads:
                threads = set(t for t in threads if t.is_alive())
                sleep(0.01)

            call([
                "python", "-c",
                "from scanomatic.util.analysis import produce_grid_images; produce_grid_images('{0}')"
                .format(self._analysis_model.output_directory)
            ])

        return True

    def load_image(self, path):

        try:

            self.im = load_image_to_numpy(path, IMAGE_ROTATIONS.Portrait)
            self._im_loaded = True

        except (TypeError, IOError):

            alt_path = os.path.join(
                os.path.dirname(self._analysis_model.compilation),
                os.path.basename(path))

            self._logger.warning(
                "Failed to load image at '{0}', trying '{1}'.".format(
                    path, alt_path))
            try:

                self.im = load_image_to_numpy(alt_path,
                                              IMAGE_ROTATIONS.Portrait)
                self._im_loaded = True

            except (TypeError, IOError):

                self._im_loaded = False

        if self._im_loaded:
            self._logger.info("Image loaded")
        else:
            self._logger.error("Failed to load image")

        self.validate_rotation()
        self._convert_to_grayscale()

    def _convert_to_grayscale(self):
        if self.im.ndim == 3:
            self.im = np.dot(self.im[..., :3], [0.299, 0.587, 0.144])

    def validate_rotation(self):

        pass

    @property
    def orientation(self):
        """The currently loaded image's rotation considered as first dimension of image array being image rows
        :return:
        """
        if not self._im_loaded:
            return IMAGE_ROTATIONS.Unknown
        elif self.im.shape[0] > self.im.shape[1]:
            return IMAGE_ROTATIONS.Portrait
        else:
            return IMAGE_ROTATIONS.Landscape

    def get_im_section(self, plate_model, im=None):
        def _flip_axis(a, b):

            return b, a

        def _bound(bounds, a, b):
            def bounds_check(bound, val):

                if 0 <= val < bound:
                    return val
                elif val < 0:
                    return 0
                else:
                    return bound - 1

            return ((bounds_check(bounds[0],
                                  a[0]), bounds_check(bounds[0], a[1])),
                    (bounds_check(bounds[1],
                                  b[0]), bounds_check(bounds[1], b[1])))

        if not im:
            if self._im_loaded:
                im = self.im
            else:
                return

        x = sorted((plate_model.x1, plate_model.x2))
        y = sorted((plate_model.y1, plate_model.y2))

        if self.orientation == IMAGE_ROTATIONS.Landscape:
            x, y = _flip_axis(x, y)

        y, x = _bound(im.shape, y, x)

        # In images, the first dimension is typically the y-axis
        section = im[y[0]:y[1], x[0]:x[1]]

        return self._flip_short_dimension(section, im.shape)

    @staticmethod
    def _flip_short_dimension(section, im_shape):

        short_dim = [p == min(im_shape) for p in im_shape].index(True)

        def get_slicer(idx):
            if idx == short_dim:
                # noinspection PyTypeChecker
                return slice(None, None, -1)
            else:
                # noinspection PyTypeChecker
                return slice(None)

        slicer = []
        for i in range(len(im_shape)):
            slicer.append(get_slicer(i))

        return section[slicer]

    def _set_current_grid_move(self, d1, d2):

        self._grid_corrections = np.array((d1, d2))

    def clear_features(self):
        for grid_array in self._grid_arrays.itervalues():
            grid_array.clear_features()

    def analyse(self, image_model):
        """

        :type image_model: scanomatic.models.compile_project_model.CompileImageAnalysisModel
        """
        self.load_image(image_model.image.path)
        self._logger.info("Image loaded")
        if self._im_loaded is False:
            self.clear_features()
            return

        if not image_model.fixture.grayscale.values or not is_valid_grayscale(
                getGrayscale(image_model.fixture.grayscale.name)['targets'],
                image_model.fixture.grayscale.values):

            self._logger.warning("Not a valid grayscale")
            self.clear_features()
            return

        self.features.index = image_model.image.index
        grid_arrays_processed = set()
        # threads = set()
        for plate in image_model.fixture.plates:

            if plate.index in self._grid_arrays:
                grid_arrays_processed.add(plate.index)
                im = self.get_im_section(plate)
                grid_arr = self._grid_arrays[plate.index]
                """:type: scanomatic.image_analysis.grid_array.GridArray"""
                grid_arr.analyse(im, image_model)
                """
                t = Thread(target=grid_arr.analyse, args=(im, image_model))
                t.start()
                threads.add(t)

        while threads:
            threads = set(t for t in threads if t.is_alive())
            sleep(0.01)
        """
        for index, grid_array in self._grid_arrays.iteritems():
            if index not in grid_arrays_processed:
                grid_array.clear_features()

        self._logger.info("Image {0} processed".format(
            image_model.image.index))