def _train( self, template, shapes, increment=False, group=None, shape_forgetting_factor=1.0, verbose=False, batch_size=None ): r""" """ # If batch_size is not None, then we may have a generator, else we # assume we have a list. # If batch_size is not None, then we may have a generator, else we # assume we have a list. if batch_size is not None: # Create a generator of fixed sized batches. Will still work even # on an infinite list. shape_batches = batch(shapes, batch_size) else: shape_batches = [list(shapes)] for k, shape_batch in enumerate(shape_batches): if k == 0: # Rescale the template the reference shape if self.reference_shape is None: # If no reference shape was given, use the mean of the first # batch if batch_size is not None: warnings.warn( "No reference shape was provided. The " "mean of the first batch will be the " "reference shape. If the batch mean is " "not representative of the true mean, " "this may cause issues.", MenpoFitBuilderWarning, ) checks.check_trilist(shape_batch[0], self.transform) self.reference_shape = compute_reference_shape(shape_batch, self.diagonal, verbose=verbose) # Rescale the template the reference shape template = template.rescale_to_pointcloud(self.reference_shape, group=group) # After the first batch, we are incrementing the model if k > 0: increment = True if verbose: print("Computing batch {}".format(k)) # Train each batch self._train_batch( template, shape_batch, increment=increment, group=group, shape_forgetting_factor=shape_forgetting_factor, verbose=verbose, )
def _train(self, images, increment=False, group=None, bounding_box_group_glob=None, verbose=False, batch_size=None): r""" """ # If batch_size is not None, then we may have a generator, else we # assume we have a list. # If batch_size is not None, then we may have a generator, else we # assume we have a list. if batch_size is not None: # Create a generator of fixed sized batches. Will still work even # on an infinite list. image_batches = batch(images, batch_size) else: image_batches = [list(images)] for k, image_batch in enumerate(image_batches): if k == 0: if self.reference_shape is None: # If no reference shape was given, use the mean of the first # batch if batch_size is not None: warnings.warn( "No reference shape was provided. The " "mean of the first batch will be the " "reference shape. If the batch mean is " "not representative of the true mean, " "this may cause issues.", MenpoFitBuilderWarning, ) self.reference_shape = compute_reference_shape( [i.landmarks[group].lms for i in image_batch], self.diagonal, verbose=verbose ) # We set landmarks on the images to archive the perturbations, so # when the default 'None' is used, we need to grab the actual # label to sort out the ambiguity if group is None: group = image_batch[0].landmarks.group_labels[0] # After the first batch, we are incrementing the model if k > 0: increment = True if verbose: print("Computing batch {}".format(k)) # Train each batch self._train_batch( image_batch, increment=increment, group=group, bounding_box_group_glob=bounding_box_group_glob, verbose=verbose, )
def _train(self, images, increment=False, group=None, shape_forgetting_factor=1.0, appearance_forgetting_factor=1.0, verbose=False, batch_size=None): r""" """ # If batch_size is not None, then we may have a generator, else we # assume we have a list. # If batch_size is not None, then we may have a generator, else we # assume we have a list. if batch_size is not None: # Create a generator of fixed sized batches. Will still work even # on an infinite list. image_batches = batch(images, batch_size) else: image_batches = [list(images)] for k, image_batch in enumerate(image_batches): if k == 0: if self.reference_shape is None: # If no reference shape was given, use the mean of the first # batch if batch_size is not None: warnings.warn('No reference shape was provided. The ' 'mean of the first batch will be the ' 'reference shape. If the batch mean is ' 'not representative of the true mean, ' 'this may cause issues.', MenpoFitBuilderWarning) checks.check_landmark_trilist(image_batch[0], self.transform, group=group) self.reference_shape = compute_reference_shape( [i.landmarks[group].lms for i in image_batch], self.diagonal, verbose=verbose) # After the first batch, we are incrementing the model if k > 0: increment = True if verbose: print('Computing batch {}'.format(k)) # Train each batch self._train_batch( image_batch, increment=increment, group=group, shape_forgetting_factor=shape_forgetting_factor, appearance_forgetting_factor=appearance_forgetting_factor, verbose=verbose)
def build_reference_shape(paths, diagonal=200): """Builds the reference shape. Args: paths: paths that contain the ground truth landmark files. diagonal: the diagonal of the reference shape in pixels. Returns: the reference shape. """ landmarks = [] for path in paths: path = Path(path).parent.as_posix() landmarks += [group.lms for group in mio.import_landmark_files( path, verbose=True) if group.lms.n_points == 68] return compute_reference_shape(landmarks, diagonal=diagonal).points.astype(np.float32)
def build_reference_shape(paths, diagonal=200): """Builds the reference shape. Args: paths: paths that contain the ground truth landmark files. diagonal: the diagonal of the reference shape in pixels. Returns: the reference shape. """ landmarks = [] for path in paths: path = Path(path).parent.as_posix() landmarks += [ group.lms for group in mio.import_landmark_files(path, verbose=True) if group.lms.n_points == 68 ] return compute_reference_shape(landmarks, diagonal=diagonal).points.astype(np.float32)
def _train(self, images, increment=False, group=None, batch_size=None, verbose=False): # If batch_size is not None, then we may have a generator, else we # assume we have a list. if batch_size is not None: # Create a generator of fixed sized batches. Will still work even # on an infinite list. image_batches = batch(images, batch_size) else: image_batches = [list(images)] for k, image_batch in enumerate(image_batches): if k == 0: if self.reference_shape is None: # If no reference shape was given, use the mean of the first # batch if batch_size is not None: warnings.warn('No reference shape was provided. The ' 'mean of the first batch will be the ' 'reference shape. If the batch mean is ' 'not representative of the true mean, ' 'this may cause issues.', MenpoFitBuilderWarning) self.reference_shape = compute_reference_shape( [i.landmarks[group] for i in image_batch], self.diagonal, verbose=verbose) # After the first batch, we are incrementing the model if k > 0: increment = True if verbose: print('Computing batch {}'.format(k)) # Train each batch self._train_batch( image_batch, increment=increment, group=group, verbose=verbose)
def _train(self, original_images, group=None, bounding_box_group_glob=None, verbose=False): # Dlib does not support incremental builds, so we must be passed a list if not isinstance(original_images, list): original_images = list(original_images) # We use temporary landmark groups - so we need the group key to not be # None if group is None: group = original_images[0].landmarks.group_labels[0] # Temporarily store all the bounding boxes for rescaling for i in original_images: i.landmarks['__gt_bb'] = i.landmarks[group].lms.bounding_box() if self.reference_shape is None: # If no reference shape was given, use the mean of the first batch self._reference_shape = compute_reference_shape( [i.landmarks['__gt_bb'].lms for i in original_images], self.diagonal, verbose=verbose) # Rescale images wrt the scale factor between the existing # reference_shape and their ground truth (group) bboxes images = rescale_images_to_reference_shape(original_images, '__gt_bb', self.reference_shape, verbose=verbose) # Scaling is done - remove temporary gt bounding boxes for i, i2 in zip(original_images, images): del i.landmarks['__gt_bb'] del i2.landmarks['__gt_bb'] # Create a callable that generates perturbations of the bounding boxes # of the provided images. generated_bb_func = generate_perturbations_from_gt( images, self.n_perturbations, self._perturb_from_gt_bounding_box, gt_group=group, bb_group_glob=bounding_box_group_glob, verbose=verbose) # For each scale (low --> high) for j in range(self.n_scales): # Print progress if asked if verbose: if len(self.scales) > 1: scale_prefix = ' - Scale {}: '.format(j) else: scale_prefix = ' - ' else: scale_prefix = None # Rescale images according to scales. Note that scale_images is smart # enough in order not to rescale the images if the current scale # factor equals to 1. scaled_images, scale_transforms = scale_images( images, self.scales[j], prefix=scale_prefix, return_transforms=True, verbose=verbose) # Get bbox estimations of current scale. If we are at the first # scale, this is done by using generated_bb_func. If we are at the # rest of the scales, then the current bboxes are attached on the # scaled_images with key '__ert_current_bbox_{}'. current_bounding_boxes = [] if j == 0: # At the first scale, the current bboxes are created by calling # generated_bb_func. current_bounding_boxes = [ generated_bb_func(im) for im in scaled_images ] else: # At the rest of the scales, extract the current bboxes that # were attached to the images msg = '{}Extracting bbox estimations from previous ' \ 'scale.'.format(scale_prefix) wrap = partial(print_progress, prefix=msg, end_with_newline=False, verbose=verbose) for ii in wrap(scaled_images): c_bboxes = [] for k in list(range(self.n_perturbations)): c_key = '__ert_current_bbox_{}'.format(k) c_bboxes.append(ii.landmarks[c_key].lms) current_bounding_boxes.append(c_bboxes) # Extract scaled ground truth shapes for current scale scaled_gt_shapes = [i.landmarks[group].lms for i in scaled_images] # Train the Dlib model. This returns the bbox estimations for the # next scale. current_bounding_boxes = self.algorithms[j].train( scaled_images, scaled_gt_shapes, current_bounding_boxes, prefix=scale_prefix, verbose=verbose) # Scale the current bbox estimations for the next level. This # doesn't have to be done for the last scale. The only thing we need # to do at the last scale is to remove any attached landmarks from # the training images. if j < (self.n_scales - 1): for jj, image_bboxes in enumerate(current_bounding_boxes): for k, bbox in enumerate(image_bboxes): c_key = '__ert_current_bbox_{}'.format(k) images[jj].landmarks[c_key] = \ scale_transforms[jj].apply(bbox)
def _train(self, original_images, group=None, bounding_box_group_glob=None, verbose=False): r""" """ # Dlib does not support incremental builds, so we must be passed a list if not isinstance(original_images, list): original_images = list(original_images) # We use temporary landmark groups - so we need the group key to not be # None if group is None: group = original_images[0].landmarks.group_labels[0] # Temporarily store all the bounding boxes for rescaling for i in original_images: i.landmarks['__gt_bb'] = i.landmarks[group].lms.bounding_box() if self.reference_shape is None: # If no reference shape was given, use the mean of the first batch self.reference_shape = compute_reference_shape( [i.landmarks['__gt_bb'].lms for i in original_images], self.diagonal, verbose=verbose) # Rescale to existing reference shape images = rescale_images_to_reference_shape( original_images, '__gt_bb', self.reference_shape, verbose=verbose) # Scaling is done - remove temporary gt bounding boxes for i, i2 in zip(original_images, images): del i.landmarks['__gt_bb'] del i2.landmarks['__gt_bb'] generated_bb_func = generate_perturbations_from_gt( images, self.n_perturbations, self._perturb_from_gt_bounding_box, gt_group=group, bb_group_glob=bounding_box_group_glob, verbose=verbose) # for each scale (low --> high) current_bounding_boxes = [] for j in range(self.n_scales): if verbose: if len(self.scales) > 1: scale_prefix = ' - Scale {}: '.format(j) else: scale_prefix = ' - ' else: scale_prefix = None # handle scales if self.scales[j] != 1: # Scale feature images only if scale is different than 1 scaled_images = scale_images(images, self.scales[j], prefix=scale_prefix, verbose=verbose) else: scaled_images = images if j == 0: current_bounding_boxes = [generated_bb_func(im) for im in scaled_images] # Extract scaled ground truth shapes for current scale scaled_gt_shapes = [i.landmarks[group].lms for i in scaled_images] # Train the Dlib model current_bounding_boxes = self.algorithms[j].train( scaled_images, scaled_gt_shapes, current_bounding_boxes, prefix=scale_prefix, verbose=verbose) # Scale current shapes to next resolution, don't bother # scaling final level if j != (self.n_scales - 1): transform = Scale(self.scales[j + 1] / self.scales[j], n_dims=2) for bboxes in current_bounding_boxes: for bb in bboxes: transform.apply_inplace(bb)
def _train(self, images, group=None, verbose=False): checks.check_landmark_trilist(images[0], self.transform, group=group) self.reference_shape = compute_reference_shape( [i.landmarks[group] for i in images], self.diagonal, verbose=verbose) # normalize images images = rescale_images_to_reference_shape(images, group, self.reference_shape, verbose=verbose) if self.sigma: images = [fsmooth(i, self.sigma) for i in images] # Build models at each scale if verbose: print_dynamic('- Building models\n') feature_images = [] # for each scale (low --> high) for j in range(self.n_scales): if verbose: if len(self.scales) > 1: scale_prefix = ' - Scale {}: '.format(j) else: scale_prefix = ' - ' else: scale_prefix = None # Handle holistic features if j == 0 and self.holistic_features[j] == no_op: # Saves a lot of memory feature_images = images elif j == 0 or self.holistic_features[ j] is not self.holistic_features[j - 1]: # Compute features only if this is the first pass through # the loop or the features at this scale are different from # the features at the previous scale feature_images = compute_features(images, self.holistic_features[j], prefix=scale_prefix, verbose=verbose) # handle scales if self.scales[j] != 1: # Scale feature images only if scale is different than 1 scaled_images = scale_images(feature_images, self.scales[j], prefix=scale_prefix, verbose=verbose) else: scaled_images = feature_images # Extract potentially rescaled shapes scale_shapes = [i.landmarks[group] for i in scaled_images] # Build the shape model if verbose: print_dynamic('{}Building shape model'.format(scale_prefix)) shape_model = self._build_shape_model(scale_shapes, j) self.shape_models.append(shape_model) # Obtain warped images - we use a scaled version of the # reference shape, computed here. This is because the mean # moves when we are incrementing, and we need a consistent # reference frame. scaled_reference_shape = Scale(self.scales[j], n_dims=2).apply( self.reference_shape) warped_images = self._warp_images(scaled_images, scale_shapes, scaled_reference_shape, j, scale_prefix, verbose) # obtain appearance model if verbose: print_dynamic( '{}Building appearance model'.format(scale_prefix)) appearance_model = PCAModel(warped_images) # trim appearance model if required if self.max_appearance_components[j] is not None: appearance_model.trim_components( self.max_appearance_components[j]) # add appearance model to the list self.appearance_models.append(appearance_model) expert_ensemble = self.expert_ensemble_cls[j]( images=scaled_images, shapes=scale_shapes, patch_shape=self.patch_shape[j], patch_normalisation=self.patch_normalisation, cosine_mask=self.cosine_mask, context_shape=self.context_shape[j], sample_offsets=self.sample_offsets, prefix=scale_prefix, verbose=verbose) self.expert_ensembles.append(expert_ensemble) if verbose: print_dynamic('{}Done\n'.format(scale_prefix))
def build(self, shapes, template, group=None, label=None, verbose=False): r""" Builds a Multilevel Active Template Model given a list of shapes and a template image. Parameters ---------- shapes : list of :map:`PointCloud` The set of shapes from which to build the shape model of the ATM. template : :map:`Image` or subclass The image to be used as template. group : `string`, optional The key of the landmark set of the template that should be used. If ``None``, and if there is only one set of landmarks, this set will be used. label : `string`, optional The label of the landmark manager of the template that you wish to use. If ``None`` is passed, the convex hull of all landmarks is used. verbose : `boolean`, optional Flag that controls information and progress printing. Returns ------- atm : :map:`ATM` The ATM object. Shape and appearance models are stored from lowest to highest level. """ # compute reference_shape self.reference_shape = compute_reference_shape( shapes, self.normalization_diagonal, verbose=verbose) # normalize the template size using the reference_shape scaling if verbose: print_dynamic('- Normalizing template size') normalized_template = template.rescale_to_reference_shape( self.reference_shape, group=group, label=label) # create pyramid for template image if verbose: print_dynamic('- Creating template pyramid') generator = create_pyramid([normalized_template], self.n_levels, self.downscale, self.features) # build the model at each pyramid level if verbose: if self.n_levels > 1: print_dynamic('- Building model for each of the {} pyramid ' 'levels\n'.format(self.n_levels)) else: print_dynamic('- Building model\n') shape_models = [] warped_templates = [] # for each pyramid level (high --> low) for j in range(self.n_levels): # since models are built from highest to lowest level, the # parameters in form of list need to use a reversed index rj = self.n_levels - j - 1 if verbose: level_str = ' - ' if self.n_levels > 1: level_str = ' - Level {}: '.format(j + 1) # rescale shapes if required if j > 0 and self.scaled_shape_models: scale_transform = Scale(scale_factor=1.0 / self.downscale, n_dims=2) shapes = [scale_transform.apply(s) for s in shapes] # train shape model and find reference frame if verbose: print_dynamic('{}Building shape model'.format(level_str)) shape_model = build_shape_model(shapes, self.max_shape_components[rj]) reference_frame = self._build_reference_frame(shape_model.mean()) # add shape model to the list shape_models.append(shape_model) # get template's feature image of current level if verbose: print_dynamic('{}Warping template'.format(level_str)) feature_template = next(generator[0]) # compute transform transform = self.transform(reference_frame.landmarks['source'].lms, feature_template.landmarks[group][label]) # warp template to reference frame warped_templates.append( feature_template.warp_to_mask(reference_frame.mask, transform)) # attach reference_frame to template's source shape warped_templates[j].landmarks['source'] = \ reference_frame.landmarks['source'] if verbose: print_dynamic('{}Done\n'.format(level_str)) # reverse the list of shape and appearance models so that they are # ordered from lower to higher resolution shape_models.reverse() warped_templates.reverse() n_training_shapes = len(shapes) return self._build_atm(shape_models, warped_templates, n_training_shapes)
def _train(self, original_images, group=None, bounding_box_group_glob=None, verbose=False): r""" """ # Dlib does not support incremental builds, so we must be passed a list if not isinstance(original_images, list): original_images = list(original_images) # We use temporary landmark groups - so we need the group key to not be # None if group is None: group = original_images[0].landmarks.group_labels[0] # Temporarily store all the bounding boxes for rescaling for i in original_images: i.landmarks['__gt_bb'] = i.landmarks[group].lms.bounding_box() if self.reference_shape is None: # If no reference shape was given, use the mean of the first batch self.reference_shape = compute_reference_shape( [i.landmarks['__gt_bb'].lms for i in original_images], self.diagonal, verbose=verbose) # Rescale to existing reference shape images = rescale_images_to_reference_shape(original_images, '__gt_bb', self.reference_shape, verbose=verbose) # Scaling is done - remove temporary gt bounding boxes for i, i2 in zip(original_images, images): del i.landmarks['__gt_bb'] del i2.landmarks['__gt_bb'] generated_bb_func = generate_perturbations_from_gt( images, self.n_perturbations, self._perturb_from_gt_bounding_box, gt_group=group, bb_group_glob=bounding_box_group_glob, verbose=verbose) # for each scale (low --> high) current_bounding_boxes = [] for j in range(self.n_scales): if verbose: if len(self.scales) > 1: scale_prefix = ' - Scale {}: '.format(j) else: scale_prefix = ' - ' else: scale_prefix = None # handle scales if self.scales[j] != 1: # Scale feature images only if scale is different than 1 scaled_images = scale_images(images, self.scales[j], prefix=scale_prefix, verbose=verbose) else: scaled_images = images if j == 0: current_bounding_boxes = [ generated_bb_func(im) for im in scaled_images ] # Extract scaled ground truth shapes for current scale scaled_gt_shapes = [i.landmarks[group].lms for i in scaled_images] # Train the Dlib model current_bounding_boxes = self.algorithms[j].train( scaled_images, scaled_gt_shapes, current_bounding_boxes, prefix=scale_prefix, verbose=verbose) # Scale current shapes to next resolution, don't bother # scaling final level if j != (self.n_scales - 1): transform = Scale(self.scales[j + 1] / self.scales[j], n_dims=2) for bboxes in current_bounding_boxes: for bb in enumerate(bboxes): bboxes[k] = transform.apply(bb)
def _train(self, original_images, group=None, bounding_box_group_glob=None, verbose=False): # Dlib does not support incremental builds, so we must be passed a list if not isinstance(original_images, list): original_images = list(original_images) # We use temporary landmark groups - so we need the group key to not be # None if group is None: group = original_images[0].landmarks.group_labels[0] # Temporarily store all the bounding boxes for rescaling for i in original_images: i.landmarks['__gt_bb'] = i.landmarks[group].bounding_box() if self.reference_shape is None: # If no reference shape was given, use the mean of the first batch self._reference_shape = compute_reference_shape( [i.landmarks['__gt_bb'] for i in original_images], self.diagonal, verbose=verbose) # Rescale images wrt the scale factor between the existing # reference_shape and their ground truth (group) bboxes images = rescale_images_to_reference_shape( original_images, '__gt_bb', self.reference_shape, verbose=verbose) # Scaling is done - remove temporary gt bounding boxes for i, i2 in zip(original_images, images): del i.landmarks['__gt_bb'] del i2.landmarks['__gt_bb'] # Create a callable that generates perturbations of the bounding boxes # of the provided images. generated_bb_func = generate_perturbations_from_gt( images, self.n_perturbations, self._perturb_from_gt_bounding_box, gt_group=group, bb_group_glob=bounding_box_group_glob, verbose=verbose) # For each scale (low --> high) for j in range(self.n_scales): # Print progress if asked if verbose: if len(self.scales) > 1: scale_prefix = ' - Scale {}: '.format(j) else: scale_prefix = ' - ' else: scale_prefix = None # Rescale images according to scales. Note that scale_images is smart # enough in order not to rescale the images if the current scale # factor equals to 1. scaled_images, scale_transforms = scale_images( images, self.scales[j], prefix=scale_prefix, return_transforms=True, verbose=verbose) # Get bbox estimations of current scale. If we are at the first # scale, this is done by using generated_bb_func. If we are at the # rest of the scales, then the current bboxes are attached on the # scaled_images with key '__ert_current_bbox_{}'. current_bounding_boxes = [] if j == 0: # At the first scale, the current bboxes are created by calling # generated_bb_func. current_bounding_boxes = [generated_bb_func(im) for im in scaled_images] else: # At the rest of the scales, extract the current bboxes that # were attached to the images msg = '{}Extracting bbox estimations from previous ' \ 'scale.'.format(scale_prefix) wrap = partial(print_progress, prefix=msg, end_with_newline=False, verbose=verbose) for ii in wrap(scaled_images): c_bboxes = [] for k in list(range(self.n_perturbations)): c_key = '__ert_current_bbox_{}'.format(k) c_bboxes.append(ii.landmarks[c_key]) current_bounding_boxes.append(c_bboxes) # Extract scaled ground truth shapes for current scale scaled_gt_shapes = [i.landmarks[group] for i in scaled_images] # Train the Dlib model. This returns the bbox estimations for the # next scale. current_bounding_boxes = self.algorithms[j].train( scaled_images, scaled_gt_shapes, current_bounding_boxes, prefix=scale_prefix, verbose=verbose) # Scale the current bbox estimations for the next level. This # doesn't have to be done for the last scale. The only thing we need # to do at the last scale is to remove any attached landmarks from # the training images. if j < (self.n_scales - 1): for jj, image_bboxes in enumerate(current_bounding_boxes): for k, bbox in enumerate(image_bboxes): c_key = '__ert_current_bbox_{}'.format(k) images[jj].landmarks[c_key] = \ scale_transforms[jj].apply(bbox)
def _train(self, images, group=None, verbose=False): checks.check_landmark_trilist(images[0], self.transform, group=group) self.reference_shape = compute_reference_shape( [i.landmarks[group] for i in images], self.diagonal, verbose=verbose) # normalize images images = rescale_images_to_reference_shape( images, group, self.reference_shape, verbose=verbose) if self.sigma: images = [fsmooth(i, self.sigma) for i in images] # Build models at each scale if verbose: print_dynamic('- Building models\n') feature_images = [] # for each scale (low --> high) for j in range(self.n_scales): if verbose: if len(self.scales) > 1: scale_prefix = ' - Scale {}: '.format(j) else: scale_prefix = ' - ' else: scale_prefix = None # Handle holistic features if j == 0 and self.holistic_features[j] == no_op: # Saves a lot of memory feature_images = images elif j == 0 or self.holistic_features[j] is not self.holistic_features[j - 1]: # Compute features only if this is the first pass through # the loop or the features at this scale are different from # the features at the previous scale feature_images = compute_features(images, self.holistic_features[j], prefix=scale_prefix, verbose=verbose) # handle scales if self.scales[j] != 1: # Scale feature images only if scale is different than 1 scaled_images = scale_images(feature_images, self.scales[j], prefix=scale_prefix, verbose=verbose) else: scaled_images = feature_images # Extract potentially rescaled shapes scale_shapes = [i.landmarks[group] for i in scaled_images] # Build the shape model if verbose: print_dynamic('{}Building shape model'.format(scale_prefix)) shape_model = self._build_shape_model(scale_shapes, j) self.shape_models.append(shape_model) # Obtain warped images - we use a scaled version of the # reference shape, computed here. This is because the mean # moves when we are incrementing, and we need a consistent # reference frame. scaled_reference_shape = Scale(self.scales[j], n_dims=2).apply( self.reference_shape) warped_images = self._warp_images(scaled_images, scale_shapes, scaled_reference_shape, j, scale_prefix, verbose) # obtain appearance model if verbose: print_dynamic('{}Building appearance model'.format( scale_prefix)) appearance_model = PCAModel(warped_images) # trim appearance model if required if self.max_appearance_components[j] is not None: appearance_model.trim_components( self.max_appearance_components[j]) # add appearance model to the list self.appearance_models.append(appearance_model) expert_ensemble = self.expert_ensemble_cls[j]( images=scaled_images, shapes=scale_shapes, patch_shape=self.patch_shape[j], patch_normalisation=self.patch_normalisation, cosine_mask=self.cosine_mask, context_shape=self.context_shape[j], sample_offsets=self.sample_offsets, prefix=scale_prefix, verbose=verbose) self.expert_ensembles.append(expert_ensemble) if verbose: print_dynamic('{}Done\n'.format(scale_prefix))