text:
#!/usr/bin/python3.7
# -*- coding: utf-8 -*-
import numpy as np
import cv2
import time
from datetime import datetime
import LeptonCCI as l
import sys
from subprocess import call
from threading import Thread
import sysv_ipc as ipc
from picamera.array import PiRGBArray
from picamera import PiCamera
from picamera.color import Color
from SACFaceFinder import FaceFinder

######### Gobals ###########
state = "IDLE"
values = (0,0,0,0)
# Settings. We will need to get these from a JSON file.
thSensorWidth = 80
thSensorHeight = 60
faceSizeUpperLimit = 280
faceSizeLowerLimit = 220
transformMatrix = np.array([[1.5689e-1, 8.6462e-3, -1.1660e+1],[1.0613e-4, 1.6609e-1, -1.4066e+1]])

# Globals for logging
currentTime = int(round(time.time()))
lastLogTime = 0
logInterval = 2 # seconds

# Globals for FFC timing
lastFFCTime = 0
needsFFC = False
maxFFCInterval = 20 # seconds.
############################

######## Functions #########
def addText(image, sMessage):
    font = cv2.FONT_HERSHEY_SIMPLEX
    org = (50, 50)
    fontScale = 1
    color = (255, 0, 0)
    thickness = 2
    image = cv2.putText(image, sMessage, org, font, fontScale, color, thickness, cv2.LINE_AA)

def checkFaceSize(image, currWidth, minWidth, maxWidth):
    if currWidth > maxWidth:
        addText(image, 'Ga wat verder staan.')
        return False
    elif currWidth < minWidth:
        addText(image, 'Ga wat dichter staan.')
        return False
    else:
        addText(image, 'Afstand OK.')
        return True

def writeLog(updateRoIValues):
    if updateRoIValues:
        line = str(int(round(time.time()))) + "," + str(l.GetAuxTemp()) + "," + str(l.GetFpaTemp()) + "," + str(l.GetROIValues()) + "," + str(l.GetROI()) + ",\n"
    else:
        line = str(int(round(time.time()))) + "," + str(l.GetAuxTemp()) + "," + str(l.GetFpaTemp()) + "," + str((0,0,0,0)) + "," + str(l.GetROI()) + ",\n"
    line = line.replace('(','')
    line =line.replace(')','')
    line = line.replace(',',';')
    line = line.replace('.',',')
    f.write(line)

# Start Displaying thread and init shared memory connection
def startDisplay():
    print("[INFO]: Starting display thread...")
    call(["./SACDisplayMixer/OGLESSimpleImageWithIPC"])
############################

######## main init #########
th1 = Thread(target=startDisplay)
th1.start()
time.sleep(1)

key = ipc.ftok("/home/pi/SACLeptonRPi", ord('i'))
shm = ipc.SharedMemory(key, 0, 0)
shm.attach()
print("[INFO]: Shared memory key " + str(key) + " with pointer " +  str(shm))

# Initialize PiCamera
cameraWidth = 640
cameraHeight = 480
camera = PiCamera()
camera.resolution = (cameraWidth,cameraHeight)
camera.framerate = 10

#camera.annotate_foreground = Color(y=0.4, u=-0.05, v=0.615)
#camera.annotate_text = 'Hallo!'
camera.meter_mode = 'spot'
camera.hflip = True

rawCapture = PiRGBArray(camera, size=(640,480))
# allow warmup
time.sleep(0.1)

# initialize the face finder
ff = FaceFinder()
ff.setTransformMatrix(transformMatrix)

print("[INFO]: Starting state machine...")
try:
    f = open("SACTemplog.csv", "a")
    for frame in camera.capture_continuous(rawCapture, format="bgr", use_video_port=True):
        # get an image from the camera
        image = frame.array

        currentTime = int(round(time.time()))
        alreadyLogged = False

        # run state machine
        if state == "IDLE":
            if ff.getTcFaceContours(image) == True:
                state = "WAIT_FOR_SIZE_OK"
            else:
                state = "IDLE"
                #print("face not present")
                addText(image, 'Geen gezicht gevonden.')

        elif state == "WAIT_FOR_SIZE_OK":
            if ff.getTcFaceContours(image) == True:
                if checkFaceSize(image, ff.getTcFaceROIWidth(), faceSizeLowerLimit, faceSizeUpperLimit) == False:
                    state = "WAIT_FOR_SIZE_OK"
                else:
                    state = "RUN_FFC"
            else:
                state = "IDLE"

        elif state == "RUN_FFC":
            if currentTime > (lastFFCTime + maxFFCInterval):
                l.RunRadFfc()
                lastFFCTime = currentTime
            state = "SET_FLUX_LINEAR_PARAMS"

        elif state == "SET_FLUX_LINEAR_PARAMS":
            sensorTemp = l.GetAuxTemp()
            sceneEmissivity = 0.98
            TBkg = sensorTemp
            tauWindow = 1.0
            TWindow = sensorTemp
            tauAtm = 1.0
            TAtm = sensorTemp
            reflWindow = 0.0
            TRefl = sensorTemp
            FLParams = (sceneEmissivity,TBkg,tauWindow,TWindow,tauAtm,TAtm,reflWindow,TRefl)
            print(str(FLParams))
            l.SetFluxLinearParams(FLParams)
            state = "GET_TEMPERATURE"

        elif state == "GET_TEMPERATURE":
            thRect_x, thRect_y, thRect_w, thRect_h = cv2.boundingRect(ff.getThFaceContours())
            # x and y should not be negativeor lager then the FPA. Clip the values.
            thRect_x = max(0, min(thRect_x, thSensorWidth-2))
            thRect_y = max(0, min(thRect_y, thSensorHeight-2))
            thRect_xe = max(0, min(thRect_x + thRect_w, thSensorWidth-1))
            thRect_ye = max(0, min(thRect_y + thRect_w, thSensorHeight-1))
            thRoi = (thRect_x, thRect_y, thRect_xe, thRect_ye)
            print(str(thRoi))
            l.SetROI(thRoi)
            values = l.GetROIValues()
            print(str(values))
            writeLog(True)
            alreadyLogged = True
            state = "WAIT_FOR_NO_FACE"

        elif state == "WAIT_FOR_NO_FACE":
            if ff.getTcFaceContours(image) == True:
                state = "WAIT_FOR_NO_FACE"
                addText(image, str(values[1]) + " degC")
            else:
                state = "IDLE"

        elif state == "TEMP_OK":
            if ff.getTcFaceContours(image) == True:
                state = "TEMP_OK"
            else:
                state = "IDLE"

        else:
            break

        # display image
        try:
            im_rgb = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)
            im_rgb = cv2.flip(im_rgb, 0)
            shm.write(im_rgb)
        except Exception as e:
            print("[ERROR]: Can not write to shared memory: " + str(e))

        # periodically write to log
        if currentTime > lastLogTime + logInterval:
            if not alreadyLogged:
                writeLog(False)
                lastLogTime = currentTime

        # clear the stream in preparation for the next frame
        rawCapture.truncate(0)

except KeyboardInterrupt:
    print("[INFO]: Interuptted by user.")
    f.close()
    shm.detach()
    sys.exit(-1)

except Exception as e:
    print("[ERROR]: Error while running state machine: " + str(e))
    f.close()
    shm.detach()
    sys.exit(-1)

max_stars_repo_name: smeerpate/SACLeptonRPi

max_issues_repo_name: smeerpate/SACLeptonRPi

max_forks_repo_name: smeerpate/SACLeptonRPi


text:
# --------------------------------------------------------
# Multitask Network Cascade
# Written by Haozhi Qi
# Copyright (c) 2016, Haozhi Qi
# Licensed under The MIT License [see LICENSE for details]
# --------------------------------------------------------

import cPickle
import os
import scipy.io as sio
import numpy as np
from datasets.pascal_voc_det import PascalVOCDet
from mnc_config import cfg
from utils.vis_seg import vis_seg
from utils.voc_eval import voc_eval_sds
import scipy

