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:
def __init__(self, factory): """ :type factory: AbstractModelFactory """ self._factory = factory self._logger = Logger(factory.__name__)
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 __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 logger(cls): """ :rtype: scanomatic.io.logger.Logger """ if cls._LOGGER is None: cls._LOGGER = Logger(cls.__name__) return cls._LOGGER
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)
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)
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
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
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
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)
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))
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(
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()
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())
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 ''
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
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.
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"""
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)
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:
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))