content: __author__ = 'IVMIT KFU: Gataullin Ravil & Veselovkiy Sergei'
  
  from copy import copy
  import numpy as np
  from sklearn.ensemble import RandomForestClassifier
  from  time import time
  import warnings
  warnings.filterwarnings("ignore")
  from sklearn.cross_validation import train_test_split
  from structure import Position
  
  class PatchVarianceClassifier:
      def __init__(self, init_patch):
          self.init_patch_variance = np.var(init_patch.content)
  
      def classify(self, patch):
          # return 1 if object is positive detected
          # return 0 if object is negative detected
          if np.var(patch.content) > 0.5 * self.init_patch_variance:
              return 1
          else:
              return 0
  
      def predict_patch(self, patch):
          return np.var(patch.content) / self.init_patch_variance
  
      def predict_position(self, position):
          return np.var(position.calculate_patch().content) / self.init_patch_variance
  
  class EnsembleClassifier:
      def __init__(self, learning_component):
          self.learning_component = learning_component
          self.classifier = RandomForestClassifier(max_depth=3)
  
      def classify(self, patch):
          # return 1 if object is positive detected
          # return 0 if object is negative detected
          feature = patch.calculate_feature(self.learning_component.descriptor)
          if self.classifier.predict_proba(feature)[0][self.positive_class_index] > 0.5:
              return 1
          else:
              return 0
  
      def predict_patch(self, patch):
          feature = patch.calculate_feature(self.learning_component.descriptor)
          return self.classifier.predict_proba(feature)[0][self.positive_class_index]
  
      def predict_position(self, position):
          feature = position.calculate_patch().calculate_feature(self.learning_component.descriptor)
          return self.classifier.predict_proba(feature)[0][self.positive_class_index]
  
      def relearn(self, test_size=0):
          samples, weights, targets = self.learning_component.get_training_set(const_weight=True)
          train_samples, test_samples, train_targets, test_targets = train_test_split(samples, targets, test_size=test_size, random_state=np.random.RandomState(0))
          count_positives = 1.0*np.count_nonzero(train_targets)
          count_negatives = 1.0*(len(train_targets) - count_positives)
          positive_weight = count_negatives/len(train_targets)
          negative_weight = count_positives/len(train_targets)
          weights = np.array([positive_weight if target == 1 else negative_weight for target in train_targets])
          self.classifier.fit(train_samples, train_targets, sample_weight=weights)
          self.learning_component.new_samples_count = 0
          if len(test_samples) > 0:
              test_result = [self.classifier.predict(sample) for sample in test_samples]
              true_positives = 0.0
              count_test_positives = 1.0*np.count_nonzero(test_targets)
              count_result_positives = 1.0*np.count_nonzero(test_result)
              for i in xrange(len(test_targets)):
                  if test_targets[i] == test_result[i] and test_result[i] == 1:
                      true_positives += 1
              precision = true_positives / count_test_positives
              recall = true_positives / count_result_positives
              print "Precision:", precision
              print "Recall", recall
              if precision + recall != 0:
                  print "F-score:", 2 * precision * recall / (precision + recall)
              else:
                  print "F-score:", 0
          self.positive_class_index = 0
          for elem in self.classifier.classes_:
              if elem != 1.0:
                  self.positive_class_index += 1
              else:
                  break
  
  class NearestNeighborClassifier:
  
      def __init__(self, learning_component, lmbd = 0.1, tetta = 0.6):
          self.learning_component = learning_component
          self.lmbd = lmbd
          self.tetta = tetta
  
      def classify(self, patch):
          # return 1 if object is positive detected
          # return 0 if object is negative detected
          if self.learning_component.relative_similarity(patch) > self.tetta:
              return 1
          else:
              return 0
  
      def predict_patch(self, patch):
          return self.learning_component.relative_similarity(patch)
  
      def predict_position(self, position):
          return self.learning_component.relative_similarity(position.calculate_patch())
  
  def scanning_window(init_position, scales_step = 1.2, slip_step = 0.1, minimal_bounding_box_size = 20, min_step=1, max_step=20):
      flag_inc = True
      flag_dec = False
      position = copy(init_position)
      while min(position.width, position.height) >= minimal_bounding_box_size:
          position.update(x=0,y=0)
          step_width = min(max(min_step,int(slip_step * position.width)),max_step)
          step_height = min(max(min_step,int(slip_step * position.height)),max_step)
          while position.is_correct():
              while position.is_correct():
                  yield position
                  position.update(x=position.x+step_width)
              position.update(x=0, y=position.y+step_height)
          # if position.is_correct():
          #     yield position
          # is_end = False
          # step_width = int(slip_step * position.width)
          # step_height = int(slip_step * position.height)
          # layer = 1
          # xx = position.x
          # yy = position.y
          # while not is_end:
          #     is_end = True
          #     for start_point, vector in (([-1,-1],[1,0]),([1,-1],[0,1]),([1,1],[-1,0]),([-1,1],[0,-1])):
          #         position.update(x=xx + (start_point[0]*layer + vector[0])*step_width, y=yy+(start_point[1]*layer + vector[1])*step_height)
          #         while position.is_correct() and xx - layer*step_width <= position.x <= xx + layer*step_width and yy - layer*step_height <= position.y <= yy + layer*step_height:
          #             is_end = False
          #             yield position
          #             position.update(x=position.x+vector[0]*step_width, y=position.y+vector[1]*step_height)
          #     layer += 1
          if flag_inc:
              position.update(height=int(position.height * scales_step), width = int(position.width * scales_step))
          if position.height > position.buffer[0].shape[0] or position.width > position.buffer[0].shape[0]:
              flag_inc = False
              flag_dec = True
              position = copy(init_position)
          if flag_dec:
              position.update(height=int(position.height / scales_step), width = int(position.width / scales_step))
  
  def get_sliding_positions(init_position, scales_step = 1.2, slip_step = 0.1, minimal_bounding_box_size = 20, min_step=2, max_step=2):
      sliding_positions = []
      flag_inc = True
      flag_dec = False
      position = copy(init_position)
      while min(position.width, position.height) >= minimal_bounding_box_size:
          position.update(x=0,y=0)
          step_width = min(max(min_step,int(slip_step * position.width)),max_step)
          step_height = min(max(min_step,int(slip_step * position.height)),max_step)
          while position.is_correct():
              while position.is_correct():
                  sliding_positions.append(copy(position))
                  position.update(x=position.x+step_width)
              position.update(x=0, y=position.y+step_height)
          if flag_inc:
              position.update(height=int(position.height * scales_step), width = int(position.width * scales_step))
          if position.height > position.buffer[0].shape[0] or position.width > position.buffer[0].shape[0]:
              flag_inc = False
              flag_dec = True
              position = copy(init_position)
          if flag_dec:
              position.update(height=int(position.height / scales_step), width = int(position.width / scales_step))
      return sliding_positions
  
  class Detector:
      def __init__(self, init_position, learning_component, threshold_patch_variance=0.5, threshold_ensemble=0.5, threshold_nearest_neighbor=0.6):
          self.learning_component = learning_component
          self.patch_variance_classifier = PatchVarianceClassifier(learning_component.init_patch)
          self.ensemble_classifier = EnsembleClassifier(learning_component)
          self.nearest_neighbor_classifier = NearestNeighborClassifier(learning_component)
          self.threshold_patch_variance = threshold_patch_variance
          self.threshold_ensemble = threshold_ensemble
          self.threshold_nearest_neighbor = threshold_nearest_neighbor
          self.sliding_positions = get_sliding_positions(init_position, scales_step = 1.2, slip_step = 0.1, minimal_bounding_box_size = 50, min_step=2, max_step=10)
  
      def cascaded_classifier(self, patch):
          # 3 stages of classify
          # return 1 if object is positive detected
          # return 0 if object is negative detected
          if self.patch_variance_classifier.predict_patch(patch) < self.threshold_patch_variance:
              return 0
          if self.ensemble_classifier.predict_patch(patch) < self.threshold_patch_variance:
              return 0
          # elif self.nearest_neighbor_classifier.predict_patch(patch) < self.threshold_nearest_neighbor:
          #     return 0
          return 1
  
      def detect(self, position, is_tracked):
          if self.learning_component.new_samples_count > 10:
              start = time()
              self.ensemble_classifier.relearn()
              print "Relearn:", time() - start
          detected_windows = []
          predict_times = []
          for current_position in self.sliding_positions:
              start = time()
              proba = self.predict_position(current_position)
              predict_times.append(time() - start)
              if proba == 1:
                  detected_windows.append((current_position.get_window(), current_position.calculate_patch(), proba))
                  self.learning_component.add_new_positive(current_position.calculate_patch())
                  if is_tracked:
                      return detected_windows
              else:
                  self.learning_component.add_new_negative(current_position.calculate_patch())
          print "Analysed window count:", len(predict_times)
          print "Max detection time:", np.max(predict_times)
          print "Min detection time:", np.min(predict_times)
          print "Mean detection time:", np.mean(predict_times)
          return detected_windows
  
      def predict_patch(self, patch):
          return self.cascaded_classifier(patch)
  
      def predict_position(self, position):
          return self.cascaded_classifier(position.calculate_patch())