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)
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)
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 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))