class PascalVOCSeg(PascalVOCDet):
    """
    A subclass for datasets.imdb.imdb
    This class contains information of ROIDB and MaskDB
    This class implements roidb and maskdb related functions
    """
    def __init__(self, image_set, year, devkit_path=None):
        PascalVOCDet.__init__(self, image_set, year, devkit_path)
        self._ori_image_num = len(self._image_index)
        self._comp_id = 'comp6'
        # PASCAL specific config options
        self.config = {'cleanup': True,
                       'use_salt': True,
                       'top_k': 2000,
                       'use_diff': False,
                       'matlab_eval': False,
                       'rpn_file': None}
        self._data_path = os.path.join(self._devkit_path)
        self._roidb_path = os.path.join(self.cache_path, 'voc_2012_' + image_set + '_mcg_maskdb')

    def image_path_at(self, i):
        # image_path = os.path.join(self._data_path, 'VOC2012', 'JPEGImages',  self._image_index[i] + self._image_ext)
        image_path = os.path.join(self._data_path, 'img', self._image_index[i] + self._image_ext)
        assert os.path.exists(image_path), 'Path does not exist: {}'.format(image_path)
        return image_path

    def roidb_path_at(self, i):
        if i >= self._ori_image_num:
            return os.path.join(self._roidb_path,
                                self.image_index[i % self._ori_image_num] + '_flip.mat')
        else:
            return os.path.join(self._roidb_path,
                                self.image_index[i] + '.mat')

    def gt_maskdb(self):
        cache_file = os.path.join(self.cache_path, self.name + '_gt_maskdb.pkl')
        if os.path.exists(cache_file):
            with open(cache_file, 'rb') as fid:
                gt_maskdb = cPickle.load(fid)
            print '{} gt maskdb loaded from {}'.format(self.name, cache_file)
        else:
            num_image = len(self.image_index)
            gt_roidbs = self.gt_roidb()
            gt_maskdb = [self._load_sbd_mask_annotations(index, gt_roidbs)
                         for index in xrange(num_image)]
            with open(cache_file, 'wb') as fid:
                cPickle.dump(gt_maskdb, fid, cPickle.HIGHEST_PROTOCOL)
            print 'wrote gt roidb to {}'.format(cache_file)
        return gt_maskdb

    def _load_image_set_index(self):
        # image_set_file = os.path.join(self._data_path, 'ImageSets' , 'Segmentation', self._image_set + '.txt')
        image_set_file = os.path.join(self._data_path, self._image_set + '.txt')
        assert os.path.exists(image_set_file), 'Path does not exist: {}'.format(image_set_file)
        with open(image_set_file) as f:
            image_index = [x.strip() for x in f.readlines()]
        return image_index

    def _load_sbd_mask_annotations(self, index, gt_roidbs):
        """
        Load gt_masks information from SBD's additional data
        """
        if index % 1000 == 0:
            print '%d / %d' % (index, len(self._image_index))
        image_name = self._image_index[index]
        inst_file_name = os.path.join(self._data_path, 'inst', image_name + '.mat')
        gt_inst_mat = scipy.io.loadmat(inst_file_name)
        gt_inst_data = gt_inst_mat['GTinst']['Segmentation'][0][0]
        unique_inst = np.unique(gt_inst_data)
        background_ind = np.where(unique_inst == 0)[0]
        unique_inst = np.delete(unique_inst, background_ind)
        gt_roidb = gt_roidbs[index]
        cls_file_name = os.path.join(self._data_path, 'cls', image_name + '.mat')
        gt_cls_mat = scipy.io.loadmat(cls_file_name)
        gt_cls_data = gt_cls_mat['GTcls']['Segmentation'][0][0]
        gt_masks = []
        for ind, inst_mask in enumerate(unique_inst):
            box = gt_roidb['boxes'][ind]
            im_mask = (gt_inst_data == inst_mask)
            im_cls_mask = np.multiply(gt_cls_data, im_mask)
            unique_cls_inst = np.unique(im_cls_mask)
            background_ind = np.where(unique_cls_inst == 0)[0]
            unique_cls_inst = np.delete(unique_cls_inst, background_ind)
            assert len(unique_cls_inst) == 1
            assert unique_cls_inst[0] == gt_roidb['gt_classes'][ind]
            mask = im_mask[box[1]: box[3]+1, box[0]:box[2]+1]
            gt_masks.append(mask)

        # Also record the maximum dimension to create fixed dimension array when do forwarding
        mask_max_x = max(gt_masks[i].shape[1] for i in xrange(len(gt_masks)))
        mask_max_y = max(gt_masks[i].shape[0] for i in xrange(len(gt_masks)))
        return {
            'gt_masks': gt_masks,
            'mask_max': [mask_max_x, mask_max_y],
            'flipped': False
        }

    def append_flipped_masks(self):
        """
        This method is only accessed when we use maskdb, so implement here
        Append flipped images to mask database
        Note this method doesn't actually flip the 'image', it flip masks instead
        """
        cache_file = os.path.join(self.cache_path, self.name + '_' + cfg.TRAIN.PROPOSAL_METHOD + '_maskdb_flip.pkl')
        if os.path.exists(cache_file):
            with open(cache_file, 'rb') as fid:
                flip_maskdb = cPickle.load(fid)
            print '{} gt flipped roidb loaded from {}'.format(self.name, cache_file)
            self.maskdb.extend(flip_maskdb)
            # Need to check this condition since otherwise we may occasionally *4
            if self._image_index == self.num_images:
                self._image_index *= 2
        else:
            # pure image number hold for future development
            # this is useless since append flip mask will only be called once
            num_images = self._ori_image_num
            flip_maskdb = []
            for i in xrange(num_images):
                masks = self.maskdb[i]['gt_masks']
                masks_flip = []
                for mask_ind in xrange(len(masks)):
                    mask_flip = np.fliplr(masks[mask_ind])
                    masks_flip.append(mask_flip)
                entry = {'gt_masks': masks_flip,
                         'mask_max': self.maskdb[i]['mask_max'],
                         'flipped': True}
                flip_maskdb.append(entry)
            with open(cache_file, 'wb') as fid:
                cPickle.dump(flip_maskdb, fid, cPickle.HIGHEST_PROTOCOL)
            print 'wrote gt flipped maskdb to {}'.format(cache_file)
            self.maskdb.extend(flip_maskdb)
            # Need to check this condition since otherwise we may occasionally *4
            if self._image_index == self.num_images:
                self._image_index *= 2

    def visualization_segmentation(self, output_dir):
        vis_seg(self.image_index, self.classes, output_dir, self._data_path)

    # --------------------------- Evaluation ---------------------------
    def evaluate_segmentation(self, all_boxes, all_masks, output_dir):
        self._write_voc_seg_results_file(all_boxes, all_masks, output_dir)
        self._py_evaluate_segmentation(output_dir)

    def _write_voc_seg_results_file(self, all_boxes, all_masks, output_dir):
        """
        Write results as a pkl file, note this is different from
        detection task since it's difficult to write masks to txt
        """
        # Always reformat result in case of sometimes masks are not
        # binary or is in shape (n, sz*sz) instead of (n, sz, sz)
        all_boxes, all_masks = self._reformat_result(all_boxes, all_masks)
        for cls_inds, cls in enumerate(self.classes):
            if cls == '__background__':
                continue
            print 'Writing {} VOC results file'.format(cls)
            filename = os.path.join(output_dir, cls + '_det.pkl')
            with open(filename, 'wr') as f:
                cPickle.dump(all_boxes[cls_inds], f, cPickle.HIGHEST_PROTOCOL)
            filename = os.path.join(output_dir, cls + '_seg.pkl')
            with open(filename, 'wr') as f:
                cPickle.dump(all_masks[cls_inds], f, cPickle.HIGHEST_PROTOCOL)

    def _reformat_result(self, boxes, masks):
        num_images = len(self.image_index)
        num_class = len(self.classes)
        reformat_masks = [[[] for _ in xrange(num_images)]
                          for _ in xrange(num_class)]
        for cls_inds in xrange(1, num_class):
            for img_inds in xrange(num_images):
                if len(masks[cls_inds][img_inds]) == 0:
                    continue
                num_inst = masks[cls_inds][img_inds].shape[0]
                reformat_masks[cls_inds][img_inds] = masks[cls_inds][img_inds]\
                    .reshape(num_inst, cfg.MASK_SIZE, cfg.MASK_SIZE)
                reformat_masks[cls_inds][img_inds] = reformat_masks[cls_inds][img_inds] >= cfg.BINARIZE_THRESH
        all_masks = reformat_masks
        return boxes, all_masks

    def _py_evaluate_segmentation(self, output_dir):
        gt_dir = self._data_path
        imageset_file = os.path.join(gt_dir, self._image_set + '.txt')
        cache_dir = os.path.join(self._devkit_path, 'annotations_cache')
        aps = []
        # define this as true according to SDS's evaluation protocol
        use_07_metric = True
        print 'VOC07 metric? ' + ('Yes' if use_07_metric else 'No')
        if not os.path.isdir(output_dir):
            os.mkdir(output_dir)
        print '~~~~~~ Evaluation use min overlap = 0.5 ~~~~~~'
        for i, cls in enumerate(self._classes):
            if cls == '__background__':
                continue
            det_filename = os.path.join(output_dir, cls + '_det.pkl')
            seg_filename = os.path.join(output_dir, cls + '_seg.pkl')
            ap = voc_eval_sds(det_filename, seg_filename, gt_dir,
                              imageset_file, cls, cache_dir, self._classes, ov_thresh=0.5)
            aps += [ap]
            print('AP for {} = {:.2f}'.format(cls, ap*100))
        print('Mean AP@0.5 = {:.2f}'.format(np.mean(aps)*100))
        print '~~~~~~ Evaluation use min overlap = 0.7 ~~~~~~'
        aps = []
        for i, cls in enumerate(self._classes):
            if cls == '__background__':
                continue
            det_filename = os.path.join(output_dir, cls + '_det.pkl')
            seg_filename = os.path.join(output_dir, cls + '_seg.pkl')
            ap = voc_eval_sds(det_filename, seg_filename, gt_dir,
                              imageset_file, cls, cache_dir, self._classes, ov_thresh=0.7)
            aps += [ap]
            print('AP for {} = {:.2f}'.format(cls, ap*100))
        print('Mean AP@0.7 = {:.2f}'.format(np.mean(aps)*100))

ext: py

reason: import numpy,import scipy


text:
import numpy as np
import os
import random
from copy import copy
from typing import List

import pickle

from agents.plastic_dqn_v1 import config

REPLAY_MEMORY_SIZE = 50_000  # How many last steps to keep for model training
MIN_REPLAY_MEMORY_SIZE = 1_000  # Minimum number of steps in a memory to

class Transition:
    def __init__(self, obs: np.ndarray, act: int, reward: int,
                 new_obs: np.ndarray, done: bool, correct_action: bool):
        self.obs = obs
        self.act = act
        self.reward = reward
        self.new_obs = new_obs
        self.done = done
        # Auxiliar var:
        self.correct_action = correct_action

    def to_tuple(self) -> tuple:
        return tuple(
            [self.obs, self.act, self.reward, self.new_obs, self.done])

class LearnBuffer:
    """ Buffer used during game. Saves and decides which steps to save """
    def __init__(self, buffer: list = None):
        if buffer is None:
            buffer = list()
        self.buffer = buffer

    @classmethod
    def load_from_file(cls, file_name: str):
        with open(file_name, 'rb') as f:
            data = pickle.load(f)
        if isinstance(data, LearnBuffer):
            return data
        elif isinstance(data, list):
            return cls(data)
        else:
            raise ValueError(f"Unexpected type of {file_name}")

    def to_list(self):
        return self.buffer

    def parse_episode(self, episodes_transitions: List[Transition],
                      verbose: bool = False) -> list:
        if len(episodes_transitions) == 0:
            return []

        # Remove last actions without ball:
        last_reward = copy(episodes_transitions[-1].reward)
        for idx in range(len(episodes_transitions) - 1, -1, -1):
            # Has ball:
            if episodes_transitions[idx].obs[5] > 0:
                episodes_transitions = episodes_transitions[:idx + 1]
                break
            # No ball:
            elif episodes_transitions[idx].obs[5] < 0:
                pass
            else:
                raise ValueError("Features has ball, wrong value!!")
        else:
            return []

        # selected wrong action?:
        if episodes_transitions[-1].correct_action is False and last_reward > 0:
            episodes_transitions[-1].reward = -1
        else:
            episodes_transitions[-1].reward = last_reward
        episodes_transitions[-1].done = True

        if verbose and random.random() > 0.99:
            print("\n ** Transictions:")
            #for el in episodes_transitions:
                #print_transiction(el.to_tuple(), self.actions, simplex=True)
            print('**')

        return episodes_transitions

    def save_episode(self, episode: List[Transition], verbose: bool = True):
        parsed_episode = self.parse_episode(episode, verbose)
        if parsed_episode:
            self.buffer += parsed_episode

    def export_to_pickle(self, file_name: str):
        with open(file_name, 'wb') as f:
            pickle.dump(self.buffer, f)

class ExperienceBuffer:
    """ Contains all the experience the agent retain during training """
    def __init__(self, data: list):
        self._data = data

    @classmethod
    def create_by_merge_files(cls, directory: str, team_name: str):
        print("[Experience Buffer] Merging smaller files")
        step = 0
        exp_episodes = list()
        while True:
            # Check if file exists:
            experience_file = config.EXPERIENCE_BUFFER_FORMAT.format(step=step)
            data_file = os.path.join(directory, team_name, experience_file)
            if not os.path.isfile(data_file):
                break
            # Load Learn Buffer:
            learn_buffer = LearnBuffer.load_from_file(data_file)
            exp_episodes += learn_buffer.to_list()
            # Inc step:
            print(f"Add stage {step} data. SIZE={len(learn_buffer.to_list())}")
            step += 1

        experience_buffer = cls(exp_episodes)
        experience_buffer.save_to_pickle(dir=directory, team_name=team_name)
        return experience_buffer

    @classmethod
    def load(cls, file_path: str):
        print("[Experience Buffer] Loading")
        with open(file_path, "rb") as fp:
            data = pickle.load(fp)
        if isinstance(data, np.ndarray):
            data = data.tolist()
        return cls(data)

    def to_list(self) -> list:
        return self._data

    def to_array(self) -> np.ndarray:
        return np.array(self._data)

    def save_to_pickle(self, dir: str, team_name: str):
        experience_file = config.REPLAY_BUFFER_FORMAT.format(
            base_dir=dir,
            team_name=team_name)
        with open(experience_file, 'wb') as f:
            pickle.dump(self.to_array(), f)

    def store_transition(self, curr_st: np.ndarray, action_idx: int,
                         reward: int, new_st: np.ndarray, done: int):
        """ Adds step's data to a memory replay array
        (observation space, action, reward, new observation space, done) """
        transition = np.array([curr_st, action_idx, reward, new_st, done])
        self._data.append(transition)

    def store_episode(self, transitions: List[Transition]):
        """ Adds step's data to a memory replay array
        (observation space, action, reward, new observation space, done) """
        if len(transitions) > 0:
            for transition in transitions:
                self._data.append(transition.to_tuple())

reason: import numpy


