In [34]:
import numpy as np
from sklearn.neighbors import NearestNeighbors

In [55]:
r = 1 # Initial radius for model
x = 1 # increment for expanding radius
d = .01 # distance threshold for determing if two points near equidistant

# existing points and corresponding classes
existingPoints = np.array([[2,3,4,5,6,7,8],[1,3,4,5,6,7,8],[6,2,3,4,5,6,7]]) # existing points in model
existingClasses = np.array([1,2,3])

# initialize classifier
neigh = NearestNeighbors(radius = r)

# read in new point
newPoints = np.array([2,3,4,5,6,7,8],ndmin=2)

# fit existing points to classifier
neigh.fit(existingPoints)

#distances,indices = neigh.radius_neighbors(newPoints)

newClass = classify(neigh,existingClasses,newPoints)

# Add the new point and new class to the model. Class = 0 if point is unclassified
existingPoints = np.append(existingPoints,newPoints)
existingClasses = np.append(existingClasses,newClass)

print(newClass)


2

In [54]:
def classify(neigh,existingClasses,newPoints):
    
    distances,indices = neigh.radius_neighbors(newPoints)

    # if there are no points in the radius, expand radius by x and check again before classifying point.
    if len(indices)==0 or len(indices)==1: 
        neigh.radius = r+x
        distances,indices = neigh.radius_neighbors(newPoints)         
        
    # else if there are two points from different classes that are close to the same distance
    # (within distance threshold), expand radius to see if there is another very close point
    elif len(indices) ==2 and existingClasses[indices[0]]!=existingClasses[indices[1]]:
        if abs(distances[0]-distances[1]) <= d:
            neigh.radius = r+x
    
    # predict class of new data 
    if len(indices)!=0:
        # calculate weights (arbitrary weights for now)
        weights = np.array([0,5,5])
        
        # sum weights for each class
        classes = existingClasses[indices[0]]
        classes = np.unique(classes[np.where(classes!=0)])  # ignore zero class (outliers)
                      
        classWeight = np.zeros(len(classes)) # initialize weight array
        
        for i,cl in enumerate(classes):
            classWeight[i] = sum(weights[np.where(classes==cl)])
            
        newClass = classes[np.argmax(classWeight)] 
                
    else: 
        newClass = 0
        
    return(newClass)

In [4]:
## Since we are including PAMguard into feature space, use an enum to make np.array work

In [10]:
from datetime import datetime

class DetectedTarget:
    
    features_label = ["","","","","","","",""]
        
    def __init__(self, features=[], source="", date=datetime.now(), classification=None):
        self.features = features
        self.source = source
        self.date = date
        self.classification = classification

In [88]:
def rescaleFrom0To1(features):
    for i in range(features.shape[1]):
        if features[:,i].min() == features[:,i].max():
            features[:,i] = 0.5
        else:
            features[:,i] = (features[:,i] - features[:,i].min()) / (features[:,i].max() - features[:,i].min())
    return features

In [70]:
features


Out[70]:
array([[0, 0, 3, 4, 5, 6, 7],
       [1, 1, 5, 4, 3, 2, 1]])

In [89]:
rescaleFrom0To1(features)


[ 0.  1.]
[ 0.  1.]
[ 0.  1.]
[ 0.5  0.5]
[ 1.  0.]
[ 1.  0.]
[ 1.  0.]
Out[89]:
dtype('float64')

In [111]:
from enum import Enum
import numpy as np
from sklearn.neighbors import NearestNeighbors
from datetime import datetime
import os.path
import csv

class Classes(Enum):
    Interesting = 1.1
    NotInteresting = 1.2
    
    FastLarge = 2.1
    FastSmall = 2.2
    SlowLarge = 2.3
    SlowSmall = 2.4
    
    # SchoolOfFish = 3.1
    # SingleFish = 3.2
    # Kelp = 3.3
    # DolphinOrPorpoise = 3.4

class DetectedTarget:
    
    features_label = ["","","","","","","",""]
        
    def __init__(self, features=[], source="Unknown", date=datetime.now(), classification=None):
        self.features = features
        self.source = source
        self.date = date
        self.classification = classification