text:
###############################################################################################################
#                                   Numpy Array                                                               #
# A numpy array is a grid of values, all of the same type, and is indexed by a tuple of nonnegative integers. #
# The number of dimensions is the rank of the array; the shape of an array is a tuple of integers giving the  #
# size of the array along each dimension.                                                                     #
###############################################################################################################

import numpy as np

### Create a 3x1 numpy array
a = np.array([1,2,3])
print('\nArray 3*1:\t',a)

### Print object type
print('\nClass of the Array:\t',type(a))

### Print shape
print('\nShape of the Array:\t',a.shape)

### Print some values in a
print('\nValues of [0],[1],[2]:\t',a[0], a[1], a[2])

### Create a 2x2 numpy array
b = np.array([[1,2],[3,4]])
print('\nArray of 2*2:\n',b)

### Print shape
print('\nShape of the Array:\t',b.shape)

## Print some values in b
print('\nValues of b:\t',b[0,0], b[0,1], b[1,1])

### Create a 3x2 numpy array
c = np.array([[1,2],[3,4],[5,6]])
print('\nArray of 3*2:\n',c)

### Print shape
print('\nShape of the Array:\t',c.shape)

### Print some values in c
print('\nValues of c:\t',c[0,1], c[1,0], c[2,0], c[2,1])

### 2x3 zero array
d = np.zeros((2,3))
print('\nZero Array 2*3:\n',d)

### 4x2 array of ones
e = np.ones((4,2))
print('\nArray of Ones 4*2:\n',e)

### 2x2 constant array
f = np.full((2,2), 9)
print('\nArray of Constant 9:\n',f)

### 3x3 random array
g = np.random.random((3,3))
print('\nRandom Array 3*3:\n',g)

################################################
#              Array Indexing                  #
################################################\

### Create 3x4 array
h = np.array([[1,2,3,4,], [5,6,7,8], [9,10,11,12]])
print('\nArray 3*4:\n',h)

### Slice array to make a 2x2 sub-array
i = h[:2, 1:3]
print('\nSlice Array to make sub Array:\n',i)

################################################
#              Datatypes in Arrays             #
################################################

### Integer
j = np.array([1, 2])
print('\nFuction type:\t',j.dtype)

### Float
k = np.array([1.0, 2.0])
print('\nType:\t',k.dtype)

### Force Data Type
l = np.array([1.0, 2.0], dtype=np.int64)
print('\nForce Change in DataType:\t',l.dtype)

#########################################################################
#                           Array Math                                  #
#   Basic mathematical functions operate elementwise on arrays,         #
#   and are available both as operator overloads and as functions       #
#   in the numpy module:                                                #
#########################################################################

x = np.array([[1,2],[3,4]], dtype=np.float64)
y = np.array([[5,6],[7,8]], dtype=np.float64)

# Elementwise sum; both produce the array
# [[ 6.0  8.0]
#  [10.0 12.0]]
print('\nSum Array classical `+`:\n',x + y)
print('\nSum Array `np.add(x,y)` :\n',np.add(x, y))

# Elementwise difference; both produce the array
# [[-4.0 -4.0]
#  [-4.0 -4.0]]
print('\nRest Array classical `-`:\n',x - y)
print('\nRest Array `np.subtract(x,y)` :\n',np.subtract(x, y))

# Elementwise product; both produce the array
# [[ 5.0 12.0]
#  [21.0 32.0]]
print('\nProduct Array classical `*`:\n',x * y)
print('\nProduct Array `np.multiply(x,y)` :\n',np.multiply(x, y))

# Elementwise division; both produce the array
# [[ 0.2         0.33333333]
#  [ 0.42857143  0.5       ]]
print('\nDivision Array classical `/`:\n',x / y)
print('\nDivision Array `np.divide(x,y)` :\n',np.divide(x, y))

# Elementwise square root; produces the array
# [[ 1.          1.41421356]
#  [ 1.73205081  2.        ]]
print('\nsquare Root Array `np.sqrt(x)` :\n',np.sqrt(x))

x = np.array([[1,2],[3,4]])

### Compute sum of all elements; prints "10"
print('\nSum of all elements Array `np.sum(x)`\n',np.sum(x))

### Compute sum of each column; prints "[4 6]"
print('\nSum of ech column Array `np.sum(x, axis=0)`\n',np.sum(x, axis=0))

### Compute sum of each row; prints "[3 7]"
print('\nSum of ech row Array `np.sum(x, axis=1)`\n',np.sum(x, axis=1))

### Compute mean of all elements; prints "2.5"
print('\nMean of all elements in Array `np.mean(x)`\n',np.mean(x))

### Compute mean of each column; prints "[2 3]"
print('\nMen  of ech Column in Array `np.mean(x, axis=1)`\n',np.mean(x, axis=0))

### Compute mean of each row; prints "[1.5 3.5]"
print('\nMean of each Row elements in Array `np.mean(x, axis=1)`\n',np.mean(x, axis=1))

max_stars_repo_path: Understanding_and_Visualizing_Data_With_Python/Univariate Data/Python for Analysis of Univariate Data/Numpy.py


text:
import time
import sys
import random
import networks
import math
import numpy
import copy
import pymunk

from PyQt5 import QtCore, QtGui
from PyQt5 import QtWidgets
from userInterface import Ui_MainWindow
from threading import Thread
from pymunk import Vec2d
from torch import tensor
#import neural
import neural
import physics

cell_types = ["barrier", "carniv", "eye", "olfactory", "co2C", "push", "body", "rotate"]
dead_image = "dead.png"
dirType = "/"

if sys.platform.startswith("win"):
    dirType = "\\"

#co2_dispersion_ratio = 20
# The dispersion ratio for dead cells.
# This is used to calculate how much CO2 is added back into the environment when a dead cell is dispersed
# Dispersion equation: <dispersion_ratio> * (<cell_size> / 2) * (<cell_mass> / 2)

last_bubble_creation = time.time()
bubble_interval = 1.

co2_conversion_ratio = 3 # The amount of energy generated from 1 CO2 particle

cell_dispersion_rate = 6 # The percentage of a dead cell's mass to disperse per second

reproduction_limit = 6 # The amount of time (in seconds) an organism has to wait before reproducing again

offspring_amount = 1 # How many offspring to create when an organism reproduces

heal_rate = 4 # The percentage of health to heal each second if the cell has enough energy

mass_cutoff = 1 # If a dead cell's percentage of mass becomes less than this, it will be removed (dispersed completely)

break_damage = 5 # The amount of damage a cell does to its parent when it dies (break off from body) - multiplied by its size

starvation_rate = 6 # The percentage of damage to do to a cell each second if its energy equals 0

neural_update_delay = .5 # How long to wait before activating an organism's brain - helps reduce lag

learning_update_delay = .5 # How long to wait before training an organism

training_epochs = 1 # How many epochs to train a network for per training interval

max_push_speed = 250
max_rotation_speed = 6 # Radians per second

age_limit = 200

eyesight_multiplier = 10 # This multiplied by an eye cell's size is how far away that eye can see
olfactory_multiplier = 15 # This multiplied by an olfactory cell's size is how far away that organism can smell dead cells

base_cell_info = {
    "health": {
        # This is multiplied by the size and mass of each given cell
        # <max_health> = <base_health> * <cell_size> * <cell_mass>
        "barrier": 30,
        "carniv": 30,
        "co2C": 15,
        "eye": 8,
        "olfactory": 24,
        "push": 24,
        "body": 24,
        "heart": 15,
        "rotate": 24
    },
    "energy_usage": {
        # The first variable in the lists below represent an idle cell
        # The second variable reprents a cell's energy intake while in use
        # This is multiplied by the size and mass of each given cell
        # <max_energy_usage> = <base_energy_usage> * (<cell_size> / 2) * (<cell_mass> / 2)
        "barrier": [1.2, 1.2],
        "carniv": [2.5, 4],
        "co2C": [3.6, 3.6],
        "eye": [2, 2],
        "olfactory": [3.5, 5],
        "push": [3, 6],
        "body": [1.2, 1.2],
        "heart": [2.5, 2.5],
        "rotate": [2.5, 5]
    },
    "energy_storage": {
        # This is multiplied by the size and mass of each given cell
        # <energy_storage> = <base_energy_storage> * <cell_size> * <cell_mass>
        "barrier": 15,
        "carniv": 30,
        "co2C": 33,
        "eye": 20,
        "olfactory": 30,
        "push": 30,
        "body": 30,
        "heart": 33,
        "rotate": 30
    },
    "damage": {
        # The damage each cell does when in contact with a cell from another organism
        "barrier": 0,
        "carniv": 2,
        "co2C": 0,
        "eye": 0,
        "olfactory": 0,
        "push": 0,
        "body": 0,
        "heart": 0,
        "rotate": 0
    }
}

def random_direction():
    deg = random.randrange(0, 360)
    rad = math.radians(deg)
    x_direction = math.cos(rad)
    y_direction = math.sin(rad)
    return [x_direction, y_direction]

def calculateDistance(x1,y1,x2,y2):
    return math.sqrt( (x2 - x1)**2 + (y2 - y1)**2 )

def positive(x):
    if x > 0:
        return x
    return -x

def num2perc(num, maxNum):
    return (float(num) / float(maxNum)) * 100.0

def perc2num(perc, maxNum):
    return (float(perc) / 100.0) * float(maxNum)

def getAngle(x1, y1, x2, y2):
    myradians = math.atan2(y2-y1, x2-x1)
    return myradians

def getDirection(angle):
    direction = [math.cos(angle), math.sin(angle)]
    return direction

def randomizeList(l):
    return sorted(l, key=lambda x: random.random())

def viable_organism_position(pos, dna, environment):
    orgList = environment.info["organism_list"]
    dead_cells = [sprite for sprite in environment.sprites if not sprite.alive]
    overlappingSprites = []

    dna_rect = dna.get_rect()

    dna_rect = [
        dna_rect[0] + pos[0],
        dna_rect[1] + pos[1],
        dna_rect[2] + pos[0],
        dna_rect[3] + pos[1]
    ]

    if dna_rect[0] < 0 or dna_rect[2] > environment.width or dna_rect[1] < 0 or dna_rect[3] > environment.height:
        return False, []

    for org in orgList:
        if not org.alive():
            continue
        rect = org.get_rect()

        # Rect = [min_x, min_y, max_x, max_y]

        if (dna_rect[0] > rect[0] and dna_rect[0] < rect[2]) or (dna_rect[2] > rect[0] and dna_rect[2] < rect[2]) or (rect[0] > dna_rect[0] and rect[2] < dna_rect[2]): # Within X boundaries
            if (dna_rect[1] > rect[1] and dna_rect[1] < rect[3]) or (dna_rect[3] > rect[1] and dna_rect[3] < rect[3]) or (rect[1] > dna_rect[1] and rect[3] < dna_rect[3]): # Within Y boundaries
                return False, []

        for sprite in dead_cells:
            spritePos = sprite.getPos()
            spriteRadius = sprite.radius

            if spritePos[0] > dna_rect[0] and spritePos[0] < dna_rect[2]: # Within X boundaries
                if spritePos[1] > dna_rect[1] and spritePos[1] < dna_rect[3]:
                    overlappingSprites.append(sprite)

    return True, overlappingSprites

def get_new_organism_position(old_organism, dna, environment):
    orgSize = old_organism.dna.get_size()
    newSize = dna.get_size()
    dist = [ (orgSize[0]/1.3) + (newSize[0]/1.3), (orgSize[1]/1.3) + (newSize[1]/1.3) ]

    degList = list(range(360))
    #rDegList = sorted(degList, key=lambda x: random.random())
    rDegList = randomizeList(degList)

    for i in rDegList:
        rad = math.radians(i)
        direction = [math.cos(rad), math.sin(rad)]

        addX = direction[0] * dist[0]
        addY = direction[1] * dist[1]

        xPos = old_organism.pos[0] + addX
        yPos = old_organism.pos[1] + addY

        viable, overlappingSprites = viable_organism_position([xPos, yPos], dna, environment)
        if viable:
            for sprite in overlappingSprites:
                environment.removeSprite(sprite)
                sprite.info["removed"] = True
            return [xPos, yPos]
    return False

def eye_segment_handler(arbiter, space, data):
    if isinstance(arbiter.shapes[0], pymunk.Segment) or isinstance(arbiter.shapes[1], pymunk.Segment):
        return False
    if arbiter.shapes[1].sensor:
        return False

    sensor = arbiter.shapes[0].sprite
    sprite = arbiter.shapes[1].sprite

    if not sensor.alive:
        return False

    if sensor.organism != sprite.organism:
        if not sprite in sensor.info["view"]:
            sensor.info["view"].append(sprite)
    return False

def finish_cell_info(cell_info):
    cell_type = cell_info["type"]
    cell_mass = cell_info["mass"]
    cell_size = cell_info["size"]

    cell_health = (base_cell_info["health"][cell_type] * cell_size) + (base_cell_info["health"][cell_type] * cell_mass)
    cell_info["max_health"] = cell_health

    cell_energy_usage = [ (i * cell_size) + (i * cell_mass) for i in base_cell_info["energy_usage"][cell_type] ]
    cell_info["energy_usage"] = cell_energy_usage

    cell_damage = (base_cell_info["damage"][cell_type] * cell_size) + (base_cell_info["damage"][cell_type] * cell_mass)
    cell_info["damage"] = cell_damage

    cell_energy_storage = (base_cell_info["energy_storage"][cell_type] * cell_size) + (base_cell_info["energy_storage"][cell_type] * cell_mass)
    cell_info["energy_storage"] = cell_energy_storage

def build_sprite(environment, organism, pos, cell_id, cell_info, alive=True):
    if alive:
        image = "Images{}{}.png".format(dirType, cell_info["type"])
    else:
        image = "Images{}{}".format(dirType, dead_image)
    body, shape = physics.makeCircle(
        cell_info["size"]/2,
        friction = cell_info["friction"],
        elasticity = cell_info["elasticity"],
        mass = cell_info["mass"]
    )
    newCell = physics.Sprite(
        pos,
        cell_info["size"],
        cell_info["size"],
        environment,
        body,
        shape,
        image=image
    )

    newCell.cell_id = cell_id
    if organism is not None:
        newCell.collision = lambda sprite: organism._cell_collision(newCell, sprite)
    newCell.organism = organism
    newCell.cell_info = cell_info
    newCell.info = {
        "health": cell_info["max_health"],
        "energy": cell_info["energy_storage"]/2,
        "in_use": False,
        "detection_items": [],
        "view": [],
        "removed": False,
        "colliding": [],
        "sight_range": 0.0,
        "smell_range": 0.0
    }
    newCell.alive = alive
    newCell.isBubble = False
    newCell.creationTime = time.time()
    newCell.environment = environment

    if organism is not None:
        newCell.mouseDoubleClickEvent = lambda event: organism.cell_double_clicked(newCell)

    if cell_info["type"] == "eye" and newCell.alive:
        newCell.info["sight_range"] = cell_info["size"] * eyesight_multiplier

        newBody, newShape = physics.makeCircle( newCell.info["sight_range"], friction=0.0, elasticity=0.0, mass=1 )
        newShape.sensor = True
        newShape.collision_type = 1
        newShape.sprite = newCell
        newBody.position = pos

        joint = pymunk.PinJoint(newBody, newCell.body, [0,0], [0,0])

        newCell.info["detection_items"] = [newBody, newShape, joint]
    elif cell_info["type"] == "olfactory" and newCell.alive:
        newCell.info["smell_range"] = cell_info["size"] * olfactory_multiplier

        newBody, newShape = physics.makeCircle( newCell.info["smell_range"], friction=0.0, elasticity=0.0, mass=1 )
        newShape.sensor = True
        newShape.collision_type = 1
        newShape.sprite = newCell
        newBody.position = pos

        joint = pymunk.PinJoint(newBody, newCell.body, [0,0], [0,0])

        newCell.info["detection_items"] = [newBody, newShape, joint]

    return newCell

class DNA():
    def __init__(self):
        self.cells = {}
        # Structure: {
        #    <Cell_ID>: {
        #       "size": <integer>,
        #       "type": <string>,
        #       "elasticity": <float>,
        #       "friction": <float>
        #       "mass": <integer>,
        #       "first": <boolean>,
        #       "relative_pos": [<integer>, <integer>],
        #       "mirror_self": <cell_id>,
        #       "max_health": <integer>,
        #       "energy_usage": [<idle>, <in use>],
        #       "energy_storage": <integer>,
        #       "damage": <integer>
        #   }
        #}
        # `relative_pos` helps keep cells from forming to close to one another (starts at [0,0] for the first cell)

        self.growth_pattern = {}
        # Structure: {
        #   <Cell_ID>: {
        #        <Cell_ID_1>: [<X_Direction>, <Y_Direction>],
        #        <Cell_ID_2>: [<X_Direction>, <Y_Direction>]
        #   },
        #   <Cell_ID>: {
        #        <Cell_ID_1>: [<X_Direction>, <Y_Direction>],
        #        <Cell_ID_2>: [<X_Direction>, <Y_Direction>]
        #   }
        #}

        self._base_brain_structure = {
            "input_size": 6,
            "inputs": ["energy", "health", "rotation", "speed", "x_direction", "y_direction"],
            "hidden_layers": [], # [<integer>, <integer>, <integer>]
            "hidden_weights": [], # [ [<float>, <float>], [<float>, <float>, <float>], [<float>, <float>] ]
            "output_size": 4,
            "outputs": ["rotation", "speed", "x_direction", "y_direction"],
            "optimizer": "adam"
        }

        self.brain_structure = copy.deepcopy(self._base_brain_structure)

        self.trainingInput = []
        self.trainingOutput = []
        self.trainingReward = []
        self.trainingHidden = []

        self.previousInputs = []
        self.previousOutputs = []
        self.previousDopamine = []

        self.stim_history = [0]*10

        self.short_term_memory = []
        self.hidden_memory = []

        self.base_info = {
            "distanceThreshold": .8,
            "mirror_x": False,
            "mirror_y": False,
            "maximum_creation_tries": 30,
            "curiosity": 0.5 # Ranges from 0.0 to 1.0
        }
        # Structure: {
        #    "distanceThreshold": <float>,
        #    "mirror_x": <boolean>,
        #    "mirror_y": <boolean>,
        #    "maximum_creation_tries": <integer>,
        #    "curiosity": <float>
        #}

        self.cellRange = [3,30]
        self.sizeRange = [6,42]
        self.massRange = [5,20]
        self.mirror_x = [0.6, 0.4]
        self.mirror_y = [0.6, 0.4]
        self.generation = 0

    def copy(self):
        return copy.deepcopy(self)

    def erase_memory(self):
        self.trainingInput = []
        self.trainingOutput = []
        self.trainingReward = []

        self.previousInputs = []
        self.previousOutputs = []

        self.short_term_memory = []

    def first_cell(self):
        # Returns the first cell that's created while building this creature's body
        for cid in self.cells:
            if self.cells[cid]["first"]:
                return cid

    def sub_cells(self, cell_id):
        if cell_id in self.growth_pattern:
            return [ cid for cid in self.growth_pattern[cell_id] ]
        return []

    def all_sub_cells(self, cell_id):
        all_cells = []
        checkList = []
        if cell_id in self.growth_pattern:
            checkList.extend( self.sub_cells(cell_id) )

            while checkList:
                cid = checkList[0]
                all_cells.append(cid)
                checkList.extend( self.sub_cells(cid) )
                checkList.remove(cid)
        return all_cells

    def _lower_cells(self):
        # Returns all cells that do not contain children
        lower_cell_list = []

        for cell_id in self.growth_pattern:
            if not self.sub_cells(cell_id):
                lower_cell_list.append(cell_id)
        return lower_cell_list

    def grows_from(self, cell_id):
        # Returns the cell ID that this given cell grows from
        for cid in self.growth_pattern:
            if cell_id in self.growth_pattern[cid]:
                return cid

    def path_to_first(self, cell_id):
        pList = []
        while True:
            parentCell = self.grows_from(cell_id)
            if parentCell:
                cell_id = parentCell
                pList.append(cell_id)
            else:
                break
        return pList

    def get_rect(self):
        # The is a rect of the relative positions of the cells in this DNA class - not their in-world position
        fCell = self.first_cell()
        minX = self.cells[fCell]["relative_pos"][0]
        maxX = self.cells[fCell]["relative_pos"][0]
        minY = self.cells[fCell]["relative_pos"][1]
        maxY = self.cells[fCell]["relative_pos"][1]

        for cell_id in self.cells:
            pos = self.cells[cell_id]["relative_pos"]
            radius = self.cells[cell_id]["size"]/2

            if pos[0]-radius < minX:
                minX = pos[0]
            if pos[0]+radius > maxX:
                maxX = pos[0]

            if pos[1]-radius < minY:
                minY = pos[1]
            if pos[1]+radius > maxY:
                maxY = pos[1]

        return [minX, minY, maxX, maxY]

    def get_size(self):
        rect = self.get_rect()

        minX = rect[0]
        minY = rect[1]
        maxX = rect[2]
        maxY = rect[3]

        if minX < 0:
            minX = -minX
        if minY < 0:
            minY = -minY
        width = maxX + minX
        height = maxY + minY

        return [width, height]

    def cell_distance(self, id1, id2):
        if not id1 in self.cells or not id2 in self.cells:
            return
        rel_pos1 = self.cells[id1]["relative_pos"]
        rel_pos2 = self.cells[id2]["relative_pos"]

        x1 = rel_pos1[0]
        y1 = rel_pos1[1]
        x2 = rel_pos2[0]
        y2 = rel_pos2[1]

        distance = calculateDistance(x1, y1, x2, y2)
        return distance

    def _new_cell_id(self):
        cid = 1
        while cid in self.cells:
            cid += 1
        return cid

    def _finish_cell_info(self, cell_info):
        cell_type = cell_info["type"]
        cell_mass = cell_info["mass"]
        cell_size = cell_info["size"]

        cell_health = base_cell_info["health"][cell_type] * cell_size * cell_mass
        cell_info["max_health"] = cell_health

        cell_energy_usage = [i * cell_size * cell_mass for i in base_cell_info["energy_usage"][cell_type]]
        cell_info["energy_usage"] = cell_energy_usage

        cell_damage = base_cell_info["damage"][cell_type] * cell_size * cell_mass
        cell_info["damage"] = cell_damage

        cell_energy_storage = base_cell_info["energy_storage"][cell_type] * cell_size * cell_mass
        cell_info["energy_storage"] = cell_energy_storage

    def _make_random_cell(self, sizeRange, massRange, first_cell=False):
        cell_id = self._new_cell_id()
        cell_info = {}

        cell_size = random.randrange(sizeRange[0], sizeRange[1])
        cell_info["size"] = cell_size

        cell_mass = random.randrange(massRange[0], massRange[1])
        cell_info["mass"] = cell_mass

        cell_elasticity = random.random()
        cell_info["elasticity"] = cell_elasticity

        cell_friction = random.random()
        cell_info["friction"] = cell_friction

        if first_cell:
            cell_info["mirror_self"] = cell_id
        else:
            cell_info["mirror_self"] = None

        if first_cell:
            cell_type = "heart"
        else:
            cell_type = random.choice(cell_types)
        cell_info["type"] = cell_type

        finish_cell_info(cell_info)

        cell_info["first"] = first_cell

        return cell_id, cell_info

    def remove_cell(self, cell_id, removing_mirror=False, remove_sub_cells=True):
        # Returns a modified version of this DNA with the missing cells

        if not cell_id in self.cells:
            return

        new_dna = self.copy()

        if remove_sub_cells:
            for cID in new_dna.sub_cells(cell_id):
                new_dna = new_dna.remove_cell(cID, removing_mirror=removing_mirror)

        cell_info = new_dna.cells[cell_id]

        if cell_info["mirror_self"] and not removing_mirror:
            new_dna = new_dna.remove_cell(cell_info["mirror_self"], removing_mirror=True)

        new_dna.growth_pattern.pop(cell_id)
        new_dna.cells.pop(cell_id)

        for cID in new_dna.growth_pattern:
            if cell_id in new_dna.growth_pattern[cID]:
                new_dna.growth_pattern[cID].pop(cell_id)

        return new_dna

    def set_mass(self, cell_id, new_mass, changing_mirror=False):
        if not cell_id in self.cells:
            return

        self.cells[cell_id]["mass"] = new_mass

        if self.cells[cell_id]["mirror_self"] and not changing_mirror:
            self.set_mass(self.cells[cell_id]["mirror_self"], new_mass, changing_mirror=True)

        finish_cell_info(self.cells[cell_id])

    def set_size(self, cell_id, new_size, changing_mirror=False):
        if not cell_id in self.cells:
            return

        self.cells[cell_id]["size"] = new_size

        if self.cells[cell_id]["mirror_self"] and not changing_mirror:
            self.set_size(self.cells[cell_id]["mirror_self"], new_size, changing_mirror=True)

        finish_cell_info(self.cells[cell_id])

    def change_type(self, cell_id, new_type, changing_mirror=False):
        if not cell_id in self.cells:
            return

        self.cells[cell_id]["type"] = new_type

        if self.cells[cell_id]["mirror_self"] and not changing_mirror:
            self.change_type(self.cells[cell_id]["mirror_self"], new_type, changing_mirror=True)

        finish_cell_info(self.cells[cell_id])

    def add_cell(self, parentID, cell_type, size, mass, elasticity, friction, angle):
        if not parentID in self.cells:
            return

        grow_direction = [ math.cos(angle), math.sin(angle) ]

        cell_id = self._new_cell_id()
        cell_info = {}

        cell_info = {
            "size": size,
            "mass": mass,
            "elasticity": elasticity,
            "friction": friction,
            "type": cell_type,
            "mirror_self": None,
            "first": False
        }

        finish_cell_info(cell_info)

        relative_pos = self._new_relative_pos(parentID, grow_direction, cell_info["size"])
        cell_info["relative_pos"] = relative_pos

        self.growth_pattern[parentID][cell_id] = grow_direction

        self.cells[cell_id] = cell_info
        self.growth_pattern[cell_id] = {}

        if self.base_info["mirror_x"] or self.base_info["mirror_y"]:
            parentID = self.grows_from(cell_id)
            grow_direction = self.growth_pattern[parentID][cell_id]
            self._apply_mirror(cell_id, parentID, grow_direction)

        return cell_id

    def set_elasticity(self, cell_id, new_value):
        if not cell_id in self.cells:
            return

        self.cells[cell_id]["elasticity"] = new_value

        if cell_id == self.first_cell():
            return

        mirror_id = self.cells[cell_id]["mirror_self"]
        if mirror_id is not None and mirror_id in self.cells:
            self.cells[mirror_id]["elasticity"] = new_value

    def set_friction(self, cell_id, new_value):
        if not cell_id in self.cells:
            return

        self.cells[cell_id]["friction"] = new_value

        if cell_id == self.first_cell():
            return

        mirror_id = self.cells[cell_id]["mirror_self"]
        if mirror_id is not None and mirror_id in self.cells:
            self.cells[mirror_id]["friction"] = new_value

    def _closest_cell_to_point(self, x, y):
        # This function only compares the relative positions of cells
        distances = {}
        count = 0
        for cell_id in self.cells:
            rel_pos = self.cells[cell_id]["relative_pos"]
            radius = self.cells[cell_id]["size"] / 2

            dist = calculateDistance(x, y, rel_pos[0], rel_pos[1]) - radius
            distances[dist] = cell_id
        if distances:
            closest_cell = distances[min(distances)]
            dist = min(distances)
            return closest_cell, dist

    def _new_relative_pos(self, parentID, direction, childSize):
        relParentPos = self.cells[parentID]["relative_pos"]
        parentSize = self.cells[parentID]["size"]
        newRelPos = relParentPos[:]

        addX = direction[0] * ( (parentSize/2.1) + (childSize/2.1) )
        addY = direction[1] * ( (parentSize/2.1) + (childSize/2.1) )

        newRelPos = [newRelPos[0] + addX, newRelPos[1] + addY]

        return newRelPos

    def _cell_mirrorable(self, x, y, cell_info):
        cellSize = cell_info["size"]
        cellRadius = cellSize / 2

        newX = x
        newY = y

        if self.base_info["mirror_x"]:
            newX = -newX
        if self.base_info["mirror_y"]:
            newY = -newY

        distance = calculateDistance(x, y, newX, newY) - (cellSize/2)

        cid, distance2 = self._closest_cell_to_point(newX, newY)

        if distance > (cellRadius*self.base_info["distanceThreshold"]) and distance2 > ( cellRadius*self.base_info["distanceThreshold"] ):
            return True
        return False

    def _viable_cell_position(self, x, y, cell_info):
        cid, dist = self._closest_cell_to_point(x, y)

        size = cell_info["size"]
        radius = size / 2

        if self.base_info["mirror_x"] or self.base_info["mirror_y"]:
            if not self._cell_mirrorable(x, y, cell_info):
                return False

        if dist > (radius*self.base_info["distanceThreshold"]):
            return True
        return False

    def _apply_mirror(self, cell_id, grows_from, grow_direction, newID=None):
        if newID is None:
            newID = self._new_cell_id()

        cell_info = self.cells[cell_id].copy()

        self.cells[cell_id]["mirror_self"] = newID
        cell_info["mirror_self"] = cell_id

        relative_pos = self.cells[cell_id]["relative_pos"][:]
        new_grow_direction = grow_direction[:]

        if self.base_info["mirror_x"]:
            relative_pos[0] = -relative_pos[0]
            new_grow_direction[0] = -new_grow_direction[0]
        if self.base_info["mirror_y"]:
            relative_pos[1] = -relative_pos[1]
            new_grow_direction[1] = -new_grow_direction[1]

        new_grows_from = self.cells[grows_from]["mirror_self"]
        #new_grow_direction = [-i for i in grow_direction]

        self.growth_pattern[new_grows_from][newID] = new_grow_direction

        cell_info["relative_pos"] = relative_pos

        self.cells[newID] = cell_info

        if not newID in self.growth_pattern:
            self.growth_pattern[newID] = {}

    def add_randomized_cell(self, sizeRange, massRange, first_cell=False):
        cell_id, cell_info = self._make_random_cell(sizeRange, massRange, first_cell=first_cell)

        if first_cell:
            relative_pos = [0,0]

        if self.cells and not first_cell:
            pos_verified = False
            iterations = 0
            while not pos_verified:
                if iterations > self.base_info["maximum_creation_tries"]:
                    return False
                iterations += 1
                grow_from = random.choice( list(self.cells) )
                grow_direction = random_direction()

                relative_pos = self._new_relative_pos(grow_from, grow_direction, cell_info["size"])

                if self._viable_cell_position(relative_pos[0], relative_pos[1], cell_info):
                    pos_verified = True

            self.growth_pattern[grow_from][cell_id] = grow_direction

        cell_info["relative_pos"] = relative_pos

        self.cells[cell_id] = cell_info
        self.growth_pattern[cell_id] = {}

        return cell_id

    def _setup_brain_structure(self, hiddenRange):
        self.brain_structure = copy.deepcopy(self._base_brain_structure)

        for cell_id in self.cells:
            cell_info = self.cells[cell_id]

            if cell_info["type"] == "eye":
                self.brain_structure["input_size"] += neural.output_lengths["eye"]
            elif cell_info["type"] == "carniv":
                self.brain_structure["input_size"] += neural.output_lengths["carniv"]

        hiddenSize = random.randrange(hiddenRange[0], hiddenRange[1])
        hiddenLayers = []

        if self.brain_structure["input_size"] > self.brain_structure["output_size"]:
            decRange = numpy.linspace(
                self.brain_structure["input_size"],
                self.brain_structure["output_size"],
                hiddenSize
            )
            decRange = list(decRange)
        else:
            decRange = numpy.linspace(
                self.brain_structure["output_size"],
                self.brain_structure["input_size"],
                hiddenSize
            )
            decRange = list(decRange)
            decRange = decRange.reverse()

        minRange = min(decRange)-1
        maxRange = max(decRange)*2

        for i in decRange:
            hiddenLayerSize = int(random.triangular(minRange, i, maxRange))
            while hiddenLayerSize == 0:
                hiddenLayerSize = int(random.triangular(minRange, i, maxRange))
            hiddenLayers.append(hiddenLayerSize)

        self.brain_structure["hidden_layers"] = hiddenLayers

        tempNet = neural.setup_network(self)
        for layer in tempNet.hiddenLayers:
            self.brain_structure["hidden_weights"].append( layer.weight.tolist() )

    def randomize(self, cellRange=[3,30], sizeRange=[6,42], massRange=[5,20], mirror_x=[0.6, 0.4], mirror_y=[0.6, 0.4], hiddenRange=[2, 6]):
        self.cellRange = cellRange
        self.sizeRange = sizeRange
        self.massRange = massRange
        self.mirror_x = mirror_x
        self.mirror_y = mirror_y

        self.cells = {}
        self.growth_pattern = {}

        mirror_x = numpy.random.choice(numpy.arange(0, 2), p=mirror_x)
        mirror_y = numpy.random.choice(numpy.arange(0, 2), p=mirror_y)

        mirror_x = bool(mirror_x)
        mirror_y = bool(mirror_y)

        self.base_info["mirror_x"] = mirror_x
        self.base_info["mirror_y"] = mirror_y
        self.base_info["curiosity"] = random.random()

        first_cell = True
        for i in range(random.randrange(cellRange[0], cellRange[1])):
            added = False
            while not added:
                added = self.add_randomized_cell(
                    sizeRange,
                    massRange,
                    first_cell=first_cell
                )
            first_cell = False

        if self.base_info["mirror_x"] or self.base_info["mirror_y"]:
            first_id = self.first_cell()
            for cell_id in copy.deepcopy(self.cells):
                if cell_id != first_id:
                    grow_from = self.grows_from(cell_id)
                    grow_direction = self.growth_pattern[grow_from][cell_id]
                    self._apply_mirror(cell_id, grow_from, grow_direction)

        self._setup_brain_structure(hiddenRange)

        return self

    def mutate_cell_count(self, severity):
        new_dna = self.copy()
        current_count = len(self.cells)
        countRange = int(severity * 18.)
        countRange = round(countRange)
        if not countRange:
            return new_dna
        addedCells = random.randrange(-countRange, countRange)

        while current_count + addedCells < 2:
            addedCells = random.randrange(-countRange, countRange)

        if addedCells < 0:
            for i in range(positive(addedCells)):
                lCells = randomizeList(new_dna._lower_cells())
                rCell = lCells[0]
                if rCell in self.cells and not self.cells[rCell]["first"]:
                    new_dna.remove_cell(rCell)
        elif addedCells > 0:
            for i in range(addedCells):
                added = False
                while not added:
                    added = new_dna.add_randomized_cell(
                        new_dna.sizeRange,
                        new_dna.massRange
                    )
                if new_dna.base_info["mirror_x"] or new_dna.base_info["mirror_y"]:
                    grow_from = new_dna.grows_from(added)
                    grow_direction = new_dna.growth_pattern[grow_from][added]
                    new_dna._apply_mirror(added, grow_from, grow_direction)

        return new_dna

    def mutate_cell_types(self, severity):
        new_dna = self.copy()
        for cell_id in new_dna.cells:
            if random.random() <= severity and not new_dna.cells[cell_id]["first"]:
                new_type = random.choice(cell_types)
                new_dna.cells[cell_id]["type"] = new_type

                if new_dna.base_info["mirror_x"] or new_dna.base_info["mirror_y"]:
                    mirror_id = new_dna.cells[cell_id]["mirror_self"]
                    if mirror_id:
                        if mirror_id in new_dna.cells:
                            new_dna.cells[mirror_id]["type"] = new_type

        return new_dna

    def mutate_cell_masses(self, severity):
        new_dna = self.copy()
        for cell_id in new_dna.cells:
            if random.random() <= severity:
                mRange = int(severity * 10.)
                mRange = round(mRange)
                if not mRange:
                    return new_dna
                addRange = random.randrange(-mRange, mRange)

                while new_dna.cells[cell_id]["mass"] + addRange <= 0:
                    addRange = random.randrange(-mRange, mRange)

                new_dna.cells[cell_id]["mass"] += addRange

        return new_dna

    def mutate_brain_layers(self, severity, weight_persistence):
        oldHidden = self.brain_structure["hidden_layers"][:]
        newHidden = []
        lengthChange = round(severity * 10.)

        try:
            random.randrange(-lengthChange, lengthChange)
        except ValueError:
            return

        #lengthChange = random.randrange(-lengthChange, lengthChange)
        lengthChange = int(random.triangular(-lengthChange, 0, lengthChange))

        if lengthChange > 0:
            for i in range(lengthChange):
                if len(oldHidden)-1 >= i:
                    newLayer = oldHidden[i]
                else:
                    newLayer = random.randrange( min(oldHidden), max(oldHidden)+1 )
                newHidden.append(newLayer)
        else:
            newLength = len(oldHidden) + lengthChange
            if newLength < 1:
                newLength = 1

            for i in range(newLength):
                if len(oldHidden)-1 >= i:
                    newLayer = oldHidden[i]
                else:
                    newLayer = random.randrange( min(oldHidden), max(oldHidden)+1 )
                newHidden.append(newLayer)

        sizeChange = round(severity * 10.)

        for i in range(len(newHidden)):
            newAmt = int(random.triangular(-sizeChange, 0, sizeChange))
            newHidden[i] = newHidden[i]-newAmt
            if newHidden[i] < 2:
                newHidden[i] = 2

        self.brain_structure["hidden_layers"] = newHidden

        tempNet = neural.setup_network(self)

        if not tempNet.hiddenLayers or not weight_persistence:
            return # No hidden layers = no hidden weights to transfer over (or weight persistence is not active)

        #~ Weight preservation
        #  This allows for the weights of the parent network to be passed on to the offspring network - regardless of shape
        old_weights = self.brain_structure["hidden_weights"]
        old_layers = [ neural.torch.tensor(layer).float() for layer in old_weights ]

        self.brain_structure["hidden_weights"] = []

        for i, layer in enumerate(tempNet.hiddenLayers):
            newLayer = []
            for i2, subLayer in enumerate(layer.weight.data):
                try:
                    oldSubLayer = old_layers[i][i2]
                    newSubLayer = oldSubLayer.resize_(subLayer.shape)
                except IndexError:
                    newSubLayer = neural.torch.rand(subLayer.shape)
                newLayer.append(newSubLayer.tolist())
            if newLayer:
                self.brain_structure["hidden_weights"].append(newLayer)
        #~

    def mutate_curiosity(self, severity):
        new_dna = self.copy()
        add_curiosity = random.random() - random.random()
        add_curiosity *= severity
        new_curiosity = self.base_info["curiosity"] + add_curiosity

        if new_curiosity > 1.0:
            new_curiosity = 1.0
        elif new_curiosity < 0.0:
            new_curiosity = 0.0

        new_dna.base_info["curiosity"] = new_curiosity

        return new_dna

    def mutate_brain_structure(self, severity, weight_persistence):
        new_dna = self.copy()
        new_dna.mutate_brain_layers(severity, weight_persistence)

        return new_dna

    def mutate(self, severity, neural_severity, weight_persistence=True):
        # `severity` : 0.0 - 1.0
        new_dna = self.copy()

        new_dna.erase_memory()

        new_dna = new_dna.mutate_cell_count(severity)

        if random.random() <= severity:
            new_dna = new_dna.mutate_cell_types(severity)

        new_dna = new_dna.mutate_cell_masses(severity)

        new_dna = new_dna.mutate_brain_structure(neural_severity, weight_persistence)

        new_dna = new_dna.mutate_curiosity(neural_severity)

        return new_dna

class Organism():
    def __init__(self, pos, environment, dna=None):
        if not dna:
            dna = DNA().randomize()
        self.dna = dna
        self.pos = pos   # [<x>, <y>]
        self.environment = environment

        self.energy_consumed = 0.0
        # The amount of energy this organism has consumed from other cells

        self.cells = {}
        # Structure: {<Cell_ID>: <Sprite_Class>}

        self.immortal = False
        self.maladaptive = False
        self.finite_memory = environment.info["finite_memory"]

        self.movement = {
            "speed": 0,
            "direction": [0.0, 0.0],
            "rotation": 0
        }
        self.lastEnergy = 0.0 # Used to calculate the organism's dopamine
        self.lastHealth = 0.0 # Used to calculate the organism's pain
        self.energy_diff = 0.0
        self.dopamine_usage = 0.0
        self.dopamine = 0.0 # The current dopamine output
        self.pain = 0.0
        self.dopamine_update_interval = .1

        self.brain = None

        self.lastUpdated = time.time()
        self.last_updated_dopamine = time.time()
        self.lastBirth = time.time()
        self.birthTime = time.time()

    def _growth_position(self, newCellID, rel_pos=None):
        if not rel_pos:
            rel_pos = self.dna.cells[newCellID]["relative_pos"]
        new_position = [self.pos[0] + rel_pos[0],
                        self.pos[1] + rel_pos[1]]

        return new_position

    def cell_double_clicked(self, sprite):
        pass

    def _create_cell(self, cell_id, subCells=False):
        # When `subCells` equals True, it will also create all cells that grow from the given cell
        firstCell = self.dna.cells[cell_id]["first"]
        if cell_id in self.dna.cells and not cell_id in self.cells:
            if not self.dna.grows_from(cell_id) in self.cells and not firstCell:
                print("NO PARENT")
                print(self.dna.grows_from(cell_id))
                return
            if firstCell:
                pos = self.pos
            else:
                pos = self._growth_position(cell_id)

            cell_info = self.dna.cells[cell_id]

            #newCell = self._build_sprite(pos, cell_id, cell_info, alive=True)
            newCell = build_sprite(self.environment, self, pos, cell_id, cell_info, alive=True)

            self.cells[cell_id] = newCell

            if subCells:
                for cid in self.dna.sub_cells(cell_id):
                    self._create_cell(cid, subCells=False)

            return newCell

    def _make_dead_cell(self, sprite):
        cell_id = sprite.cell_id
        cell_info = sprite.cell_info

        new_cell_info = cell_info.copy()
        new_cell_info["type"] = "dead"

        #pos = sprite.body.position
        pos = [sprite.body.position[0], sprite.body.position[1]]

        #newCell = self._build_sprite(pos, cell_id, new_cell_info, alive=False)
        newCell = build_sprite(self.environment, self, pos, cell_id, new_cell_info, alive=False)

        return newCell

    def rotation(self):
        fCell = self.dna.first_cell()

        live_cells = self.living_cells()
        if fCell in live_cells:
            live_cells.remove(fCell)

        if not live_cells:
            return 0

        first_pos = self.cells[fCell].body.position
        first_size = self.dna.cells[fCell]["size"]

        rel_id = live_cells[0]
        rel_pos = self.cells[rel_id].body.position
        rel_info = self.dna.cells[rel_id]

        oldAngle = getAngle( 0, 0, rel_info["relative_pos"][0], rel_info["relative_pos"][1] )
        curAngle = getAngle( first_pos[0], first_pos[1], rel_pos[0], rel_pos[1] )

        angleDiff = curAngle - oldAngle

        return math.degrees(angleDiff) % 360

    def living_cells(self):
        # Returns all living cell IDs
        living_cells = []
        for cell_id in self.cells:
            if self.cells[cell_id].alive:
                living_cells.append(cell_id)
        return living_cells

    def get_rect(self):
        fCell = self.dna.first_cell()
        minX = self.cells[fCell].body.position[0]
        maxX = self.cells[fCell].body.position[0]
        minY = self.cells[fCell].body.position[1]
        maxY = self.cells[fCell].body.position[1]

        for cell_id in self.living_cells():
            pos = self.cells[cell_id].body.position
            radius = self.cells[cell_id].radius

            if pos[0]-radius < minX:
                minX = pos[0] - radius
            if pos[0]+radius > maxX:
                maxX = pos[0] + radius

            if pos[1]-radius < minY:
                minY = pos[1] - radius
            if pos[1]+radius > maxY:
                maxY = pos[1] + radius

        return [minX, minY, maxX, maxY]

    def get_width(self):
        rect = self.get_rect()
        return rect[2] - rect[0]
    def get_height(self):
        rect = self.get_rect()
        return rect[3] - rect[1]

    def _revive_cell_angle(self, cell_id):
        # BACKBURNER
        parentID = self.dna.grows_from(cell_id)

        if not self.cells[parentID].alive:
            return

        childSize = self.dna.cells[cell_id]["size"]

        newAngle = math.radians(self.rotation())

        newDirection = getDirection(newAngle)

        newRelPos = self.dna._new_relative_pos(parentID, newDirection, childSize)

        return newRelPos

    def revive_cell(self, sprite):
        # BACKBURNER
        if sprite.alive:
            return
        cell_id = sprite.cell_id

        firstCell = self.dna.cells[cell_id]["first"]

        #if firstCell:
        #    pos = self.pos
        #else:
        #    pos = self._growth_position(cell_id)

        newRelPos = self._revive_cell_angle(cell_id)
        pos = self._growth_position(cell_id, rel_pos=newRelPos)

        cell_info = self.dna.cells[cell_id]
        #newCell = self._build_sprite(pos, cell_id, cell_info, alive=True)
        newCell = build_sprite(self.environment, self, pos, cell_id, cell_info, alive=True)
        self.cells[cell_id] = newCell

        sprite = self.cells[cell_id]
        self.environment.add_sprite(sprite)

        for cid in self.cells:
            if cid != cell_id:
                sprite2 = self.cells[cid]
                sprite.connectTo(sprite2)

    def dead_children(self, sprite):
        dead_list = []
        cell_id = sprite.cell_id
        if cell_id in self.cells:
            for cid in self.dna.sub_cells(cell_id):
                if cid in self.cells:
                    sprite = self.cells[cid]
                    if not sprite.alive:
                        dead_list.append(sprite)
        return dead_list

    def dead_cells(self):
        # Returns all dead cell IDs
        dead_cells = []
        for cell_id in self.cells:
            if not self.cells[cell_id].alive:
                dead_cells.append(cell_id)
        return dead_cells

    def max_energy(self):
        return sum([ self.dna.cells[id]["energy_storage"] for id in self.living_cells() ])
    def total_energy(self):
        # Returns the total amount of energy within this organism
        energyList = [self.cells[i].info["energy"] for i in self.living_cells()]
        return sum(energyList)
    def energy_percent(self):
        if not self.total_energy() or not self.max_energy():
            return 0.0
        return num2perc( self.total_energy(), self.max_energy() )

    def max_health(self):
        return sum([self.dna.cells[id]["max_health"] for id in self.cells])
    def current_health(self):
        return sum([self.cells[id].info["health"] for id in self.living_cells()])
    def health_percent(self):
        if not self.current_health() or not self.max_health():
            return 0.0
        return num2perc( self.current_health(), self.max_health() )

    def kill_cell(self, sprite):
        if not sprite.alive:
            return
        sprite.alive = False
        sprite.info["health"] = 0
        cell_id = sprite.cell_id
        cell_info = sprite.cell_info

        if not cell_info["first"]:
            parentID = self.dna.grows_from(cell_id)
            parentSprite = self.cells[parentID]
            if parentSprite.alive:
                damage_dealt = break_damage * cell_info["size"]
                parentSprite.info["health"] -= damage_dealt

        newCell = self._make_dead_cell(sprite)
        self.environment.removeSprite(sprite)
        sprite.info["removed"] = True
        self.environment.add_sprite(newCell)

        self.environment.space.remove(*sprite.info["detection_items"])

        newCell.body.velocity = sprite.body.velocity
        newCell.body.angular_velocity = sprite.body.angular_velocity

        for cid in self.dna.sub_cells(cell_id):
            self.kill_cell(self.cells[cid])

        return newCell

    def alive(self):
        # Returns True if this organism is alive, False otherwise
        if self.living_cells():
            return True
        return False

    def kill(self):
        fCell = self.dna.first_cell()
        if self.cells[fCell].alive:
            self.kill_cell(self.cells[fCell])

    def collision(self, cell, sprite):
        if not sprite in cell.info["colliding"]:
            cell.info["colliding"].append(sprite)
        if not cell in sprite.info["colliding"]:
            sprite.info["colliding"].append(cell)

    def _cell_collision(self, cell, sprite):
        # cell is the sprite for THIS organism's cell
        # sprite is the sprite for the OTHER organism's cell
        if not sprite in self.cells.values():
            if cell.alive or sprite.alive:
                self.collision(cell, sprite)

    def add_energy(self, amount):
        if not self.living_cells():
            return
        energy_per_cell = amount / len(self.living_cells())
        spare_energy = 0
        for cell_id in self.cells.copy():
            if self.cells[cell_id].alive:
                sprite = self.cells[cell_id]
                cell_info = sprite.cell_info

                sprite.info["energy"] += energy_per_cell + spare_energy
                spare_energy = 0

                if sprite.info["energy"] > cell_info["energy_storage"]:
                    diff = sprite.info["energy"] - cell_info["energy_storage"]
                    sprite.info["energy"] = cell_info["energy_storage"]
                    spare_energy = diff

        return spare_energy

    def take_energy(self, amount):
        live_cells = self.living_cells()
        for cell_id in live_cells:
            sprite = self.cells[cell_id]
            sprite.info["energy"] -= amount / len(live_cells)

            if sprite.info["energy"] < 0:
                sprite.info["energy"] = 0

    def convert_co2(self, cell_id, uDiff):
        sprite = self.cells[cell_id]
        if sprite.alive:
            cell_info = sprite.cell_info
            conversion_perc = num2perc(cell_info["size"], self.environment.width + self.environment.height)
            conversion_ratio = conversion_perc * uDiff

            co2 = perc2num(conversion_ratio, self.environment.info["co2"])

            self.environment.info["co2"] -= co2
            self.environment.info["oxygen"] += co2
            #added_energy = co2 * (cell_info["mass"] / 3) * (cell_info["size"] / 3)
            added_energy = co2 * co2_conversion_ratio

            spare_energy = self.add_energy(added_energy)

            #spare_co2 = spare_energy / (cell_info["mass"] / 3) / (cell_info["size"] / 3)
            spare_co2 = spare_energy / co2_conversion_ratio
            self.environment.info["co2"] += spare_co2
            self.environment.info["oxygen"] -= spare_co2

    def convert_o2(self, cell_id, uDiff):
        sprite = self.cells[cell_id]
        if sprite.alive:
            cell_info = sprite.cell_info
            conversion_perc = num2perc(cell_info["size"], self.environment.width + self.environment.height)
            conversion_ratio = conversion_perc * uDiff
            oxygen = perc2num(conversion_ratio, self.environment.info["oxygen"])

            self.environment.info["oxygen"] -= oxygen
            self.environment.info["co2"] += oxygen

    def _reproduce_organism(self, severity, neural_severity):
        self.lastBirth = time.time()
        new_dna = self.dna.mutate(severity=severity, neural_severity=neural_severity, weight_persistence=self.environment.info["weight_persistence"])

        new_pos = get_new_organism_position(self, new_dna, self.environment)
        if not new_pos:
            return False

        organism = Organism(new_pos, self.environment, dna=new_dna)
        organism.dna.generation = self.dna.generation + 1
        self.environment.info["organism_list"].append(organism)
        organism.build_body()
        return True

    def reproduce(self, severity=0.5, neural_severity=0.5):
        for i in range(offspring_amount):
            success = self._reproduce_organism(severity, neural_severity)
            if not success:
                break

    def _update_brain_weights(self):
        new_weights = []

        for layer in self.brain.hiddenLayers:
            layer_weights = layer.weight.data.tolist()
            new_weights.append(layer_weights)

        self.dna.brain_structure["hidden_weights"] = new_weights

    def _update_cell(self, cell_id, uDiff):
        sprite = self.cells[cell_id]
        cell_info = self.dna.cells[cell_id]

        if sprite.info["health"] <= 0 and not self.immortal:
            self.kill_cell(sprite)
            return

        if not sprite.alive:
            return

        if cell_info["type"] == "eye" or cell_info["type"] == "olfactory":
            cShape = sprite.info["detection_items"][1]
            for cell in sprite.info["view"][:]:
                if not cShape.shapes_collide(cell.shape).points or cell.info["removed"]:
                    sprite.info["view"].remove(cell)

        for cell in sprite.info["colliding"]:
            if not sprite.shape.shapes_collide(cell.shape).points or cell.info["removed"]:
                sprite.info["colliding"].remove(cell)

        if cell_info["type"] == "carniv":
            if sprite.info["colliding"]:
                sprite.info["in_use"] = True
            else:
                sprite.info["in_use"] = False

            damage_dealt = cell_info["damage"]
            for cell in sprite.info["colliding"]:
                added_energy = damage_dealt / 2
                if cell.alive:
                    if cell.cell_info["type"] != "barrier" or not cell.alive:
                        cell.info["health"] -= damage_dealt
                else:
                    mass = cell.body.mass
                    newMass = mass - (damage_dealt / sprite.cell_info["size"])

                    added_energy *= cell.cell_info["mass"]
                    added_energy *= cell.cell_info["size"]

                    if newMass > 0:
                        cell.body._set_mass(newMass)
                    else:
                        cell.body._set_mass(0)

                #sprite.organism.add_energy(added_energy)
                sprite.organism.energy_consumed += added_energy

        if cell_info["type"] == "push" and sprite.alive:
            if positive(self.movement["speed"]) >= .1:
                push_x = self.movement["direction"][0]
                push_y = self.movement["direction"][1]

                rotation = self.rotation()

                push_angle = math.atan2( push_y, push_x )

                push_angle += math.radians(rotation)

                #push_x += math.cos(math.radians(rotation))
                #push_y += math.sin(math.radians(rotation))
                push_x = math.cos(push_angle)
                push_y = math.sin(push_angle)

                speed = self.movement["speed"] * max_push_speed
                speed *= (uDiff)

                new_velocity = [push_x*speed, push_y*speed]
                new_velocity = Vec2d(new_velocity)

                sprite.body.velocity += new_velocity
                sprite.info["in_use"] = True
            else:
                sprite.info["in_use"] = False

        if cell_info["type"] == "rotate" and sprite.alive:
            sprite.body.angular_velocity += self.movement["rotation"] * max_rotation_speed

            if positive(self.movement["rotation"]) >= .1:
                sprite.info["in_use"] = True
            else:
                sprite.info["in_use"] = False

        if sprite.info["in_use"]:
            if cell_info["type"] == "push":
                usage_diff = cell_info["energy_usage"][1] - cell_info["energy_usage"][0]
                push_energy = usage_diff * self.movement["speed"]

                if push_energy < 0:
                    energy_usage = positive(-cell_info["energy_usage"][0] + push_energy)
                else:
                    energy_usage = cell_info["energy_usage"][0] + push_energy
            elif cell_info["type"] == "rotate":
                usage_diff = cell_info["energy_usage"][1] - cell_info["energy_usage"][0]
                rotate_energy = usage_diff * self.movement["rotation"]

                if rotate_energy < 0:
                    energy_usage = positive(-cell_info["energy_usage"][0] + rotate_energy)
                else:
                    energy_usage = cell_info["energy_usage"][0] + rotate_energy
            else:
                energy_usage = cell_info["energy_usage"][1]
        else:
            energy_usage = cell_info["energy_usage"][0]

        self.take_energy(energy_usage * uDiff)

        if sprite.info["energy"] < 0:
            sprite.info["energy"] = 0

        if cell_info["type"] == "co2C":
            self.convert_co2(cell_id, uDiff)
        else:
            self.convert_o2(cell_id, uDiff)

        if sprite.info["energy"] > cell_info["energy_storage"]:
            sprite.info["energy"] = cell_info["energy_storage"]

        if sprite.info["energy"] >= cell_info["energy_storage"]/2 and sprite.info["health"] < cell_info["max_health"]:
            added_health = perc2num( heal_rate, cell_info["max_health"] ) * uDiff

            sprite.info["health"] += added_health
            if sprite.info["health"] > cell_info["max_health"]:
                sprite.info["health"] = cell_info["max_health"]

        #if sprite.info["energy"] >= cell_info["energy_storage"]/1.01 and self.dead_children(sprite):
        #    dead_list = self.dead_children(sprite)
        #    print(len(dead_list))
        #    if dead_list:
        #        self.revive_cell(dead_list[0])
        #        sprite.info["energy"] /= 2

        if sprite.info["energy"] <= perc2num(2, sprite.cell_info["energy_storage"]):
            damage_dealt = perc2num(starvation_rate, sprite.cell_info["max_health"])
            damage_dealt *= uDiff
            sprite.info["health"] -= damage_dealt

    def _update_brain(self):
        dopamine_uDiff = time.time() - self.last_updated_dopamine
        health_diff = self.health_percent() - self.lastHealth
        energy_diff = self.energy_percent() - self.lastEnergy
        self.energy_diff = energy_diff / dopamine_uDiff

        self.pain = health_diff / dopamine_uDiff
        self.dopamine_usage = energy_diff / dopamine_uDiff

        self.pain *= 5.

        if self.pain > 0:
            self.pain = 0.0

        self.pain = positive(self.pain)

        self.dopamine = self.dopamine_usage - self.pain
        self.last_updated_dopamine = time.time()

    def update(self):
        uDiff = time.time() - self.lastUpdated
        self.lastUpdated = time.time()

        if self.energy_consumed >= self.max_energy()/2:
            self.energy_consumed = self.max_energy()/2
        if self.energy_consumed < 0.001:
            self.energy_consumed = 0

        if self.energy_consumed:
            digested_energy = perc2num(95, self.energy_consumed) * uDiff

            self.energy_consumed -= digested_energy
            self.energy_consumed += self.add_energy(digested_energy * 1.2)

        #~ Brain udpate section
        brain_uDiff = time.time() - self.brain.lastUpdated
        #if time.time() - self.last_updated_dopamine >= self.dopamine_update_interval:

        if brain_uDiff >= neural_update_delay:
            self._update_brain()

            neural_thread = Thread( target=self.brain.activate, args=[self.environment, self, uDiff] )
            neural_thread.run()
        #~

        self.lastHealth = self.health_percent()
        self.lastEnergy = self.energy_percent()

        if len(self.living_cells()) == 1:
            self.kill()

        fCell = self.dna.first_cell()
        if self.cells[fCell].alive:
            self.pos = self.cells[fCell].body.position

        max_energy = self.max_energy()
        if self.total_energy() >= max_energy/1.01 and (time.time() - self.lastBirth) >= reproduction_limit:
            if random.random()*100. <= self.health_percent():
                if not self.environment.info["population"] >= self.environment.info["population_limit"]:
                    self.reproduce(
                        severity=self.environment.info["mutation_severity"],
                        neural_severity=self.environment.info["brain_mutation_severity"]
                    )

            for cell_id in self.cells:
                sprite = self.cells[cell_id]
                if sprite.alive:
                    sprite.info["energy"] = sprite.info["energy"]/2

            self.lastHealth = self.health_percent()
            self.lastEnergy = self.energy_percent()

        for cell_id in self.cells.copy():
            self._update_cell(cell_id, uDiff)

    def build_brain(self):
        learning_rate = self.environment.info["learning_rate"]
        self.brain = neural.setup_network(
            self.dna,
            learning_rate = learning_rate,
            rnn = self.environment.info["use_rnn"],
            hiddenSize = self.environment.info["hidden_rnn_size"]
        )

        for i, layer in enumerate(self.brain.hiddenLayers):
            new_weights = self.dna.brain_structure["hidden_weights"][i]
            new_weights = neural.torch.tensor(new_weights).float()

            layer.weight.data = new_weights

    def build_body(self):
        self.cells = {}
        self.lastUpdated = time.time()

        self.build_brain()

        fCell = self.dna.first_cell()
        # `fCell` holds information on the first cell that needs to be added

        if fCell:
            self._create_cell(fCell, subCells=True)
        else:
            print("ERROR: First cell not located - Quitting...")
            return

        for cell_id in self.dna.cells:
            self._create_cell(cell_id, subCells=True)

        for cell_id in self.cells:
            sprite = self.cells[cell_id]
            self.environment.add_sprite(sprite)
            self.environment.space.add(*sprite.info["detection_items"])

        for cell_id in self.cells:
            sprite = self.cells[cell_id]
            for cid in self.cells:
                if cid != cell_id:
                    sprite2 = self.cells[cid]
                    sprite.connectTo(sprite2)

        self.lastHealth = self.health_percent()
        self.lastEnergy = self.energy_percent()

def disperse_cell(sprite):
    environment = sprite.environment
    #cell_info = sprite.cell_info
    #cell_id = sprite.cell_id

    #mass = sprite.body.mass

    #dispersed_co2 = co2_dispersion_ratio * (cell_info["size"] / 2) + mass

    #environment.info["co2"] += dispersed_co2
    #environment.info["oxygen"] -= dispersed_co2
    environment.removeSprite(sprite)
    sprite.info["removed"] = True

def disperse_all_dead(environment):
    for sprite in environment.sprites[:]:
        if not sprite.alive and not sprite.isBubble:
            disperse_cell(sprite)

def gradual_dispersion(environment, sprite, uDiff):
    mass = sprite.body.mass
    dispersion_rate = perc2num(cell_dispersion_rate, mass) * uDiff
    cell_info = sprite.cell_info
    #organism = sprite.organism
    #cell_info = organism.dna.cells[sprite.cell_id]
    #dispersion_rate = cell_dispersion_rate * uDiff

    if mass < mass_cutoff:
        disperse_cell(sprite)
    else:
        newMass = mass - dispersion_rate
        if newMass > 0:
            renewedCo2 = perc2num(dispersion_rate, environment.info["oxygen"])

            sprite.body._set_mass(newMass)

            environment.info["co2"] += renewedCo2
            environment.info["oxygen"] -= renewedCo2
        else:
            disperse_cell(sprite)

def make_bubble(environment):
    def bubbleGravity(body, gravity, damping, dt):
        pymunk.Body.update_velocity(body, (0,-5*(body.radius/2)), damping, dt)

    bubbleRadius = random.randrange(3, 12)
    bubblePos = [ random.randrange(0, environment.width), environment.height-(bubbleRadius*2) ]

    newBody, newShape = physics.makeCircle( bubbleRadius, friction=0.0, elasticity=0.0, mass=1 )
    newShape.sensor = True
    newShape.collision_type = 2
    newBody.position = bubblePos
    newBody.velocity_func = bubbleGravity
    newBody.radius = bubbleRadius

    bubble = physics.Sprite(
                            bubblePos, bubbleRadius*2, bubbleRadius*2, environment, newBody, newShape,
                            image="Images{}bubble.png".format(dirType),
                            parent=None
                            )
    bubble.alive = False
    bubble.isBubble = True

    bubble.info = {
        "health": 0,
        "energy": 0,
        "in_use": False,
        "detection_items": [],
        "view": [],
        "removed": False,
        "colliding": [],
        "sight_range": 0.0
    }

    environment.add_sprite(bubble)

def update_organisms(environment):
    # For this to work properly the environment must have the variable `lastUpdated` which holds a time.time() value
    # The environment must also have the ".info" variable which holds a dictionary containing "oganism_list" as a value
    uDiff = time.time() - environment.lastUpdated

    if environment.info["paused"]:
        return
    environment.lastUpdated = time.time()

    worldSpeedPerc = num2perc(environment.worldSpeed, environment.default_world_speed) / 100.

    global last_bubble_creation
    if time.time() - last_bubble_creation >= (bubble_interval / worldSpeedPerc):
        make_bubble(environment)
        last_bubble_creation = time.time()

    for org in environment.info["organism_list"][:]:
        if time.time() - org.birthTime >= age_limit and not org.immortal:
            org.kill()

        if org.alive():
            org.update()

            train_uDiff = time.time() - org.brain.lastTrained
            if train_uDiff >= learning_update_delay:
                if not org.maladaptive:
                    neural.train_network(org, epochs=training_epochs)
        else:
            environment.info["organism_list"].remove(org)

    for sprite in environment.sprites:
        if not sprite.alive and not sprite.isBubble:
            gradual_dispersion(environment, sprite, uDiff)
        elif sprite.isBubble:
            if sprite.getPos()[1] <= 0:
                environment.removeSprite(sprite)
                sprite.info["removed"] = True
            else:
                newVel = pymunk.Vec2d(random.randrange(-2, 2), 0)
                sprite.body.velocity += newVel
        sprite_pos = sprite.getPos()
        if sprite_pos[0] < 0 or sprite_pos[0] > environment.width or sprite_pos[1] < 0 or sprite_pos[1] > environment.height:
            if sprite.alive:
                organism = sprite.organism
                sprite.info["health"] = 0
            else:
                environment.removeSprite(sprite)
                sprite.info["removed"] = True


text:
""" File for missing data correction using last observation carried forward.

Note this is quite slow to do. Lambda functions aren't terrible but this can probably be improved.
"""

import pandas as pd
import numpy as np
import US_utils
import US_missing_description
from multiprocessing import Pool, cpu_count

def applyParallelLOCF(dfGrouped, func):
    with Pool(cpu_count()) as p:
        ret_list = p.map(func, [group for name, group in dfGrouped])
    return pd.concat(ret_list)

def ffill_groupby(pid_groupby):
    return pid_groupby.apply(lambda x: x.replace(US_utils.missing_types, method="ffill"))

def bfill_groupby(pid_groupby):
    return pid_groupby.apply(lambda x: x.replace(US_utils.missing_types, method="bfill"))

def fbfill_groupby(pid_groupby):
    return pid_groupby.apply(lambda x: x.replace(US_utils.missing_types, method="ffill").replace(US_utils.missing_types, method="bfill"))

def locf(data, f_columns = None, b_columns = None, fb_columns = None):
    """ Last observation carrying for correcting missing data.

    Data is often only recorded when someone either enters the study for
    the first time (immutable values e.g. ethnicity) is
    updated (mutable values e.g. education).
    Otherwise attributes are recorded as missing due to non-applicability (-8).
    This function aims to fill in any missing values due to this. E.g. ethnicity
    will be registered all the way through an individual's observations.

    Parameters
    ----------
    data : pd.DataFrame
        The main `data` frame for US to fix leading entries for. This frame must have pidp, time, and the desired list
        of columns.
    columns : list
    type : str
        Type of observation carrying to do. Either forwards 'f' backwards 'b' or forwards then backwards 'fb'.
        Defaults to forward as last observation carried forwards (locf) is the only one you should really do unless
        the attributes are immutable. Even then its tenuous.

    Returns
    -------
    data : pd. DataFrame
        The data frame with missing data correction done by observation carrying.

    """
    print("Starting locf. Grab a coffee..")
    # sort values by pidp and time. Need chronological order for carrying to make sense.
    # Also allows for easy replacing of filled data later.
    data = data.sort_values(by=["pidp", "time"])
    missing_types = ['-1', '-2', '-7', '-8', '-9', -1, -2, -7, -8, -9, '-1.0', '-2.0', '-7.0', '-8.0', '-9.0']

    # Group data into individuals to fill in separately.
    # Acts as a for loop to fill items by each individual. If you dont this it will try and fill the whole dataframe
    # at once.
    pid_groupby = data.groupby(by=["pidp"], sort=False, as_index=False)

    # Fill missing data by individual for given carrying type. Forwards, backwards, or forward then backwards.
    # See pandas ffill and bfill functions for more details.
    if f_columns:
        # Forward fill.
        fill = applyParallelLOCF(pid_groupby[f_columns], ffill_groupby)
        data[f_columns] = fill
    if b_columns:
        # backward fill. only use this on IMMUTABLE attributes.
        fill = applyParallelLOCF(pid_groupby[b_columns], bfill_groupby)
        data[b_columns] = fill
    if fb_columns:
        # forwards and backwards fill. again immutables only.
        fill = applyParallelLOCF(pid_groupby[fb_columns], fbfill_groupby)
        data[fb_columns] = fill
    data = data.reset_index(drop=True) # groupby messes with the index. make them unique again.
    return data

def main():
    # Load in data.
    # Process data by year and pidp.
    # perform LOCF using lambda forward fill functions.
    years = np.arange(1990, 2017)
    file_names = [f"data/deterministic_US/{item}_US_cohort.csv" for item in years]
    data = US_utils.load_multiple_data(file_names)
    #US_missing_description.missingness_hist(data, "education_state", "age")
    #US_missing_description.missingness_hist(data, "labour_state", "age")
    #US_missing_description.missingness_bars(data, "education_state", "ethnicity")

    before = US_missing_description.missingness_table(data)
    f_columns = ["age", "education_state", "depression", "depression_change",
                 "labour_state", "job_duration_m", "job_duration_y", "job_occupation",
                 "job_industry", "job_sec"] #add more variables here.
    fb_columns = ["sex", "ethnicity", "birth_year"] # or here if they're immutable.
    data = locf(data, f_columns=f_columns, fb_columns=fb_columns)
    after = US_missing_description.missingness_table(data)
    #US_missing_description.missingness_hist(data, "education_state", "age")
    #US_missing_description.missingness_hist(data, "labour_state", "age")
    #US_missing_description.missingness_bars(data, "education_state", "ethnicity")
    US_utils.save_multiple_files(data, years, 'data/locf_US/', "")
    return data, before, after

if __name__ == "__main__":
    data, before, after = main()