class weightedNeighbors:
    
    def __init__(self):
        
        # Global variables
        self.INITIAL_RADIUS = 3.0
        self.SIZE_FEATURE_WEIGHT = 1.0
        self.SPEED_FEATURE_WEIGHT = 1.0
        self.SPEED_RELATIVE_TO_CURRENT_FEATURE_WEIGHT = 1.0
        self.TARGET_STRENGTH_FEATURE_WEIGHT = 1.0
        self.CURRENT_SPEED_FEATURE_WEIGHT = 1.0
        self.TIME_OF_DAY_FEATURE_WEIGHT = 1.0
        self.PASSIVE_ACOUSTICS_FEATURE_WEIGHT = 1.0
        self.RADIUS_INCREMENT = 0.1 # increment for expanding radius
        self.DISTANCE_THRESHOLD = 0.01 # distance threshold for determing if two points near equidistant
        
        # load current model and initialize NearestNeighbors model
        self.current_model_targets = self.load_detectedTargets()
        self.model =  NearestNeighbors(radius=self.INITIAL_RADIUS)
        
    def load_detectedTargets(self):
        '''
        Load existing targets from current_model_targets.csv
        '''
        
        current_model_targets = []
        
        if os.path.isfile('current_model_targets.csv'):
            current_model_targets = []
            with open('current_model_targets.csv', 'r') as f:
                reader = csv.reader(f,delimiter = ";")
                next(reader,None)
                for target in reader:
                    current_model_targets.append( DetectedTarget(
                            features=list(target[1:8]), source=target[8], date=target[10],
                            classification=Classes(float(target[-1])))) 

        else:
            print('No existing targets for model')
                                                      
        return current_model_targets
    
    def fitModel(self):
        '''
        Fit current model targets to model
        '''
        self.model.fit(np.array(list(map(lambda x: x.features, self.current_model_targets))))
    
    def determine_weights(self,indices):
        '''
        This function will return the weights of points with the desired indices
        '''
        pass
        return np.array([0,5,5])
        
        
    def classify(self,newPoint):
        '''
        Predict class of new target detection(s)
        '''
        x = self.RADIUS_INCREMENT 
        r = self.DISTANCE_THRESHOLD 
        
        
        existingClasses = np.array(list(map(lambda x: x.classification, self.current_model_targets)))

        distances,indices = self.model.radius_neighbors(newPoint)

        # if there are no points in the radius, expand radius by x and check again before classifying point.
        if len(indices)==0 or len(indices)==1: 
            neigh.radius = r+x
            distances,indices = self.model.radius_neighbors(newPoint)         

        # else if there are two points from different classes that are close to the same distance
        # (within distance threshold), expand radius to see if there is another very close point
        elif len(indices) ==2 and existingClasses[indices[0]]!=existingClasses[indices[1]]:
            if abs(distances[0]-distances[1]) <= d:
                neigh.radius = r+x
                distances,indices = self.model.radius_neighbors(newPoint) 

        # predict class of new data 
        if len(indices)!=0:
            # calculate weights (arbitrary weights for now)
            weights = self.determine_weights(indices)

            # sum weights for each class
            classes = existingClasses[indices[0]]
            classes = np.unique(classes[np.where(classes!=0)])  # ignore zero class (outliers)

            classWeight = np.zeros(len(classes)) # initialize weight array

            for i,cl in enumerate(classes):
                classWeight[i] = sum(weights[np.where(classes==cl)])

            newClass = classes[np.argmax(classWeight)] 

        else: 
            newClass = 0
                          
        return(newClass)

In [112]:
neigh = weightedNeighbors()
neigh.fitModel()
neigh.classify(np.array([2,3,4,5,6,7,8],ndmin=2))


['7', '6', '5', '4', '3', '2', '1']
[['1' '2' '3' '4' '5' '6' '7']
 ['7' '6' '5' '4' '3' '2' '1']]
Out[112]:
<Classes.NotInteresting: 1.2>

In [ ]: