From e873102fbe7df8d57885ac99815faa6959ec1391 Mon Sep 17 00:00:00 2001 From: ctrl-z-9000-times Date: Thu, 26 Sep 2019 16:32:02 +0200 Subject: [PATCH 01/49] Retina encoder: initial commit eye.py is a biological implementation of retina (encoder). WIP --- py/htm/encoders/eye.py | 588 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 588 insertions(+) create mode 100644 py/htm/encoders/eye.py diff --git a/py/htm/encoders/eye.py b/py/htm/encoders/eye.py new file mode 100644 index 0000000000..6254da2937 --- /dev/null +++ b/py/htm/encoders/eye.py @@ -0,0 +1,588 @@ +# Written by David McDougall, 2018 + +""" From Wikipedia "Retina": Although there are more than 130 million +retinal receptors, there are only approximately 1.2 million fibres +(axons) in the optic nerve; a large amount of pre-processing is +performed within the retina. The fovea produces the most accurate +information. Despite occupying about 0.01% of the visual field (less +than 2 of visual angle), about 10% of axons in the optic nerve are +devoted to the fovea. + +Fun Fact 1: The human optic nerve has 800,000 ~ 1,700,000 nerve fibers. +Fun Fact 2: The human eye can distiguish between 10 million different colors. +Sources: Wikipedia. """ + +import numpy as np +import cv2 +import scipy.misc +import math +import scipy.ndimage +import random +import PIL, PIL.ImageDraw +import matplotlib.pyplot as plt +from sdr import SDR +import encoders + + +class Eye: + """ + Optic sensor with central fovae. + + Attribute output_sdr ... retina's output + Attribute roi ... The most recent view, kept as a attribute. + Attribute parvo ... + Attribute magno ... + + The following three attributes control where the eye is looking within + the image. They are Read/Writable. + Attribute position (X, Y) coords of eye center within image + Attribute orientation ... units are radians + Attribute scale ... + """ + def __init__(self, + output_diameter = 200, + resolution_factor = 3, + fovea_scale = .177, + sparsity = .2,): + """ + Argument output_diameter is size of output ... + Argument resolution_factor is used to expand the sensor array so that + the fovea has adequate resolution. After log-polar transform image + is reduced by this factor back to the output_diameter. + Argument fovea_scale is magic number ... + Argument sparsity is fraction of bits in eye.output_sdr which are + active, on average. + """ + self.output_diameter = output_diameter + self.retina_diameter = int(resolution_factor * output_diameter) + self.fovea_scale = fovea_scale + assert(output_diameter // 2 * 2 == output_diameter) # Diameter must be an even number. + assert(self.retina_diameter // 2 * 2 == self.retina_diameter) # (Resolution Factor X Diameter) must be an even number. + + self.output_sdr = SDR((output_diameter, output_diameter, 2,)) + + self.retina = cv2.bioinspired.Retina_create( + inputSize = (self.retina_diameter, self.retina_diameter), + colorMode = True, + colorSamplingMethod = cv2.bioinspired.RETINA_COLOR_BAYER,) + + print(self.retina.printSetup()) + print() + + self.parvo_enc = encoders.ChannelEncoder( + input_shape = (output_diameter, output_diameter, 3,), + num_samples = 1, sparsity = sparsity ** (1/3.), + dtype=np.uint8, drange=[0, 255,]) + + self.magno_enc = encoders.ChannelEncoder( + input_shape = (output_diameter, output_diameter), + num_samples = 1, sparsity = sparsity, + dtype=np.uint8, drange=[0, 255],) + + self.image_file = None + self.image = None + + def new_image(self, image): + """ + Argument image ... + If String, will load image from file path. + If numpy.ndarray, will attempt to cast to correct data type and + dimensions. + """ + # Load image if needed. + if isinstance(image, str): + self.image_file = image + self.image = np.array(PIL.Image.open(image), copy=False) + else: + self.image_file = None + self.image = image + # Get the image into the right format. + assert(isinstance(self.image, np.ndarray)) + if self.image.dtype != np.uint8: + raise TypeError('Image "%s" dtype is not unsigned 8 bit integer, image.dtype is %s.'%( + self.image_file if self.image_file is not None else 'argument', + self.image.dtype)) + # Ensure there are three color channels. + if len(self.image.shape) == 2 or self.image.shape[2] == 1: + self.image = np.dstack([self.image] * 3) + # Drop the alpha channel if present. + elif self.image.shape[2] == 4: + self.image = self.image[:,:,:3] + # Sanity checks. + assert(len(self.image.shape) == 3) + assert(self.image.shape[2] == 3) # Color images only. + + self.reset() + self.center_view() + + def center_view(self): + """Center the view over the image""" + self.orientation = 0 + self.position = (self.image.shape[0]/2., self.image.shape[1]/2.) + self.scale = np.min(np.divide(self.image.shape[:2], self.retina_diameter)) + + def randomize_view(self, scale_range=None): + """Set the eye's view point to a random location""" + if scale_range is None: + scale_range = [2, min(self.image.shape[:2]) / self.retina_diameter] + self.orientation = random.uniform(0, 2 * math.pi) + self.scale = random.uniform(min(scale_range), max(scale_range)) + roi_radius = self.scale * self.retina_diameter / 2 + self.position = [random.uniform(roi_radius, dim - roi_radius) + for dim in self.image.shape[:2]] + + def _crop_roi(self): + """ + Crop to Region Of Interest (ROI) which contains the whole field of view. + Note that the size of the ROI is (eye.output_diameter * + eye.resolution_factor). + + Arguments: eye.scale, eye.position, eye.image + + Returns RGB image. + """ + r = int(round(self.scale * self.retina_diameter / 2)) + x, y = self.position + x = int(round(x)) + y = int(round(y)) + x_max, y_max, color_depth = self.image.shape + # Find the boundary of the ROI and slice out the image. + x_low = max(0, x-r) + x_high = min(x_max, x+r) + y_low = max(0, y-r) + y_high = min(y_max, y+r) + image_slice = self.image[x_low : x_high, y_low : y_high] + # Make the ROI and insert the image into it. + roi = np.zeros((2*r, 2*r, 3,), dtype=np.uint8) + if x-r < 0: + x_offset = abs(x-r) + else: + x_offset = 0 + if y-r < 0: + y_offset = abs(y-r) + else: + y_offset = 0 + x_shape, y_shape, color_depth = image_slice.shape + roi[x_offset:x_offset+x_shape, y_offset:y_offset+y_shape] = image_slice + # Rescale the ROI to remove the scaling effect. + roi = scipy.misc.imresize(roi, (self.retina_diameter, self.retina_diameter)) + return roi + + def compute(self): + self.roi = self._crop_roi() + + # Retina image transforms (Parvo & Magnocellular). + self.retina.run(self.roi) + parvo = self.retina.getParvo() + magno = self.retina.getMagno() + + # Log Polar Transform. + center = self.retina_diameter / 2 + M = self.retina_diameter * self.fovea_scale + parvo = cv2.logPolar(parvo, + center = (center, center), + M = M, + flags = cv2.WARP_FILL_OUTLIERS) + magno = cv2.logPolar(magno, + center = (center, center), + M = M, + flags = cv2.WARP_FILL_OUTLIERS) + parvo = scipy.misc.imresize(parvo, (self.output_diameter, self.output_diameter)) + magno = scipy.misc.imresize(magno, (self.output_diameter, self.output_diameter)) + + # Apply rotation by rolling the images around axis 1. + rotation = self.output_diameter * self.orientation / (2 * math.pi) + rotation = int(round(rotation)) + self.parvo = np.roll(parvo, rotation, axis=0) + self.magno = np.roll(magno, rotation, axis=0) + + # Encode images into SDRs. + p = self.parvo_enc.encode(self.parvo) + pr, pg, pb = np.dsplit(p, 3) + p = np.logical_and(np.logical_and(pr, pg), pb) + p = np.expand_dims(np.squeeze(p), axis=2) + m = self.magno_enc.encode(self.magno) + sdr = np.concatenate([p, m], axis=2) + self.output_sdr.dense = sdr + return self.output_sdr + + def make_roi_pretty(self, roi=None): + """ + Makes the eye's view look more presentable. + - Adds a black circular boarder to mask out areas which the eye can't see + Note that this boarder is actually a bit too far out, playing with + eye.fovea_scale can hide areas which this ROI image will show. + - Adds 5 dots to the center of the image to show where the fovea is. + + Returns an RGB image. + """ + if roi is None: + roi = self.roi + + # Show the ROI, first rotate it like the eye is rotated. + angle = self.orientation * 360 / (2 * math.pi) + roi = self.roi[:,:,::-1] + rows, cols, color_depth = roi.shape + M = cv2.getRotationMatrix2D((cols / 2, rows / 2), angle, 1) + roi = cv2.warpAffine(roi, M, (cols,rows)) + + # Mask out areas the eye can't see by drawing a circle boarder. + center = int(roi.shape[0] / 2) + circle_mask = np.zeros(roi.shape, dtype=np.uint8) + cv2.circle(circle_mask, (center, center), center, thickness = -1, color=(255,255,255)) + roi = np.minimum(roi, circle_mask) + + # Invert 5 pixels in the center to show where the fovea is located. + roi[center, center] = np.full(3, 255) - roi[center, center] + roi[center+2, center+2] = np.full(3, 255) - roi[center+2, center+2] + roi[center-2, center+2] = np.full(3, 255) - roi[center-2, center+2] + roi[center-2, center-2] = np.full(3, 255) - roi[center-2, center-2] + roi[center+2, center-2] = np.full(3, 255) - roi[center+2, center-2] + return roi + + def show_view(self, window_name='Eye'): + if False: + print("Sparsity %g"%(len(self.output_sdr) / self.output_sdr.size)) + parvo = self.output_sdr.dense[:,:,0] + magno = self.output_sdr.dense[:,:,1] + print("Parvo Sparsity %g"%(np.count_nonzero(parvo) / np.product(parvo.shape))) + print("Magno Sparsity %g"%(np.count_nonzero(magno) / np.product(magno.shape))) + roi = self.make_roi_pretty() + cv2.imshow('Region Of Interest', roi) + cv2.imshow('Parvocellular', self.parvo[:,:,::-1]) + cv2.imshow('Magnocellular', self.magno) + cv2.waitKey(1) + + def input_space_sample_points(self, npoints): + """ + Returns a sampling of coordinates which the eye is currently looking at. + Use the result to determine the actual label of the image in the area + where the eye is looking. + """ + # Find the retina's radius in the image. + r = int(round(self.scale * self.retina_diameter / 2)) + # Shrink the retina's radius so that sample points are nearer the fovea. + # Also shrink radius B/C this does not account for the diagonal + # distance, just the manhattan distance. + r = r * 2/3 + # Generate points. + coords = np.random.random_integers(-r, r, size=(npoints, 2)) + # Add this position offset. + coords += np.array(np.rint(self.position), dtype=np.int).reshape(1, 2) + return coords + + def reset(self): + self.retina.clearBuffers() + + +class EyeSensorSampler: + """ + Samples eyesensor.rgb, the eye's view. + + Attribute samples is list of RGB numpy arrays. + """ + def __init__(self, eyesensor, sample_period, number_of_samples=30): + """ + This draws its samples directly from the output of eyesensor.view() by + wrapping the method. + """ + self.sensor = sensor = eyesensor + self.sensor_compute = sensor.compute + self.sensor.compute = self.compute + self.age = 0 + self.samples = [] + number_of_samples = min(number_of_samples, sample_period) # Don't die. + self.schedule = random.sample(range(sample_period), number_of_samples) + self.schedule.sort(reverse=True) + + def compute(self, *args, **kw_args): + """Wrapper around eyesensor.view which takes samples""" + retval = self.sensor_compute(*args, **kw_args) + if self.schedule and self.age == self.schedule[-1]: + self.schedule.pop() + roi = self.sensor.make_roi_pretty(self.sensor.roi) + self.samples.append(roi) + self.age += 1 + return retval + + def view_samples(self, show=True): + """Displays the samples.""" + if not self.samples: + return # Nothing to show... + plt.figure("Sample views") + num = len(self.samples) + rows = math.floor(num ** .5) + cols = math.ceil(num / rows) + for idx, img in enumerate(self.samples): + plt.subplot(rows, cols, idx+1) + plt.imshow(img[:,:,::-1], interpolation='nearest') + if show: + plt.show() + + +# TODO: Consider splitting motor controls and motor sensory into different +# classes... +# +# +# EXPERIMENT: Try breaking out each output encoder by type instead of +# concatenating them all together. Each type of sensors would then get its own +# HTM. Maybe keep the derivatives with their source? +# +class EyeController: + """ + Motor controller for the EyeSensor class. + + The eye sensor has 4 degrees of freedom: X and Y location, scale, and + orientation. These values can be controlled by activating control vectors, + each of which has a small but cumulative effect. CV's are normally + distributed with a mean of zero. Activate control vectors by calling + controller.move(control-vectors). + + The controller outputs its current location, scale and orientation as well + as their first derivatives w/r/t time as an SDR. + """ + def __init__(self, eye_sensor, + # Control Vector Parameters + num_cv = 600, + pos_stddev = 1, + angle_stddev = math.pi / 8, + scale_stddev = 2, + # Motor Sensor Parameters + position_encoder = None, + velocity_encoder = None, + angle_encoder = None, + angular_velocity_encoder = None, + scale_encoder = None, + scale_velocity_encoder = None,): + """ + Argument num_cv is the approximate number of control vectors to use. + Arguments pos_stddev, angle_stddev, and scale_stddev are the standard + deviations of the control vector movements, control vectors + are normally distributed about a mean of 0. + + Arguments position_encoder, velocity_encoder, angle_encoder, + angular_velocity_encoder, scale_encoder, and + scale_velocity_encoder are instances of + RandomDistributedScalarEncoderParameters. + + Attribute control_sdr ... eye movement input controls + Attribute motor_sdr ... internal motor sensor output + + Attribute gaze is a list of tuples of (X, Y, Orientation, Scale) + History of recent movements, self.move() updates this. + This is cleared by the following methods: + self.new_image() + self.center_view() + self.randomize_view() + """ + assert(isinstance(parameters, EyeControllerParameters)) + assert(isinstance(eye_sensor, EyeSensor)) + self.args = args = parameters + self.eye_sensor = eye_sensor + self.control_vectors, self.control_sdr = self.make_control_vectors( + num_cv = args.num_cv, + pos_stddev = args.pos_stddev, + angle_stddev = args.angle_stddev, + scale_stddev = args.scale_stddev,) + + self.motor_position_encoder = RandomDistributedScalarEncoder(args.position_encoder) + self.motor_angle_encoder = RandomDistributedScalarEncoder(args.angle_encoder) + self.motor_scale_encoder = RandomDistributedScalarEncoder(args.scale_encoder) + self.motor_velocity_encoder = RandomDistributedScalarEncoder(args.velocity_encoder) + self.motor_angular_velocity_encoder = RandomDistributedScalarEncoder(args.angular_velocity_encoder) + self.motor_scale_velocity_encoder = RandomDistributedScalarEncoder(args.scale_velocity_encoder) + self.motor_encoders = [ self.motor_position_encoder, # X Posititon + self.motor_position_encoder, # Y Position + self.motor_angle_encoder, + self.motor_scale_encoder, + self.motor_velocity_encoder, # X Velocity + self.motor_velocity_encoder, # Y Velocity + self.motor_angular_velocity_encoder, + self.motor_scale_velocity_encoder,] + self.motor_sdr = SDR((sum(enc.output.size for enc in self.motor_encoders),)) + self.gaze = [] + + @staticmethod + def make_control_vectors(num_cv, pos_stddev, angle_stddev, scale_stddev): + """ + Argument num_cv is the approximate number of control vectors to create + Arguments pos_stddev, angle_stddev, and scale_stddev are the standard + deviations of the controls effects of position, angle, and + scale. + + Returns pair of control_vectors, control_sdr + + The control_vectors determines what happens for each output. Each + control is a 4-tuple of (X, Y, Angle, Scale) movements. To move, + active controls are summed and applied to the current location. + control_sdr contains the shape of the control_vectors. + """ + cv_sz = int(round(num_cv // 6)) + control_shape = (6*cv_sz,) + + pos_controls = [ + (random.gauss(0, pos_stddev), random.gauss(0, pos_stddev), 0, 0) + for i in range(4*cv_sz)] + + angle_controls = [ + (0, 0, random.gauss(0, angle_stddev), 0) + for angle_control in range(cv_sz)] + + scale_controls = [ + (0, 0, 0, random.gauss(0, scale_stddev)) + for scale_control in range(cv_sz)] + + control_vectors = pos_controls + angle_controls + scale_controls + random.shuffle(control_vectors) + control_vectors = np.array(control_vectors) + + # Add a little noise to all control vectors + control_vectors[:, 0] += np.random.normal(0, pos_stddev/10, control_shape) + control_vectors[:, 1] += np.random.normal(0, pos_stddev/10, control_shape) + control_vectors[:, 2] += np.random.normal(0, angle_stddev/10, control_shape) + control_vectors[:, 3] += np.random.normal(0, scale_stddev/10, control_shape) + return control_vectors, SDR(control_shape) + + def move(self, control_sdr=None, min_dist_from_edge=0): + """ + Apply the given controls to the current gaze location and updates the + motor sdr accordingly. + + Argument control_sdr is assigned into this classes attribute + self.control_sdr. It represents the control vectors to use. + The selected control vectors are summed and their effect is + applied to the eye's location. + + Returns an SDR encoded representation of the eyes new location and + velocity. + """ + self.control_sdr.assign(control_sdr) + eye = self.eye_sensor + # Calculate the forces on the motor + controls = self.control_vectors[self.control_sdr.index] + controls = np.sum(controls, axis=0) + dx, dy, dangle, dscale = controls + # Calculate the new rotation + eye.orientation = (eye.orientation + dangle) % (2*math.pi) + # Calculate the new scale + new_scale = np.clip(eye.scale + dscale, eye.args.min_scale, eye.args.max_scale) + real_ds = new_scale - eye.scale + avg_scale = (new_scale + eye.scale) / 2 + eye.scale = new_scale + # Scale the movement such that the same CV yields the same visual + # displacement, regardless of scale. + dx *= avg_scale + dy *= avg_scale + # Calculate the new position. + x, y = eye.position + p = [x + dx, y + dy] + edge = min_dist_from_edge + p = np.clip(p, [edge,edge], np.subtract(eye.image.shape[:2], edge)) + real_dp = np.subtract(p, eye.position) + eye.position = p + # Book keeping. + self.gaze.append(tuple(eye.position) + (eye.orientation, eye.scale)) + # Put together information about the motor. + velocity = ( + eye.position[0], + eye.position[1], + eye.orientation, + eye.scale, + real_dp[0], + real_dp[1], + dangle, + real_ds, + ) + # Encode the motors sensors and concatenate them into one big SDR. + v_enc = [enc.encode(v) for v, enc in zip(velocity, self.motor_encoders)] + self.motor_sdr.dense = np.concatenate([sdr.dense for sdr in v_enc]) + return self.motor_sdr + + def reset_gaze_tracking(self): + """ + Discard any prior gaze tracking. Call this after forcibly moving eye + to a new starting position. + """ + self.gaze = [( + self.eye_sensor.position[0], + self.eye_sensor.position[1], + self.eye_sensor.orientation, + self.eye_sensor.scale)] + + def gaze_tracking(self, diag=True): + """ + Returns vector of tuples of (position-x, position-y, orientation, scale) + """ + if diag: + im = PIL.Image.fromarray(self.eye_sensor.image) + draw = PIL.ImageDraw.Draw(im) + width, height = im.size + # Draw a red line through the centers of each gaze point + for p1, p2 in zip(self.gaze, self.gaze[1:]): + x1, y1, a1, s1 = p1 + x2, y2, a2, s2 = p2 + draw.line((y1, x1, y2, x2), fill='black', width=5) + draw.line((y1, x1, y2, x2), fill='red', width=2) + # Draw the bounding box of the eye sensor around each gaze point + for x, y, orientation, scale in self.gaze: + # Find the four corners of the eye's window + corners = [] + for ec_x, ec_y in [(0,0), (0,-1), (-1,-1), (-1,0)]: + corners.append(self.eye_sensor.eye_coords[:, ec_x, ec_y]) + # Convert from list of pairs to index array. + corners = np.transpose(corners) + # Rotate the corners + c = math.cos(orientation) + s = math.sin(orientation) + rot = np.array([[c, -s], [s, c]]) + corners = np.matmul(rot, corners) + # Scale/zoom the corners + corners *= scale + # Position the corners + corners += np.array([x, y]).reshape(2, 1) + # Convert from index array to list of coordinates pairs + corners = list(tuple(coord) for coord in np.transpose(corners)) + # Draw the points + for start, end in zip(corners, corners[1:] + [corners[0]]): + line_coords = (start[1], start[0], end[1], end[0],) + draw.line(line_coords, fill='green', width=2) + del draw + plt.figure("Gaze Tracking") + im = np.array(im) + plt.imshow(im, interpolation='nearest') + plt.show() + return self.gaze[:] + + +def small_random_movement(eye_sensor): + max_change_angle = (2*3.14159) / 500 + eye_sensor.position = ( + eye_sensor.position[0] + random.gauss(1, .75), + eye_sensor.position[1] + random.gauss(1, .75),) + eye_sensor.orientation += random.uniform(-max_change_angle, max_change_angle) + eye_sensor.scale = 1 + + +if __name__ == '__main__': + eye = Eye() + + import datasets, random + # data = datasets.Dataset('./datasets/small_items') + data = datasets.Dataset('./datasets/textures') + print("Num Images:", len(data)) + data.shuffle() + for z in range(len(data)): + eye.reset() + data.next_image() + img_path = data.current_image + print("Loading image %s"%img_path) + img = np.asarray(PIL.Image.open(img_path)) + eye.new_image(img) + eye.scale = 1 + + for i in range(10): + sdr = eye.compute() + eye.show_view() + small_random_movement(eye) + + print("All images seen.") From 914d46b175f36347b8113b8e3bfa51284a97f089 Mon Sep 17 00:00:00 2001 From: ctrl-z-9000-times Date: Fri, 27 Sep 2019 10:37:22 -0400 Subject: [PATCH 02/49] Eye/Retina Encoder - progress. --- py/htm/encoders/eye.py | 425 ++++++++++++++--------------------------- 1 file changed, 148 insertions(+), 277 deletions(-) diff --git a/py/htm/encoders/eye.py b/py/htm/encoders/eye.py index 6254da2937..df20be69c3 100644 --- a/py/htm/encoders/eye.py +++ b/py/htm/encoders/eye.py @@ -18,10 +18,96 @@ import math import scipy.ndimage import random -import PIL, PIL.ImageDraw -import matplotlib.pyplot as plt -from sdr import SDR -import encoders +import PIL +from htm.bindings.sdr import SDR + + +class ChannelEncoder: + """ + This assigns a random range to each bit of the output SDR. Each bit becomes + active if its corresponding input falls in its range. By using random + ranges, each bit represents a different thing even if it mostly overlaps + with other comparable bits. This way redundant bits add meaning. + """ + def __init__(self, input_shape, num_samples, sparsity, + dtype = np.float64, + drange = range(0,1), + wrap = False): + """ + Argument input_shape is tuple of dimensions for each input frame. + + Argument num_samples is number of bits in the output SDR which will + represent each input number, this is the added data depth. + + Argument sparsity is fraction of output which on average will be active. + This is also the fraction of the input spaces which (on + average) each bin covers. + + Argument dtype is numpy data type of channel. + + Argument drange is a range object or a pair of values representing the + range of possible channel values. + + Argument wrap ... default is False. + This supports modular input spaces and ranges which wrap + around. It does this by rotating the inputs by a constant + random amount which hides where the discontinuity in ranges is. + No ranges actually wrap around the input space. + """ + self.input_shape = tuple(input_shape) + self.num_samples = int(round(num_samples)) + self.sparsity = sparsity + self.output_shape = self.input_shape + (self.num_samples,) + self.dtype = dtype + self.drange = drange + self.len_drange = max(drange) - min(drange) + self.wrap = bool(wrap) + if self.wrap: + # Each bit responds to a range of input values, length of range is 2*Radius. + radius = self.len_drange * self.sparsity / 2 + self.offsets = np.random.uniform(0, self.len_drange, self.input_shape) + self.offsets = np.array(self.offsets, dtype=self.dtype) + # If wrapping is enabled then don't generate ranges which will be + # truncated near the edges. + centers = np.random.uniform(min(self.drange) + radius, + max(self.drange) - radius, + size=self.output_shape) + else: + # Buckets near the edges of the datarange are OK. They will not + # respond to a full range of input values but are needed to + # represent the bits at the edges of the data range. + # + # Expand the data range and create bits which will encode for the + # edges of the datarange to ensure a resonable sparsity at the + # extremes of the data ragne. Increase the size of all of the + # buckets to accomidate for the extra area being represented. + M = .95 # Maximum fraction of bucket-size outside of the true data range to allocate buckets. + len_pad_drange = self.len_drange / (1 - M * self.sparsity) + extra_space = (len_pad_drange - self.len_drange) / 2 + pad_drange = (min(self.drange) - extra_space, max(self.drange) + extra_space) + radius = len_pad_drange * self.sparsity / 2 + centers = np.random.uniform(min(pad_drange), + max(pad_drange), + size=self.output_shape) + # Make the lower and upper bounds of the ranges. + low = np.clip(centers - radius, min(self.drange), max(self.drange)) + high = np.clip(centers + radius, min(self.drange), max(self.drange)) + self.low = np.array(low, dtype=self.dtype) + self.high = np.array(high, dtype=self.dtype) + + def encode(self, img): + """Returns a dense boolean np.ndarray.""" + assert(img.shape == self.input_shape) + assert(img.dtype == self.dtype) + if self.wrap: + img += self.offsets + # Technically this should subtract min(drange) before doing modulus + # but the results should also be indistinguishable B/C of the random + # offsets. Min(drange) effectively becomes part of the offset. + img %= self.len_drange + img += min(self.drange) + img = img.reshape(img.shape + (1,)) + return np.logical_and(self.low <= img, img <= self.high) class Eye: @@ -69,12 +155,12 @@ def __init__(self, print(self.retina.printSetup()) print() - self.parvo_enc = encoders.ChannelEncoder( + self.parvo_enc = ChannelEncoder( input_shape = (output_diameter, output_diameter, 3,), num_samples = 1, sparsity = sparsity ** (1/3.), dtype=np.uint8, drange=[0, 255,]) - self.magno_enc = encoders.ChannelEncoder( + self.magno_enc = ChannelEncoder( input_shape = (output_diameter, output_diameter), num_samples = 1, sparsity = sparsity, dtype=np.uint8, drange=[0, 255],) @@ -111,7 +197,6 @@ def new_image(self, image): # Sanity checks. assert(len(self.image.shape) == 3) assert(self.image.shape[2] == 3) # Color images only. - self.reset() self.center_view() @@ -241,12 +326,6 @@ def make_roi_pretty(self, roi=None): return roi def show_view(self, window_name='Eye'): - if False: - print("Sparsity %g"%(len(self.output_sdr) / self.output_sdr.size)) - parvo = self.output_sdr.dense[:,:,0] - magno = self.output_sdr.dense[:,:,1] - print("Parvo Sparsity %g"%(np.count_nonzero(parvo) / np.product(parvo.shape))) - print("Magno Sparsity %g"%(np.count_nonzero(magno) / np.product(magno.shape))) roi = self.make_roi_pretty() cv2.imshow('Region Of Interest', roi) cv2.imshow('Parvocellular', self.parvo[:,:,::-1]) @@ -271,6 +350,14 @@ def input_space_sample_points(self, npoints): coords += np.array(np.rint(self.position), dtype=np.int).reshape(1, 2) return coords + def small_random_movement(self): + max_change_angle = (2*3.14159) / 500 + self.position = ( + self.position[0] + random.gauss(1, .75), + self.position[1] + random.gauss(1, .75),) + self.orientation += random.uniform(-max_change_angle, max_change_angle) + self.scale = 1 + def reset(self): self.retina.clearBuffers() @@ -309,6 +396,7 @@ def view_samples(self, show=True): """Displays the samples.""" if not self.samples: return # Nothing to show... + import matplotlib.pyplot as plt plt.figure("Sample views") num = len(self.samples) rows = math.floor(num ** .5) @@ -320,269 +408,52 @@ def view_samples(self, show=True): plt.show() -# TODO: Consider splitting motor controls and motor sensory into different -# classes... -# -# -# EXPERIMENT: Try breaking out each output encoder by type instead of -# concatenating them all together. Each type of sensors would then get its own -# HTM. Maybe keep the derivatives with their source? -# -class EyeController: - """ - Motor controller for the EyeSensor class. - - The eye sensor has 4 degrees of freedom: X and Y location, scale, and - orientation. These values can be controlled by activating control vectors, - each of which has a small but cumulative effect. CV's are normally - distributed with a mean of zero. Activate control vectors by calling - controller.move(control-vectors). - - The controller outputs its current location, scale and orientation as well - as their first derivatives w/r/t time as an SDR. - """ - def __init__(self, eye_sensor, - # Control Vector Parameters - num_cv = 600, - pos_stddev = 1, - angle_stddev = math.pi / 8, - scale_stddev = 2, - # Motor Sensor Parameters - position_encoder = None, - velocity_encoder = None, - angle_encoder = None, - angular_velocity_encoder = None, - scale_encoder = None, - scale_velocity_encoder = None,): - """ - Argument num_cv is the approximate number of control vectors to use. - Arguments pos_stddev, angle_stddev, and scale_stddev are the standard - deviations of the control vector movements, control vectors - are normally distributed about a mean of 0. - - Arguments position_encoder, velocity_encoder, angle_encoder, - angular_velocity_encoder, scale_encoder, and - scale_velocity_encoder are instances of - RandomDistributedScalarEncoderParameters. - - Attribute control_sdr ... eye movement input controls - Attribute motor_sdr ... internal motor sensor output - - Attribute gaze is a list of tuples of (X, Y, Orientation, Scale) - History of recent movements, self.move() updates this. - This is cleared by the following methods: - self.new_image() - self.center_view() - self.randomize_view() - """ - assert(isinstance(parameters, EyeControllerParameters)) - assert(isinstance(eye_sensor, EyeSensor)) - self.args = args = parameters - self.eye_sensor = eye_sensor - self.control_vectors, self.control_sdr = self.make_control_vectors( - num_cv = args.num_cv, - pos_stddev = args.pos_stddev, - angle_stddev = args.angle_stddev, - scale_stddev = args.scale_stddev,) - - self.motor_position_encoder = RandomDistributedScalarEncoder(args.position_encoder) - self.motor_angle_encoder = RandomDistributedScalarEncoder(args.angle_encoder) - self.motor_scale_encoder = RandomDistributedScalarEncoder(args.scale_encoder) - self.motor_velocity_encoder = RandomDistributedScalarEncoder(args.velocity_encoder) - self.motor_angular_velocity_encoder = RandomDistributedScalarEncoder(args.angular_velocity_encoder) - self.motor_scale_velocity_encoder = RandomDistributedScalarEncoder(args.scale_velocity_encoder) - self.motor_encoders = [ self.motor_position_encoder, # X Posititon - self.motor_position_encoder, # Y Position - self.motor_angle_encoder, - self.motor_scale_encoder, - self.motor_velocity_encoder, # X Velocity - self.motor_velocity_encoder, # Y Velocity - self.motor_angular_velocity_encoder, - self.motor_scale_velocity_encoder,] - self.motor_sdr = SDR((sum(enc.output.size for enc in self.motor_encoders),)) - self.gaze = [] - - @staticmethod - def make_control_vectors(num_cv, pos_stddev, angle_stddev, scale_stddev): - """ - Argument num_cv is the approximate number of control vectors to create - Arguments pos_stddev, angle_stddev, and scale_stddev are the standard - deviations of the controls effects of position, angle, and - scale. - - Returns pair of control_vectors, control_sdr - - The control_vectors determines what happens for each output. Each - control is a 4-tuple of (X, Y, Angle, Scale) movements. To move, - active controls are summed and applied to the current location. - control_sdr contains the shape of the control_vectors. - """ - cv_sz = int(round(num_cv // 6)) - control_shape = (6*cv_sz,) - - pos_controls = [ - (random.gauss(0, pos_stddev), random.gauss(0, pos_stddev), 0, 0) - for i in range(4*cv_sz)] - - angle_controls = [ - (0, 0, random.gauss(0, angle_stddev), 0) - for angle_control in range(cv_sz)] - - scale_controls = [ - (0, 0, 0, random.gauss(0, scale_stddev)) - for scale_control in range(cv_sz)] - - control_vectors = pos_controls + angle_controls + scale_controls - random.shuffle(control_vectors) - control_vectors = np.array(control_vectors) - - # Add a little noise to all control vectors - control_vectors[:, 0] += np.random.normal(0, pos_stddev/10, control_shape) - control_vectors[:, 1] += np.random.normal(0, pos_stddev/10, control_shape) - control_vectors[:, 2] += np.random.normal(0, angle_stddev/10, control_shape) - control_vectors[:, 3] += np.random.normal(0, scale_stddev/10, control_shape) - return control_vectors, SDR(control_shape) - - def move(self, control_sdr=None, min_dist_from_edge=0): - """ - Apply the given controls to the current gaze location and updates the - motor sdr accordingly. - - Argument control_sdr is assigned into this classes attribute - self.control_sdr. It represents the control vectors to use. - The selected control vectors are summed and their effect is - applied to the eye's location. - - Returns an SDR encoded representation of the eyes new location and - velocity. - """ - self.control_sdr.assign(control_sdr) - eye = self.eye_sensor - # Calculate the forces on the motor - controls = self.control_vectors[self.control_sdr.index] - controls = np.sum(controls, axis=0) - dx, dy, dangle, dscale = controls - # Calculate the new rotation - eye.orientation = (eye.orientation + dangle) % (2*math.pi) - # Calculate the new scale - new_scale = np.clip(eye.scale + dscale, eye.args.min_scale, eye.args.max_scale) - real_ds = new_scale - eye.scale - avg_scale = (new_scale + eye.scale) / 2 - eye.scale = new_scale - # Scale the movement such that the same CV yields the same visual - # displacement, regardless of scale. - dx *= avg_scale - dy *= avg_scale - # Calculate the new position. - x, y = eye.position - p = [x + dx, y + dy] - edge = min_dist_from_edge - p = np.clip(p, [edge,edge], np.subtract(eye.image.shape[:2], edge)) - real_dp = np.subtract(p, eye.position) - eye.position = p - # Book keeping. - self.gaze.append(tuple(eye.position) + (eye.orientation, eye.scale)) - # Put together information about the motor. - velocity = ( - eye.position[0], - eye.position[1], - eye.orientation, - eye.scale, - real_dp[0], - real_dp[1], - dangle, - real_ds, - ) - # Encode the motors sensors and concatenate them into one big SDR. - v_enc = [enc.encode(v) for v, enc in zip(velocity, self.motor_encoders)] - self.motor_sdr.dense = np.concatenate([sdr.dense for sdr in v_enc]) - return self.motor_sdr - - def reset_gaze_tracking(self): - """ - Discard any prior gaze tracking. Call this after forcibly moving eye - to a new starting position. - """ - self.gaze = [( - self.eye_sensor.position[0], - self.eye_sensor.position[1], - self.eye_sensor.orientation, - self.eye_sensor.scale)] - - def gaze_tracking(self, diag=True): - """ - Returns vector of tuples of (position-x, position-y, orientation, scale) - """ - if diag: - im = PIL.Image.fromarray(self.eye_sensor.image) - draw = PIL.ImageDraw.Draw(im) - width, height = im.size - # Draw a red line through the centers of each gaze point - for p1, p2 in zip(self.gaze, self.gaze[1:]): - x1, y1, a1, s1 = p1 - x2, y2, a2, s2 = p2 - draw.line((y1, x1, y2, x2), fill='black', width=5) - draw.line((y1, x1, y2, x2), fill='red', width=2) - # Draw the bounding box of the eye sensor around each gaze point - for x, y, orientation, scale in self.gaze: - # Find the four corners of the eye's window - corners = [] - for ec_x, ec_y in [(0,0), (0,-1), (-1,-1), (-1,0)]: - corners.append(self.eye_sensor.eye_coords[:, ec_x, ec_y]) - # Convert from list of pairs to index array. - corners = np.transpose(corners) - # Rotate the corners - c = math.cos(orientation) - s = math.sin(orientation) - rot = np.array([[c, -s], [s, c]]) - corners = np.matmul(rot, corners) - # Scale/zoom the corners - corners *= scale - # Position the corners - corners += np.array([x, y]).reshape(2, 1) - # Convert from index array to list of coordinates pairs - corners = list(tuple(coord) for coord in np.transpose(corners)) - # Draw the points - for start, end in zip(corners, corners[1:] + [corners[0]]): - line_coords = (start[1], start[0], end[1], end[0],) - draw.line(line_coords, fill='green', width=2) - del draw - plt.figure("Gaze Tracking") - im = np.array(im) - plt.imshow(im, interpolation='nearest') - plt.show() - return self.gaze[:] - - -def small_random_movement(eye_sensor): - max_change_angle = (2*3.14159) / 500 - eye_sensor.position = ( - eye_sensor.position[0] + random.gauss(1, .75), - eye_sensor.position[1] + random.gauss(1, .75),) - eye_sensor.orientation += random.uniform(-max_change_angle, max_change_angle) - eye_sensor.scale = 1 - +def _get_images(path): + """ Returns list of all image files found under the given file path. """ + image_extensions = [ + '.bmp', + '.dib', + '.png', + '.jpg', + '.jpeg', + '.jpe', + '.tif', + '.tiff', + ] + images = [] + import os + if os.path.isfile(path): + basename, ext = os.path.splitext(path) + if ext.lower() in image_extensions: + images.append( path ) + else: + for dirpath, dirnames, filenames in os.walk(path): + for fn in filenames: + basename, ext = os.path.splitext(fn) + if ext.lower() in image_extensions: + images.append( os.path.join(dirpath, fn) ) + return images if __name__ == '__main__': - eye = Eye() - - import datasets, random - # data = datasets.Dataset('./datasets/small_items') - data = datasets.Dataset('./datasets/textures') - print("Num Images:", len(data)) - data.shuffle() - for z in range(len(data)): - eye.reset() - data.next_image() - img_path = data.current_image - print("Loading image %s"%img_path) - img = np.asarray(PIL.Image.open(img_path)) - eye.new_image(img) - eye.scale = 1 - - for i in range(10): - sdr = eye.compute() - eye.show_view() - small_random_movement(eye) - - print("All images seen.") + import argparse + args = argparse.ArgumentParser() + args.add_argument('IMAGE', type=str) + args = args.parse_args() + images = _get_images( args.IMAGE ) + random.shuffle(images) + if not images: + print('No images found at file path "%s"!'%args.IMAGE) + else: + eye = Eye() + sampler = EyeSensorSampler(eye, 1000) + for img_path in images: + eye.reset() + print("Loading image %s"%img_path) + eye.new_image(img_path) + eye.scale = 1 + for i in range(10): + sdr = eye.compute() + eye.show_view() + eye.small_random_movement() + print("All images seen.") + sampler.view_samples() From 3edddadad6e0e339951b014e181111c7fdbbd06b Mon Sep 17 00:00:00 2001 From: Marek Otahal Date: Tue, 1 Oct 2019 22:07:21 +0200 Subject: [PATCH 03/49] Retina: fix imports some imports for Eye/Retina encoder needed fixing for newer versions --- bindings/py/packaging/setup.py | 5 +++-- py/htm/encoders/eye.py | 18 +++++++++--------- 2 files changed, 12 insertions(+), 11 deletions(-) diff --git a/bindings/py/packaging/setup.py b/bindings/py/packaging/setup.py index 51fa54bb75..ec5d28d126 100644 --- a/bindings/py/packaging/setup.py +++ b/bindings/py/packaging/setup.py @@ -398,8 +398,9 @@ def configure(platform, build_type): extras_require={'scikit-image>0.15.0':'examples', 'sklearn':'examples', 'matplotlib':'examples', - 'PIL':'examples', - 'scipy':'examples' + 'scipy':'examples', + 'opencv-contrib-python': 'examples', #for cv2.bioinspired for RetinaEncoder + 'Pillow': 'examples' }, zip_safe=False, cmdclass={ diff --git a/py/htm/encoders/eye.py b/py/htm/encoders/eye.py index df20be69c3..3bbe93de86 100644 --- a/py/htm/encoders/eye.py +++ b/py/htm/encoders/eye.py @@ -12,13 +12,13 @@ Fun Fact 2: The human eye can distiguish between 10 million different colors. Sources: Wikipedia. """ -import numpy as np -import cv2 -import scipy.misc import math -import scipy.ndimage import random -import PIL + +import numpy as np +import cv2 # pip install opencv-contrib-python +from PIL import Image # pip Pillow + from htm.bindings.sdr import SDR @@ -178,7 +178,7 @@ def new_image(self, image): # Load image if needed. if isinstance(image, str): self.image_file = image - self.image = np.array(PIL.Image.open(image), copy=False) + self.image = np.array(Image.open(image), copy=False) else: self.image_file = None self.image = image @@ -250,7 +250,7 @@ def _crop_roi(self): x_shape, y_shape, color_depth = image_slice.shape roi[x_offset:x_offset+x_shape, y_offset:y_offset+y_shape] = image_slice # Rescale the ROI to remove the scaling effect. - roi = scipy.misc.imresize(roi, (self.retina_diameter, self.retina_diameter)) + roi = np.array(Image.fromarray(roi).resize( (self.retina_diameter, self.retina_diameter))) return roi def compute(self): @@ -272,8 +272,8 @@ def compute(self): center = (center, center), M = M, flags = cv2.WARP_FILL_OUTLIERS) - parvo = scipy.misc.imresize(parvo, (self.output_diameter, self.output_diameter)) - magno = scipy.misc.imresize(magno, (self.output_diameter, self.output_diameter)) + parvo = np.array(Image.fromarray(parvo).resize( (self.output_diameter, self.output_diameter))) + magno = np.array(Image.fromarray(magno).resize( (self.output_diameter, self.output_diameter))) # Apply rotation by rolling the images around axis 1. rotation = self.output_diameter * self.orientation / (2 * math.pi) From ce1e68b67634603a7c300c82d4be241244eb5a10 Mon Sep 17 00:00:00 2001 From: Marek Otahal Date: Wed, 2 Oct 2019 11:26:48 +0200 Subject: [PATCH 04/49] Eye: improve documentation --- py/htm/encoders/eye.py | 37 +++++++++++++++++++++++-------------- 1 file changed, 23 insertions(+), 14 deletions(-) diff --git a/py/htm/encoders/eye.py b/py/htm/encoders/eye.py index 3bbe93de86..3856977843 100644 --- a/py/htm/encoders/eye.py +++ b/py/htm/encoders/eye.py @@ -1,6 +1,7 @@ # Written by David McDougall, 2018 -""" From Wikipedia "Retina": Although there are more than 130 million +""" +From Wikipedia "Retina": Although there are more than 130 million retinal receptors, there are only approximately 1.2 million fibres (axons) in the optic nerve; a large amount of pre-processing is performed within the retina. The fovea produces the most accurate @@ -8,9 +9,14 @@ than 2 of visual angle), about 10% of axons in the optic nerve are devoted to the fovea. +For more information on visual processing on retina, see +https://foundationsofvision.stanford.edu/chapter-5-the-retinal-representation/#visualinformation + + Fun Fact 1: The human optic nerve has 800,000 ~ 1,700,000 nerve fibers. Fun Fact 2: The human eye can distiguish between 10 million different colors. -Sources: Wikipedia. """ +~Sources: Wikipedia. +""" import math import random @@ -116,26 +122,27 @@ class Eye: Attribute output_sdr ... retina's output Attribute roi ... The most recent view, kept as a attribute. - Attribute parvo ... - Attribute magno ... + Attribute parvo ... SDR with parvocellular pathway (color) + Attribute magno ... SDR with magnocellular pathway (movement) The following three attributes control where the eye is looking within the image. They are Read/Writable. Attribute position (X, Y) coords of eye center within image - Attribute orientation ... units are radians - Attribute scale ... + Attribute orientation ... units are radians TODO of what? sensor/image + Attribute scale ... TODO """ def __init__(self, output_diameter = 200, - resolution_factor = 3, - fovea_scale = .177, - sparsity = .2,): + resolution_factor = 3, #TODO rm as arg? + fovea_scale = .177, #TODO rm as arg? + sparsity = .2,): #TODO add arg mode: parvo, magno, both """ - Argument output_diameter is size of output ... + Argument output_diameter is size of output ... output is a + field of view (image) with circular shape Argument resolution_factor is used to expand the sensor array so that the fovea has adequate resolution. After log-polar transform image is reduced by this factor back to the output_diameter. - Argument fovea_scale is magic number ... + Argument fovea_scale ... represents "zoom" aka distance from the object/image. Argument sparsity is fraction of bits in eye.output_sdr which are active, on average. """ @@ -149,7 +156,7 @@ def __init__(self, self.retina = cv2.bioinspired.Retina_create( inputSize = (self.retina_diameter, self.retina_diameter), - colorMode = True, + colorMode = True, #TODO make color optional, ie work on B/W images too colorSamplingMethod = cv2.bioinspired.RETINA_COLOR_BAYER,) print(self.retina.printSetup()) @@ -157,12 +164,14 @@ def __init__(self, self.parvo_enc = ChannelEncoder( input_shape = (output_diameter, output_diameter, 3,), - num_samples = 1, sparsity = sparsity ** (1/3.), + num_samples = 1, + sparsity = sparsity ** (1/3.), dtype=np.uint8, drange=[0, 255,]) self.magno_enc = ChannelEncoder( input_shape = (output_diameter, output_diameter), - num_samples = 1, sparsity = sparsity, + num_samples = 1, + sparsity = sparsity, dtype=np.uint8, drange=[0, 255],) self.image_file = None From 2eace9ed0ea84681b7755da11135a4c63b82131f Mon Sep 17 00:00:00 2001 From: Marek Otahal Date: Wed, 2 Oct 2019 11:38:20 +0200 Subject: [PATCH 05/49] Eye: remove resulution_factor as argument make it a const variable --- py/htm/encoders/eye.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/py/htm/encoders/eye.py b/py/htm/encoders/eye.py index 3856977843..ea776c0114 100644 --- a/py/htm/encoders/eye.py +++ b/py/htm/encoders/eye.py @@ -133,21 +133,21 @@ class Eye: """ def __init__(self, output_diameter = 200, - resolution_factor = 3, #TODO rm as arg? fovea_scale = .177, #TODO rm as arg? sparsity = .2,): #TODO add arg mode: parvo, magno, both """ Argument output_diameter is size of output ... output is a field of view (image) with circular shape - Argument resolution_factor is used to expand the sensor array so that - the fovea has adequate resolution. After log-polar transform image - is reduced by this factor back to the output_diameter. Argument fovea_scale ... represents "zoom" aka distance from the object/image. Argument sparsity is fraction of bits in eye.output_sdr which are active, on average. """ self.output_diameter = output_diameter - self.retina_diameter = int(resolution_factor * output_diameter) + # Argument resolution_factor is used to expand the sensor array so that + # the fovea has adequate resolution. After log-polar transform image + # is reduced by this factor back to the output_diameter. + self.resolution_factor = 3 + self.retina_diameter = int(self.resolution_factor * output_diameter) self.fovea_scale = fovea_scale assert(output_diameter // 2 * 2 == output_diameter) # Diameter must be an even number. assert(self.retina_diameter // 2 * 2 == self.retina_diameter) # (Resolution Factor X Diameter) must be an even number. From 692aec995a949b85fd06286114102061a9fa5c91 Mon Sep 17 00:00:00 2001 From: Marek Otahal Date: Wed, 2 Oct 2019 12:28:09 +0200 Subject: [PATCH 06/49] Eye: rm arg fovea_scale keep it only as member variable --- py/htm/encoders/eye.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/py/htm/encoders/eye.py b/py/htm/encoders/eye.py index ea776c0114..30c8dc2097 100644 --- a/py/htm/encoders/eye.py +++ b/py/htm/encoders/eye.py @@ -133,12 +133,10 @@ class Eye: """ def __init__(self, output_diameter = 200, - fovea_scale = .177, #TODO rm as arg? sparsity = .2,): #TODO add arg mode: parvo, magno, both """ Argument output_diameter is size of output ... output is a field of view (image) with circular shape - Argument fovea_scale ... represents "zoom" aka distance from the object/image. Argument sparsity is fraction of bits in eye.output_sdr which are active, on average. """ @@ -148,7 +146,8 @@ def __init__(self, # is reduced by this factor back to the output_diameter. self.resolution_factor = 3 self.retina_diameter = int(self.resolution_factor * output_diameter) - self.fovea_scale = fovea_scale + # Argument fovea_scale ... represents "zoom" aka distance from the object/image. + self.fovea_scale = 0.177 assert(output_diameter // 2 * 2 == output_diameter) # Diameter must be an even number. assert(self.retina_diameter // 2 * 2 == self.retina_diameter) # (Resolution Factor X Diameter) must be an even number. From b8038867327771f080384f804ccf7028ef0ec6f6 Mon Sep 17 00:00:00 2001 From: Marek Otahal Date: Wed, 2 Oct 2019 12:42:11 +0200 Subject: [PATCH 07/49] Eye: add argument mode: both/parvo/magno which retinal pathway to simulate --- py/htm/encoders/eye.py | 37 ++++++++++++++++++++++++++++--------- 1 file changed, 28 insertions(+), 9 deletions(-) diff --git a/py/htm/encoders/eye.py b/py/htm/encoders/eye.py index 30c8dc2097..dc121fca08 100644 --- a/py/htm/encoders/eye.py +++ b/py/htm/encoders/eye.py @@ -133,12 +133,15 @@ class Eye: """ def __init__(self, output_diameter = 200, - sparsity = .2,): #TODO add arg mode: parvo, magno, both + sparsity = .2, + mode = "both"): """ Argument output_diameter is size of output ... output is a field of view (image) with circular shape Argument sparsity is fraction of bits in eye.output_sdr which are active, on average. + Argument mode: one of "parvo", "magno", "both". Which retinal cells + to emulate. """ self.output_diameter = output_diameter # Argument resolution_factor is used to expand the sensor array so that @@ -150,6 +153,7 @@ def __init__(self, self.fovea_scale = 0.177 assert(output_diameter // 2 * 2 == output_diameter) # Diameter must be an even number. assert(self.retina_diameter // 2 * 2 == self.retina_diameter) # (Resolution Factor X Diameter) must be an even number. + assert(mode in ["magno", "parvo", "both"]) self.output_sdr = SDR((output_diameter, output_diameter, 2,)) @@ -161,21 +165,28 @@ def __init__(self, print(self.retina.printSetup()) print() - self.parvo_enc = ChannelEncoder( + if mode == "both" or mode == "parvo": + self.parvo_enc = ChannelEncoder( input_shape = (output_diameter, output_diameter, 3,), num_samples = 1, sparsity = sparsity ** (1/3.), dtype=np.uint8, drange=[0, 255,]) + else: + self.parvo_enc = None - self.magno_enc = ChannelEncoder( + if mode == "both" or mode == "magno": + self.magno_enc = ChannelEncoder( input_shape = (output_diameter, output_diameter), num_samples = 1, sparsity = sparsity, dtype=np.uint8, drange=[0, 255],) + else: + self.magno_enc = None self.image_file = None self.image = None + def new_image(self, image): """ Argument image ... @@ -290,12 +301,20 @@ def compute(self): self.magno = np.roll(magno, rotation, axis=0) # Encode images into SDRs. - p = self.parvo_enc.encode(self.parvo) - pr, pg, pb = np.dsplit(p, 3) - p = np.logical_and(np.logical_and(pr, pg), pb) - p = np.expand_dims(np.squeeze(p), axis=2) - m = self.magno_enc.encode(self.magno) - sdr = np.concatenate([p, m], axis=2) + p = [] + m = [] + if self.parvo_enc is not None: + p = self.parvo_enc.encode(self.parvo) + pr, pg, pb = np.dsplit(p, 3) + p = np.logical_and(np.logical_and(pr, pg), pb) + p = np.expand_dims(np.squeeze(p), axis=2) + sdr = p + if self.magno_enc is not None: + m = self.magno_enc.encode(self.magno) + sdr = m + if self.magno_enc is not None and self.parvo_enc is not None: + sdr = np.concatenate([p, m], axis=2) + self.output_sdr.dense = sdr return self.output_sdr From de65135bff13d1a51d348c483abee75b640f8ee3 Mon Sep 17 00:00:00 2001 From: Marek Otahal Date: Wed, 2 Oct 2019 12:44:35 +0200 Subject: [PATCH 08/49] Eye: fix parvo:magno cells ratio should be 1/3rd, not 3rd-root --- py/htm/encoders/eye.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/py/htm/encoders/eye.py b/py/htm/encoders/eye.py index dc121fca08..9637fd14c8 100644 --- a/py/htm/encoders/eye.py +++ b/py/htm/encoders/eye.py @@ -169,7 +169,7 @@ def __init__(self, self.parvo_enc = ChannelEncoder( input_shape = (output_diameter, output_diameter, 3,), num_samples = 1, - sparsity = sparsity ** (1/3.), + sparsity = sparsity * (1/3.), #biologically, parvocellular pathway is only 33% of the magnocellular (in terms of cells) dtype=np.uint8, drange=[0, 255,]) else: self.parvo_enc = None From e2014ea7e401ac7933456e820cffcb52da550ed2 Mon Sep 17 00:00:00 2001 From: Marek Otahal Date: Wed, 2 Oct 2019 13:19:21 +0200 Subject: [PATCH 09/49] Eye: color vs B/W mode --- py/htm/encoders/eye.py | 31 ++++++++++++++++++++++--------- 1 file changed, 22 insertions(+), 9 deletions(-) diff --git a/py/htm/encoders/eye.py b/py/htm/encoders/eye.py index 9637fd14c8..7009296d4e 100644 --- a/py/htm/encoders/eye.py +++ b/py/htm/encoders/eye.py @@ -134,14 +134,17 @@ class Eye: def __init__(self, output_diameter = 200, sparsity = .2, - mode = "both"): + mode = "both", + color = False,): """ Argument output_diameter is size of output ... output is a - field of view (image) with circular shape + field of view (image) with circular shape. Default 200 Argument sparsity is fraction of bits in eye.output_sdr which are - active, on average. + active, on average. Default 0.2 (=20%) Argument mode: one of "parvo", "magno", "both". Which retinal cells - to emulate. + to emulate. Default "both". + Argument color: True/False. Emulate color vision, or only B/W? + Default True. """ self.output_diameter = output_diameter # Argument resolution_factor is used to expand the sensor array so that @@ -154,20 +157,26 @@ def __init__(self, assert(output_diameter // 2 * 2 == output_diameter) # Diameter must be an even number. assert(self.retina_diameter // 2 * 2 == self.retina_diameter) # (Resolution Factor X Diameter) must be an even number. assert(mode in ["magno", "parvo", "both"]) + assert(color is False or color is True) + self.color = color self.output_sdr = SDR((output_diameter, output_diameter, 2,)) self.retina = cv2.bioinspired.Retina_create( inputSize = (self.retina_diameter, self.retina_diameter), - colorMode = True, #TODO make color optional, ie work on B/W images too + colorMode = color, colorSamplingMethod = cv2.bioinspired.RETINA_COLOR_BAYER,) print(self.retina.printSetup()) print() if mode == "both" or mode == "parvo": + dims = (output_diameter, output_diameter) + if color is True: + dims = (output_diameter, output_diameter, 3,) + self.parvo_enc = ChannelEncoder( - input_shape = (output_diameter, output_diameter, 3,), + input_shape = dims, num_samples = 1, sparsity = sparsity * (1/3.), #biologically, parvocellular pathway is only 33% of the magnocellular (in terms of cells) dtype=np.uint8, drange=[0, 255,]) @@ -305,8 +314,9 @@ def compute(self): m = [] if self.parvo_enc is not None: p = self.parvo_enc.encode(self.parvo) - pr, pg, pb = np.dsplit(p, 3) - p = np.logical_and(np.logical_and(pr, pg), pb) + if self.color: + pr, pg, pb = np.dsplit(p, 3) + p = np.logical_and(np.logical_and(pr, pg), pb) p = np.expand_dims(np.squeeze(p), axis=2) sdr = p if self.magno_enc is not None: @@ -355,7 +365,10 @@ def make_roi_pretty(self, roi=None): def show_view(self, window_name='Eye'): roi = self.make_roi_pretty() cv2.imshow('Region Of Interest', roi) - cv2.imshow('Parvocellular', self.parvo[:,:,::-1]) + if self.color: + cv2.imshow('Parvocellular', self.parvo[:,:,::-1]) + else: + cv2.imshow('Parvocellular', self.parvo) cv2.imshow('Magnocellular', self.magno) cv2.waitKey(1) From f259ec6bb3a6a4aea8bf1bb2a44756b4c2b5d81a Mon Sep 17 00:00:00 2001 From: Marek Otahal Date: Wed, 2 Oct 2019 13:46:33 +0200 Subject: [PATCH 10/49] Eye: argument plot=False let's you disable/enable plotting, useful for headless mode --- py/htm/encoders/eye.py | 23 ++++++++++++++++++----- 1 file changed, 18 insertions(+), 5 deletions(-) diff --git a/py/htm/encoders/eye.py b/py/htm/encoders/eye.py index 7009296d4e..6f27b1b669 100644 --- a/py/htm/encoders/eye.py +++ b/py/htm/encoders/eye.py @@ -135,7 +135,8 @@ def __init__(self, output_diameter = 200, sparsity = .2, mode = "both", - color = False,): + color = False, + plot = False,): """ Argument output_diameter is size of output ... output is a field of view (image) with circular shape. Default 200 @@ -145,6 +146,7 @@ def __init__(self, to emulate. Default "both". Argument color: True/False. Emulate color vision, or only B/W? Default True. + Argument plot: True/False. Whether to display plots, default False. """ self.output_diameter = output_diameter # Argument resolution_factor is used to expand the sensor array so that @@ -159,6 +161,8 @@ def __init__(self, assert(mode in ["magno", "parvo", "both"]) assert(color is False or color is True) self.color = color + assert(plot is False or plot is True) + self.plot = plot self.output_sdr = SDR((output_diameter, output_diameter, 2,)) @@ -362,7 +366,14 @@ def make_roi_pretty(self, roi=None): roi[center+2, center-2] = np.full(3, 255) - roi[center+2, center-2] return roi - def show_view(self, window_name='Eye'): + def show_view(self, window_name='Eye', delay=1): + """plot the retina's output SDRs. + Argument delay: ms to wait between saccadic movements. + Default 1ms. + """ + if self.plot is False: + return + roi = self.make_roi_pretty() cv2.imshow('Region Of Interest', roi) if self.color: @@ -370,7 +381,7 @@ def show_view(self, window_name='Eye'): else: cv2.imshow('Parvocellular', self.parvo) cv2.imshow('Magnocellular', self.magno) - cv2.waitKey(1) + cv2.waitKey(delay) def input_space_sample_points(self, npoints): """ @@ -390,7 +401,7 @@ def input_space_sample_points(self, npoints): coords += np.array(np.rint(self.position), dtype=np.int).reshape(1, 2) return coords - def small_random_movement(self): + def small_random_movement(self): #TODO pass as custom fn max_change_angle = (2*3.14159) / 500 self.position = ( self.position[0] + random.gauss(1, .75), @@ -436,6 +447,8 @@ def view_samples(self, show=True): """Displays the samples.""" if not self.samples: return # Nothing to show... + if show is False: + return import matplotlib.pyplot as plt plt.figure("Sample views") num = len(self.samples) @@ -484,7 +497,7 @@ def _get_images(path): if not images: print('No images found at file path "%s"!'%args.IMAGE) else: - eye = Eye() + eye = Eye(plot=True) sampler = EyeSensorSampler(eye, 1000) for img_path in images: eye.reset() From 57fd3b0a033361effbb3f4bb314609a6f6ccc67c Mon Sep 17 00:00:00 2001 From: Marek Otahal Date: Wed, 2 Oct 2019 13:53:00 +0200 Subject: [PATCH 11/49] Eye: remove image_file member self.image is used for both string/data functionality --- py/htm/encoders/eye.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/py/htm/encoders/eye.py b/py/htm/encoders/eye.py index 6f27b1b669..f0054c01cd 100644 --- a/py/htm/encoders/eye.py +++ b/py/htm/encoders/eye.py @@ -160,8 +160,10 @@ def __init__(self, assert(self.retina_diameter // 2 * 2 == self.retina_diameter) # (Resolution Factor X Diameter) must be an even number. assert(mode in ["magno", "parvo", "both"]) assert(color is False or color is True) + # color, or B/W vision self.color = color assert(plot is False or plot is True) + # plot images? self.plot = plot self.output_sdr = SDR((output_diameter, output_diameter, 2,)) @@ -196,7 +198,7 @@ def __init__(self, else: self.magno_enc = None - self.image_file = None + # the current input image self.image = None @@ -209,16 +211,13 @@ def new_image(self, image): """ # Load image if needed. if isinstance(image, str): - self.image_file = image self.image = np.array(Image.open(image), copy=False) else: - self.image_file = None self.image = image # Get the image into the right format. assert(isinstance(self.image, np.ndarray)) if self.image.dtype != np.uint8: raise TypeError('Image "%s" dtype is not unsigned 8 bit integer, image.dtype is %s.'%( - self.image_file if self.image_file is not None else 'argument', self.image.dtype)) # Ensure there are three color channels. if len(self.image.shape) == 2 or self.image.shape[2] == 1: @@ -332,6 +331,7 @@ def compute(self): self.output_sdr.dense = sdr return self.output_sdr + def make_roi_pretty(self, roi=None): """ Makes the eye's view look more presentable. From f014438a2e8e3ae2d97d00318b574e6c0a68706d Mon Sep 17 00:00:00 2001 From: Marek Otahal Date: Wed, 2 Oct 2019 13:57:06 +0200 Subject: [PATCH 12/49] Eye: remove EyeSensorSampler class as unneeded --- py/htm/encoders/eye.py | 49 ------------------------------------------ 1 file changed, 49 deletions(-) diff --git a/py/htm/encoders/eye.py b/py/htm/encoders/eye.py index f0054c01cd..9f460f407f 100644 --- a/py/htm/encoders/eye.py +++ b/py/htm/encoders/eye.py @@ -413,53 +413,6 @@ def reset(self): self.retina.clearBuffers() -class EyeSensorSampler: - """ - Samples eyesensor.rgb, the eye's view. - - Attribute samples is list of RGB numpy arrays. - """ - def __init__(self, eyesensor, sample_period, number_of_samples=30): - """ - This draws its samples directly from the output of eyesensor.view() by - wrapping the method. - """ - self.sensor = sensor = eyesensor - self.sensor_compute = sensor.compute - self.sensor.compute = self.compute - self.age = 0 - self.samples = [] - number_of_samples = min(number_of_samples, sample_period) # Don't die. - self.schedule = random.sample(range(sample_period), number_of_samples) - self.schedule.sort(reverse=True) - - def compute(self, *args, **kw_args): - """Wrapper around eyesensor.view which takes samples""" - retval = self.sensor_compute(*args, **kw_args) - if self.schedule and self.age == self.schedule[-1]: - self.schedule.pop() - roi = self.sensor.make_roi_pretty(self.sensor.roi) - self.samples.append(roi) - self.age += 1 - return retval - - def view_samples(self, show=True): - """Displays the samples.""" - if not self.samples: - return # Nothing to show... - if show is False: - return - import matplotlib.pyplot as plt - plt.figure("Sample views") - num = len(self.samples) - rows = math.floor(num ** .5) - cols = math.ceil(num / rows) - for idx, img in enumerate(self.samples): - plt.subplot(rows, cols, idx+1) - plt.imshow(img[:,:,::-1], interpolation='nearest') - if show: - plt.show() - def _get_images(path): """ Returns list of all image files found under the given file path. """ @@ -498,7 +451,6 @@ def _get_images(path): print('No images found at file path "%s"!'%args.IMAGE) else: eye = Eye(plot=True) - sampler = EyeSensorSampler(eye, 1000) for img_path in images: eye.reset() print("Loading image %s"%img_path) @@ -509,4 +461,3 @@ def _get_images(path): eye.show_view() eye.small_random_movement() print("All images seen.") - sampler.view_samples() From 45da86cc1f5e63349f736c55a1fef79991da04ff Mon Sep 17 00:00:00 2001 From: Marek Otahal Date: Wed, 2 Oct 2019 14:15:04 +0200 Subject: [PATCH 13/49] Eye: fix parvo/magno split mode --- py/htm/encoders/eye.py | 25 +++++++++++++++++-------- 1 file changed, 17 insertions(+), 8 deletions(-) diff --git a/py/htm/encoders/eye.py b/py/htm/encoders/eye.py index 9f460f407f..c318db46f2 100644 --- a/py/htm/encoders/eye.py +++ b/py/htm/encoders/eye.py @@ -257,6 +257,8 @@ def _crop_roi(self): Returns RGB image. """ + assert(self.image is not None) + r = int(round(self.scale * self.retina_diameter / 2)) x, y = self.position x = int(round(x)) @@ -289,28 +291,35 @@ def compute(self): # Retina image transforms (Parvo & Magnocellular). self.retina.run(self.roi) - parvo = self.retina.getParvo() - magno = self.retina.getMagno() + if self.parvo_enc is not None: + parvo = self.retina.getParvo() + if self.magno_enc is not None: + magno = self.retina.getMagno() # Log Polar Transform. center = self.retina_diameter / 2 M = self.retina_diameter * self.fovea_scale - parvo = cv2.logPolar(parvo, + if self.parvo_enc is not None: + parvo = cv2.logPolar(parvo, center = (center, center), M = M, flags = cv2.WARP_FILL_OUTLIERS) - magno = cv2.logPolar(magno, + parvo = np.array(Image.fromarray(parvo).resize( (self.output_diameter, self.output_diameter))) + + if self.magno_enc is not None: + magno = cv2.logPolar(magno, center = (center, center), M = M, flags = cv2.WARP_FILL_OUTLIERS) - parvo = np.array(Image.fromarray(parvo).resize( (self.output_diameter, self.output_diameter))) - magno = np.array(Image.fromarray(magno).resize( (self.output_diameter, self.output_diameter))) + magno = np.array(Image.fromarray(magno).resize( (self.output_diameter, self.output_diameter))) # Apply rotation by rolling the images around axis 1. rotation = self.output_diameter * self.orientation / (2 * math.pi) rotation = int(round(rotation)) - self.parvo = np.roll(parvo, rotation, axis=0) - self.magno = np.roll(magno, rotation, axis=0) + if self.parvo_enc is not None: + self.parvo = np.roll(parvo, rotation, axis=0) + if self.magno_enc is not None: + self.magno = np.roll(magno, rotation, axis=0) # Encode images into SDRs. p = [] From 97e6f57891d442318d5e90d612821547440f9dd1 Mon Sep 17 00:00:00 2001 From: Marek Otahal Date: Wed, 2 Oct 2019 14:17:01 +0200 Subject: [PATCH 14/49] Revert "Eye: argument plot=False" This reverts commit f259ec6bb3a6a4aea8bf1bb2a44756b4c2b5d81a. --- py/htm/encoders/eye.py | 22 +++++----------------- 1 file changed, 5 insertions(+), 17 deletions(-) diff --git a/py/htm/encoders/eye.py b/py/htm/encoders/eye.py index c318db46f2..4f267ad92f 100644 --- a/py/htm/encoders/eye.py +++ b/py/htm/encoders/eye.py @@ -135,8 +135,7 @@ def __init__(self, output_diameter = 200, sparsity = .2, mode = "both", - color = False, - plot = False,): + color = False,): """ Argument output_diameter is size of output ... output is a field of view (image) with circular shape. Default 200 @@ -146,7 +145,6 @@ def __init__(self, to emulate. Default "both". Argument color: True/False. Emulate color vision, or only B/W? Default True. - Argument plot: True/False. Whether to display plots, default False. """ self.output_diameter = output_diameter # Argument resolution_factor is used to expand the sensor array so that @@ -162,9 +160,6 @@ def __init__(self, assert(color is False or color is True) # color, or B/W vision self.color = color - assert(plot is False or plot is True) - # plot images? - self.plot = plot self.output_sdr = SDR((output_diameter, output_diameter, 2,)) @@ -375,14 +370,7 @@ def make_roi_pretty(self, roi=None): roi[center+2, center-2] = np.full(3, 255) - roi[center+2, center-2] return roi - def show_view(self, window_name='Eye', delay=1): - """plot the retina's output SDRs. - Argument delay: ms to wait between saccadic movements. - Default 1ms. - """ - if self.plot is False: - return - + def show_view(self, window_name='Eye'): roi = self.make_roi_pretty() cv2.imshow('Region Of Interest', roi) if self.color: @@ -390,7 +378,7 @@ def show_view(self, window_name='Eye', delay=1): else: cv2.imshow('Parvocellular', self.parvo) cv2.imshow('Magnocellular', self.magno) - cv2.waitKey(delay) + cv2.waitKey(1) def input_space_sample_points(self, npoints): """ @@ -410,7 +398,7 @@ def input_space_sample_points(self, npoints): coords += np.array(np.rint(self.position), dtype=np.int).reshape(1, 2) return coords - def small_random_movement(self): #TODO pass as custom fn + def small_random_movement(self): max_change_angle = (2*3.14159) / 500 self.position = ( self.position[0] + random.gauss(1, .75), @@ -459,7 +447,7 @@ def _get_images(path): if not images: print('No images found at file path "%s"!'%args.IMAGE) else: - eye = Eye(plot=True) + eye = Eye() for img_path in images: eye.reset() print("Loading image %s"%img_path) From 2cc80e168ca091ff66f6ab5d2897d1d9401cf201 Mon Sep 17 00:00:00 2001 From: Marek Otahal Date: Wed, 2 Oct 2019 14:28:25 +0200 Subject: [PATCH 15/49] Eye: comments --- py/htm/encoders/eye.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/py/htm/encoders/eye.py b/py/htm/encoders/eye.py index 4f267ad92f..800acfe17d 100644 --- a/py/htm/encoders/eye.py +++ b/py/htm/encoders/eye.py @@ -132,10 +132,10 @@ class Eye: Attribute scale ... TODO """ def __init__(self, - output_diameter = 200, - sparsity = .2, + output_diameter = 200, #TODO set as percentage of imput image? + sparsity = .2, #TODO what is biological sparsity on retina? mode = "both", - color = False,): + color = True,): """ Argument output_diameter is size of output ... output is a field of view (image) with circular shape. Default 200 @@ -454,7 +454,8 @@ def _get_images(path): eye.new_image(img_path) eye.scale = 1 for i in range(10): - sdr = eye.compute() + sdr = eye.compute() #TODO derive from Encoder eye.show_view() - eye.small_random_movement() +# print(sdr) #FIXME the resulting SDR is extremely dense + eye.small_random_movement() #TODO make the positions&orientation args of encode() print("All images seen.") From 7ae83bf3a2ba1aeabc60b5cd1db30c5948c8fb0a Mon Sep 17 00:00:00 2001 From: Marek Otahal Date: Wed, 2 Oct 2019 14:44:22 +0200 Subject: [PATCH 16/49] Eye: compute accepts pos,rot,scale args --- py/htm/encoders/eye.py | 23 ++++++++++++++++++++--- 1 file changed, 20 insertions(+), 3 deletions(-) diff --git a/py/htm/encoders/eye.py b/py/htm/encoders/eye.py index 800acfe17d..a081e8b7c3 100644 --- a/py/htm/encoders/eye.py +++ b/py/htm/encoders/eye.py @@ -281,7 +281,19 @@ def _crop_roi(self): roi = np.array(Image.fromarray(roi).resize( (self.retina_diameter, self.retina_diameter))) return roi - def compute(self): + def compute(self, position=None, rotation=None, scale=None): + """ + Arguments position, rotation, scale: optional, if not None, the self.xxx is overriden + with the provided value. + """ + # set position + if position is not None: + self.position = pos + if rotation is not None: + self.orientation=rotation + if scale is not None: + self.scale=scale + self.roi = self._crop_roi() # Retina image transforms (Parvo & Magnocellular). @@ -399,12 +411,17 @@ def input_space_sample_points(self, npoints): return coords def small_random_movement(self): + """returns small difference in position, rotation, scale. + This is naive "saccadic" movements. + """ max_change_angle = (2*3.14159) / 500 self.position = ( self.position[0] + random.gauss(1, .75), self.position[1] + random.gauss(1, .75),) self.orientation += random.uniform(-max_change_angle, max_change_angle) self.scale = 1 + return (self.position, self.orientation, self.scale) + def reset(self): self.retina.clearBuffers() @@ -454,8 +471,8 @@ def _get_images(path): eye.new_image(img_path) eye.scale = 1 for i in range(10): - sdr = eye.compute() #TODO derive from Encoder + pos,rot,sc = eye.small_random_movement() + sdr = eye.compute(pos,rot,sc) #TODO derive from Encoder eye.show_view() # print(sdr) #FIXME the resulting SDR is extremely dense - eye.small_random_movement() #TODO make the positions&orientation args of encode() print("All images seen.") From 0a908cf72f2f68313bbb6195062427a237f08fcf Mon Sep 17 00:00:00 2001 From: Marek Otahal Date: Wed, 2 Oct 2019 17:38:58 +0200 Subject: [PATCH 17/49] Eye: comments --- py/htm/encoders/eye.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/py/htm/encoders/eye.py b/py/htm/encoders/eye.py index a081e8b7c3..0c4e6e0278 100644 --- a/py/htm/encoders/eye.py +++ b/py/htm/encoders/eye.py @@ -389,8 +389,8 @@ def show_view(self, window_name='Eye'): cv2.imshow('Parvocellular', self.parvo[:,:,::-1]) else: cv2.imshow('Parvocellular', self.parvo) - cv2.imshow('Magnocellular', self.magno) - cv2.waitKey(1) + cv2.imshow('Magnocellular', self.magno) #TODO also plot the output SDR + cv2.waitKey(1000) def input_space_sample_points(self, npoints): """ @@ -474,5 +474,6 @@ def _get_images(path): pos,rot,sc = eye.small_random_movement() sdr = eye.compute(pos,rot,sc) #TODO derive from Encoder eye.show_view() -# print(sdr) #FIXME the resulting SDR is extremely dense + print("Sparsity: {}".format(len(sdr.sparse)/np.product(sdr.dimensions))) + print(sdr.dimensions) #TODO make SDR 2D, diameter x diameter. not current (200, 200, 2) print("All images seen.") From 99592e91d4fa88ccd2243cb2e3d935f942771e2f Mon Sep 17 00:00:00 2001 From: Marek Otahal Date: Thu, 3 Oct 2019 11:05:46 +0200 Subject: [PATCH 18/49] Eye: improve doc --- py/htm/encoders/eye.py | 40 ++++++++++++++++++++++++++++++++++++---- 1 file changed, 36 insertions(+), 4 deletions(-) diff --git a/py/htm/encoders/eye.py b/py/htm/encoders/eye.py index 0c4e6e0278..8148eed113 100644 --- a/py/htm/encoders/eye.py +++ b/py/htm/encoders/eye.py @@ -119,17 +119,49 @@ def encode(self, img): class Eye: """ Optic sensor with central fovae. - - Attribute output_sdr ... retina's output + Simulates functionality of eye's retinal parvocellular(P-cells), + and magnocellular(M-cells) pathways, at the saccadic steps. + + On high level, + magno cells: + - detect change in temporal information in the image, ie motion + detection, video processing tasks, ... + parvo cells: + - detect color, shape information in the (static) image. Useful + for image classification, etc. + For more details see: + https://foundationsofvision.stanford.edu/chapter-5-the-retinal-representation/#visualinformation + + #TODO the motion-control of "where to look at" is not fully researched + and covered by this code. You need to manually control the positions + of the eye/sensor (where it looks at) at the level of saccades. + + + Attribute output_sdr ... retina's output SDR + dimensions are (width, height, 2). The 3rd dimension is for + P,M-cells, which are likely processed separately in the thalamus. Attribute roi ... The most recent view, kept as a attribute. Attribute parvo ... SDR with parvocellular pathway (color) Attribute magno ... SDR with magnocellular pathway (movement) The following three attributes control where the eye is looking within the image. They are Read/Writable. + Note: (X,Y,scale,rotation) require manual control by the user, as in the brain + this is part of the Motor-Control, which is not in the scope of this encoder. + Attribute position (X, Y) coords of eye center within image - Attribute orientation ... units are radians TODO of what? sensor/image - Attribute scale ... TODO + (wrt [0,0] corner of the image). Default starting position is the center + of the image. + Attribute orientation ... units are radians, the rotation of the sensor + (wrt the image) + Attribute scale ... The scale controls the distance between the eye + and the image (scale is similar to distance on Z-axis). + Note: In experiments you typically need to manually tune the `scale` for the dataset, + to find a reasonable value through trial and error. + More explanation: "When you look at a really big image, you need to take + a few steps back to be able to see the whole thing. The scale parameter + allows the eye to do just that, walk forwards and backwards from the image, + to find good place to view it from." """ def __init__(self, output_diameter = 200, #TODO set as percentage of imput image? From 61808fa96335a761bdcf585d7903cb3dc7eb8316 Mon Sep 17 00:00:00 2001 From: Marek Otahal Date: Thu, 3 Oct 2019 11:10:24 +0200 Subject: [PATCH 19/49] Eye: fix sparsity of 3D parvo cells --- py/htm/encoders/eye.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/py/htm/encoders/eye.py b/py/htm/encoders/eye.py index 8148eed113..b503b99ef7 100644 --- a/py/htm/encoders/eye.py +++ b/py/htm/encoders/eye.py @@ -205,13 +205,21 @@ def __init__(self, if mode == "both" or mode == "parvo": dims = (output_diameter, output_diameter) + sparsityP = sparsity if color is True: dims = (output_diameter, output_diameter, 3,) + # The reason the parvo-cellular has `3rd-root of the sparsity` is that there are three color channels (RGB), + # each of which is encoded separately and then combined. The color channels are combined with a logical AND, + # which on average reduces the sparsity. + # This same principal applies to any time you combine two encoders with a logical bit-wise AND, the final combined + # sparsity would be `sparsity^n`, hence the cubic root for 3 dims: + sparsityP = sparsityP ** (1/3.) + self.parvo_enc = ChannelEncoder( input_shape = dims, num_samples = 1, - sparsity = sparsity * (1/3.), #biologically, parvocellular pathway is only 33% of the magnocellular (in terms of cells) + sparsity = sparsityP, #TODO biologically, parvocellular pathway is only 33% of the magnocellular (in terms of cells) dtype=np.uint8, drange=[0, 255,]) else: self.parvo_enc = None From 60c607ca6f7e8503ad1739ef7308e104a1830fc1 Mon Sep 17 00:00:00 2001 From: Marek Otahal Date: Thu, 3 Oct 2019 11:21:59 +0200 Subject: [PATCH 20/49] doc --- py/htm/encoders/eye.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/py/htm/encoders/eye.py b/py/htm/encoders/eye.py index b503b99ef7..47c7064d9a 100644 --- a/py/htm/encoders/eye.py +++ b/py/htm/encoders/eye.py @@ -170,10 +170,11 @@ def __init__(self, color = True,): """ Argument output_diameter is size of output ... output is a - field of view (image) with circular shape. Default 200 + field of view (image) with circular shape. Default 200. + `output_sdr` size is `output_diameter^2` Argument sparsity is fraction of bits in eye.output_sdr which are active, on average. Default 0.2 (=20%) - Argument mode: one of "parvo", "magno", "both". Which retinal cells + Argument mode: one of "parvo", "magno", "both". Which retinal cells #TODO replace mode with p_m_ratio? to emulate. Default "both". Argument color: True/False. Emulate color vision, or only B/W? Default True. From 69f8d93dd932dc876b8b329990741936133e54e8 Mon Sep 17 00:00:00 2001 From: Marek Otahal Date: Thu, 3 Oct 2019 12:44:28 +0200 Subject: [PATCH 21/49] Eye: self.magno_sdr, parvo_sdr separate magno,parvo_img --- py/htm/encoders/eye.py | 23 ++++++++++++++--------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/py/htm/encoders/eye.py b/py/htm/encoders/eye.py index 47c7064d9a..37acfb2c41 100644 --- a/py/htm/encoders/eye.py +++ b/py/htm/encoders/eye.py @@ -195,6 +195,8 @@ def __init__(self, self.color = color self.output_sdr = SDR((output_diameter, output_diameter, 2,)) + self.parvo_sdr = SDR((output_diameter, output_diameter,)) + self.magno_sdr = SDR((output_diameter, output_diameter,)) self.retina = cv2.bioinspired.Retina_create( inputSize = (self.retina_diameter, self.retina_diameter), @@ -365,27 +367,30 @@ def compute(self, position=None, rotation=None, scale=None): rotation = self.output_diameter * self.orientation / (2 * math.pi) rotation = int(round(rotation)) if self.parvo_enc is not None: - self.parvo = np.roll(parvo, rotation, axis=0) + self.parvo_img = np.roll(parvo, rotation, axis=0) if self.magno_enc is not None: - self.magno = np.roll(magno, rotation, axis=0) + self.magno_img = np.roll(magno, rotation, axis=0) # Encode images into SDRs. p = [] m = [] if self.parvo_enc is not None: - p = self.parvo_enc.encode(self.parvo) + p = self.parvo_enc.encode(parvo) if self.color: pr, pg, pb = np.dsplit(p, 3) p = np.logical_and(np.logical_and(pr, pg), pb) p = np.expand_dims(np.squeeze(p), axis=2) sdr = p if self.magno_enc is not None: - m = self.magno_enc.encode(self.magno) + m = self.magno_enc.encode(magno) sdr = m if self.magno_enc is not None and self.parvo_enc is not None: sdr = np.concatenate([p, m], axis=2) self.output_sdr.dense = sdr + self.magno_sdr.dense = m.flatten() + self.parvo_sdr.dense = p.flatten() + return self.output_sdr @@ -427,10 +432,10 @@ def show_view(self, window_name='Eye'): roi = self.make_roi_pretty() cv2.imshow('Region Of Interest', roi) if self.color: - cv2.imshow('Parvocellular', self.parvo[:,:,::-1]) + cv2.imshow('Parvocellular', self.parvo_img[:,:,::-1]) else: - cv2.imshow('Parvocellular', self.parvo) - cv2.imshow('Magnocellular', self.magno) #TODO also plot the output SDR + cv2.imshow('Parvocellular', self.parvo_img) + cv2.imshow('Magnocellular', self.magno_img) #TODO also plot the output SDR cv2.waitKey(1000) def input_space_sample_points(self, npoints): @@ -515,6 +520,6 @@ def _get_images(path): pos,rot,sc = eye.small_random_movement() sdr = eye.compute(pos,rot,sc) #TODO derive from Encoder eye.show_view() - print("Sparsity: {}".format(len(sdr.sparse)/np.product(sdr.dimensions))) - print(sdr.dimensions) #TODO make SDR 2D, diameter x diameter. not current (200, 200, 2) + print("Sparsity parvo: {}".format(len(eye.parvo_sdr.sparse)/np.product(eye.parvo_sdr.dimensions))) + print("Sparsity magno: {}".format(len(eye.magno_sdr.sparse)/np.product(eye.magno_sdr.dimensions))) print("All images seen.") From ef8d0d6603496d3197f841eaf9fda9bfa6a6fca9 Mon Sep 17 00:00:00 2001 From: Marek Otahal Date: Thu, 3 Oct 2019 13:26:14 +0200 Subject: [PATCH 22/49] Eye: plot parvo/magno SDR + print sparsity --- py/htm/encoders/eye.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/py/htm/encoders/eye.py b/py/htm/encoders/eye.py index 37acfb2c41..b63b9745de 100644 --- a/py/htm/encoders/eye.py +++ b/py/htm/encoders/eye.py @@ -435,7 +435,11 @@ def show_view(self, window_name='Eye'): cv2.imshow('Parvocellular', self.parvo_img[:,:,::-1]) else: cv2.imshow('Parvocellular', self.parvo_img) - cv2.imshow('Magnocellular', self.magno_img) #TODO also plot the output SDR + cv2.imshow('Magnocellular', self.magno_img) + idx = self.parvo_sdr.dense.astype(np.uint8).reshape(self.output_diameter, self.output_diameter)*255 + cv2.imshow('Parvo SDR', idx) + idx = self.magno_sdr.dense.astype(np.uint8).reshape(self.output_diameter, self.output_diameter)*255 + cv2.imshow('Magno SDR', idx) cv2.waitKey(1000) def input_space_sample_points(self, npoints): @@ -520,6 +524,6 @@ def _get_images(path): pos,rot,sc = eye.small_random_movement() sdr = eye.compute(pos,rot,sc) #TODO derive from Encoder eye.show_view() - print("Sparsity parvo: {}".format(len(eye.parvo_sdr.sparse)/np.product(eye.parvo_sdr.dimensions))) - print("Sparsity magno: {}".format(len(eye.magno_sdr.sparse)/np.product(eye.magno_sdr.dimensions))) + print("Sparsity parvo: {}".format(len(eye.parvo_sdr.sparse)/np.product(eye.parvo_sdr.dimensions))) + print("Sparsity magno: {}".format(len(eye.magno_sdr.sparse)/np.product(eye.magno_sdr.dimensions))) print("All images seen.") From 6cb832ccb8117c7cc9809aa16c315f62cee30cd4 Mon Sep 17 00:00:00 2001 From: Marek Otahal Date: Mon, 21 Oct 2019 22:28:23 +0200 Subject: [PATCH 23/49] Retina encoder: split to sparsityParvo,Magno make more asserts, --- py/htm/encoders/eye.py | 51 ++++++++++++++++++++++++++++-------------- 1 file changed, 34 insertions(+), 17 deletions(-) diff --git a/py/htm/encoders/eye.py b/py/htm/encoders/eye.py index 7064bc62ab..e3684e5a72 100644 --- a/py/htm/encoders/eye.py +++ b/py/htm/encoders/eye.py @@ -163,21 +163,32 @@ class Eye: allows the eye to do just that, walk forwards and backwards from the image, to find good place to view it from." """ + + def __init__(self, output_diameter = 200, # output_sdr size is diameter^2 - sparsity = .2, #TODO what is biological sparsity on retina? - mode = "both", + sparsityParvo = 0.2, + sparsityMagno = 0.025, color = True,): """ Argument output_diameter is size of output ... output is a field of view (image) with circular shape. Default 200. `output_sdr` size is `output_diameter^2` - Argument sparsity is fraction of bits in eye.output_sdr which are - active, on average. Default 0.2 (=20%) - Argument mode: one of "parvo", "magno", "both". Which retinal cells #TODO replace mode with p_m_ratio? - to emulate. Default "both". - Argument color: True/False. Emulate color vision, or only B/W? - Default True. + Argument `sparsityParvo` - sparsity of parvo-cellular pathway of the eye. + As a simplification, "parvo" cells (P-cells) represent colors, static + object's properties (shape,...) and are used for image classification. + For biologically accurate details see eg. + https://foundationsofvision.stanford.edu/chapter-5-the-retinal-representation/ + The sparsity of the retinal output is the sum of sparsityParvo + sparsityMagno, + which is represented in `Eye.sparsity` and represents sparsity of the output SDR. + Note: biologically, the ratio between P/M-cells is about 8(P):1(M):(1 rest), see + https://www.pnas.org/content/94/11/5900 + Argument `sparsityMagno` - sparsity of the magno-cellular (M-cells) pathway, which + transfers (higly) temporal information in the visual data, as use "used" for + motion detection and motion tracking, video processing. + For details see @param `sparsityParvo`. + TODO: output of M-cells should be processed on a fast TM. + Argument color: use color vision (requires P-cells > 0), default true. """ self.output_diameter = output_diameter # Argument resolution_factor is used to expand the sensor array so that @@ -189,9 +200,14 @@ def __init__(self, self.fovea_scale = 0.177 assert(output_diameter // 2 * 2 == output_diameter) # Diameter must be an even number. assert(self.retina_diameter // 2 * 2 == self.retina_diameter) # (Resolution Factor X Diameter) must be an even number. - assert(mode in ["magno", "parvo", "both"]) - assert(color is False or color is True) - # color, or B/W vision + assert(sparsityParvo >= 0 and sparsityParvo <= 1.0) + self.sparsityParvo = sparsityParvo + assert(sparsityMagno >= 0 and sparsityMagno <= 1.0) + self.sparsityMagno = sparsityMagno + self.sparsity = sparsityParvo + sparsityMagno + assert(self.sparsity > 0 and self.sparsity <= 1.0) + if color is True: + assert(sparsityParvo > 0) self.color = color self.output_sdr = SDR((output_diameter, output_diameter, 2,)) @@ -206,9 +222,10 @@ def __init__(self, print(self.retina.printSetup()) print() - if mode == "both" or mode == "parvo": + if sparsityParvo > 0: dims = (output_diameter, output_diameter) - sparsityP = sparsity + + sparsityP_ = sparsityParvo if color is True: dims = (output_diameter, output_diameter, 3,) @@ -217,21 +234,21 @@ def __init__(self, # which on average reduces the sparsity. # This same principal applies to any time you combine two encoders with a logical bit-wise AND, the final combined # sparsity would be `sparsity^n`, hence the cubic root for 3 dims: - sparsityP = sparsityP ** (1/3.) + sparsityP_ = sparsityParvo ** (1/3.) self.parvo_enc = ChannelEncoder( input_shape = dims, num_samples = 1, - sparsity = sparsityP, #TODO biologically, parvocellular pathway is only 33% of the magnocellular (in terms of cells) + sparsity = sparsityP_, dtype=np.uint8, drange=[0, 255,]) else: self.parvo_enc = None - if mode == "both" or mode == "magno": + if sparsityMagno > 0: self.magno_enc = ChannelEncoder( input_shape = (output_diameter, output_diameter), num_samples = 1, - sparsity = sparsity, + sparsity = sparsityMagno, dtype=np.uint8, drange=[0, 255],) else: self.magno_enc = None From 06a100d8e7cca30abfa52b0dd32c19c5bbf5fcf3 Mon Sep 17 00:00:00 2001 From: Marek Otahal Date: Mon, 21 Oct 2019 22:47:31 +0200 Subject: [PATCH 24/49] Doc for Channel encoder --- py/htm/encoders/eye.py | 56 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 56 insertions(+) diff --git a/py/htm/encoders/eye.py b/py/htm/encoders/eye.py index e3684e5a72..efa51f2612 100644 --- a/py/htm/encoders/eye.py +++ b/py/htm/encoders/eye.py @@ -34,7 +34,63 @@ class ChannelEncoder: active if its corresponding input falls in its range. By using random ranges, each bit represents a different thing even if it mostly overlaps with other comparable bits. This way redundant bits add meaning. + + Further explanation for this encoder (which is currently only used by + Retina): + + Two requirements for encoding SDRs are that every bit of information represents + a range of possible values, and that for every input multiple bits activate in the SDR. + The effects of these requirements are that each output bit is inaccurate and redundant. + This encoder makes every output bit receptive to a unique range of inputs, + which are uniformly distributed throughout the input space. This meets the requirements + for being an SDR and has more representational power than if many of the bits + represented the identical input ranges. + This design makes all of those redundancies add useful information. + + 1. Semantic similarity happens when two inputs which are similar have similar SDR representations. + This encoder design does two things to cause semantic similarity: + (1) SDR bits are responsive to a range of input values, + and (2) topology allows near by bits to represent similar things. + + Many encoders apply thresholds to real valued input data to convert the input + into Boolean outputs. In this encoder uses two thresholds to form ranges which + are referred to as ‘bins’. A small change in the input value might cause some + of the output bits to change and a large change in input value will cause all + of the output bits to change. How sensitive the output bits are to changes + in the input value -the semantic similarity- is determined by the sizes of the bins. + The sizes of the bins are in turn determined by the sparsity, as follows: + - Assume that the inputs are distributed in a uniform random way throughout the input range. + - The size of the bins then determines the probability that an input value will fall inside of a bin. + This means that the sparsity is related to the size of the bins, + which in turn means that the sparsity is related to the amount of semantic similarity. + This may seem counter-intuitive but this same property holds true for all + encoders which use bins to convert real numbers into discrete bits. + + 2. This encoder relies on topology in the input image, + the idea that adjacent pixels in the input image are likely to show the same thing. + If an area of the output SDR can not represent a color because it did not generate + the bins needed to, then it may still be near to a pixel which does represent the color. + In the case where there are less than one active output bits per input pixel, + multiple close together outputs can work together to represent the input. + Also, if an output bit changes in response to a small change in input, + then some semantic similarity is lost. + Topology allows nearby outputs to represent the same thing, + and since each output uses random bins they will not all change when + the input reaches a single threshold. + + Note about sparsity and color channels: + To encode color images create separate encoders for each color channel. + Then recombine the output SDRs into a single monolithic SDR by multiplying them together. + Multiplication is equivalent to logical “and” in this situation. + Notice that the combined SDR’s sparsity is the different; the fraction of bits which + are active in the combined SDR is the product of the fraction of the bits + which are active in all input SDRs. + For example, to make an encoder with 8 bits per pixel and a sparsity of 1/8: + create three encoders with 8 bits per pixel and a sparsity of 1/2. + + ~see more Numenta forums. """ + def __init__(self, input_shape, num_samples, sparsity, dtype = np.float64, drange = range(0,1), From af0a85a817d1a474a5b5928e5d703f8291c645f4 Mon Sep 17 00:00:00 2001 From: Marek Otahal Date: Mon, 21 Oct 2019 23:57:34 +0200 Subject: [PATCH 25/49] Eye: cleanup --- py/htm/encoders/eye.py | 52 ++++++++++++++++++++---------------------- 1 file changed, 25 insertions(+), 27 deletions(-) diff --git a/py/htm/encoders/eye.py b/py/htm/encoders/eye.py index efa51f2612..b6f8a69694 100644 --- a/py/htm/encoders/eye.py +++ b/py/htm/encoders/eye.py @@ -193,12 +193,9 @@ class Eye: of the eye/sensor (where it looks at) at the level of saccades. - Attribute output_sdr ... retina's output SDR - dimensions are (width, height, 2). The 3rd dimension is for - P,M-cells, which are likely processed separately in the thalamus. Attribute roi ... The most recent view, kept as a attribute. - Attribute parvo ... SDR with parvocellular pathway (color) - Attribute magno ... SDR with magnocellular pathway (movement) + Attribute parvo_sdr ... SDR with parvocellular pathway (color) + Attribute magno_sdr ... SDR with magnocellular pathway (movement) The following three attributes control where the eye is looking within the image. They are Read/Writable. @@ -222,14 +219,14 @@ class Eye: def __init__(self, - output_diameter = 200, # output_sdr size is diameter^2 + output_diameter = 200, # output SDR size is diameter^2 sparsityParvo = 0.2, sparsityMagno = 0.025, color = True,): """ Argument output_diameter is size of output ... output is a field of view (image) with circular shape. Default 200. - `output_sdr` size is `output_diameter^2` + `parvo/magno_sdr` size is `output_diameter^2` Argument `sparsityParvo` - sparsity of parvo-cellular pathway of the eye. As a simplification, "parvo" cells (P-cells) represent colors, static object's properties (shape,...) and are used for image classification. @@ -266,9 +263,6 @@ def __init__(self, assert(sparsityParvo > 0) self.color = color - self.output_sdr = SDR((output_diameter, output_diameter, 2,)) - self.parvo_sdr = SDR((output_diameter, output_diameter,)) - self.magno_sdr = SDR((output_diameter, output_diameter,)) self.retina = cv2.bioinspired.Retina_create( inputSize = (self.retina_diameter, self.retina_diameter), @@ -283,7 +277,7 @@ def __init__(self, sparsityP_ = sparsityParvo if color is True: - dims = (output_diameter, output_diameter, 3,) + dims = (output_diameter, output_diameter, 3,) #3 for RGB color channels # The reason the parvo-cellular has `3rd-root of the sparsity` is that there are three color channels (RGB), # each of which is encoded separately and then combined. The color channels are combined with a logical AND, @@ -309,8 +303,13 @@ def __init__(self, else: self.magno_enc = None - # the current input image - self.image = None + # output variables: + self.image = None # the current input RGB image + self.roi = None # self.image cropped to region of interest + self.parvo_img = None # output visualization of parvo/magno cells + self.magno_img = None + self.parvo_sdr = SDR((output_diameter, output_diameter,)) # parvo/magno cellular representation (SDR) + self.magno_sdr = SDR((output_diameter, output_diameter,)) def new_image(self, image): @@ -352,6 +351,7 @@ def randomize_view(self, scale_range=None): """Set the eye's view point to a random location""" if scale_range is None: scale_range = [2, min(self.image.shape[:2]) / self.retina_diameter] + assert(len(scale_range) == 2) self.orientation = random.uniform(0, 2 * math.pi) self.scale = random.uniform(min(scale_range), max(scale_range)) roi_radius = self.scale * self.retina_diameter / 2 @@ -400,7 +400,8 @@ def _crop_roi(self): def compute(self, position=None, rotation=None, scale=None): """ Arguments position, rotation, scale: optional, if not None, the self.xxx is overriden - with the provided value. + with the provided value. + Returns tuple (SDR parvo, SDR magno) """ # set position if position is not None: @@ -410,6 +411,7 @@ def compute(self, position=None, rotation=None, scale=None): if scale is not None: self.scale=scale + # apply field of view (FOV) self.roi = self._crop_roi() # Retina image transforms (Parvo & Magnocellular). @@ -424,16 +426,16 @@ def compute(self, position=None, rotation=None, scale=None): M = self.retina_diameter * self.fovea_scale if self.parvo_enc is not None: parvo = cv2.logPolar(parvo, - center = (center, center), - M = M, - flags = cv2.WARP_FILL_OUTLIERS) + center = (center, center), + M = M, + flags = cv2.WARP_FILL_OUTLIERS) parvo = np.array(Image.fromarray(parvo).resize( (self.output_diameter, self.output_diameter))) if self.magno_enc is not None: magno = cv2.logPolar(magno, - center = (center, center), - M = M, - flags = cv2.WARP_FILL_OUTLIERS) + center = (center, center), + M = M, + flags = cv2.WARP_FILL_OUTLIERS) magno = np.array(Image.fromarray(magno).resize( (self.output_diameter, self.output_diameter))) # Apply rotation by rolling the images around axis 1. @@ -453,18 +455,13 @@ def compute(self, position=None, rotation=None, scale=None): pr, pg, pb = np.dsplit(p, 3) p = np.logical_and(np.logical_and(pr, pg), pb) p = np.expand_dims(np.squeeze(p), axis=2) - sdr = p if self.magno_enc is not None: m = self.magno_enc.encode(magno) - sdr = m - if self.magno_enc is not None and self.parvo_enc is not None: - sdr = np.concatenate([p, m], axis=2) - self.output_sdr.dense = sdr self.magno_sdr.dense = m.flatten() self.parvo_sdr.dense = p.flatten() - return self.output_sdr + return (self.parvo_sdr, self.magno_sdr) def make_roi_pretty(self, roi=None): @@ -551,6 +548,7 @@ def reset(self): + def _get_images(path): """ Returns list of all image files found under the given file path. """ image_extensions = [ @@ -595,7 +593,7 @@ def _get_images(path): eye.scale = 1 for i in range(10): pos,rot,sc = eye.small_random_movement() - sdr = eye.compute(pos,rot,sc) #TODO derive from Encoder + (sdrParvo, sdrMagno) = eye.compute(pos,rot,sc) #TODO derive from Encoder eye.show_view() print("Sparsity parvo: {}".format(len(eye.parvo_sdr.sparse)/np.product(eye.parvo_sdr.dimensions))) print("Sparsity magno: {}".format(len(eye.magno_sdr.sparse)/np.product(eye.magno_sdr.dimensions))) From e6137c37aee0052274bb5ade67c1f86d526a94a4 Mon Sep 17 00:00:00 2001 From: Marek Otahal Date: Tue, 22 Oct 2019 00:40:15 +0200 Subject: [PATCH 26/49] Eye: cleanup make circular region crop part of ROI --- py/htm/encoders/eye.py | 60 ++++++++++++++++++++---------------------- 1 file changed, 28 insertions(+), 32 deletions(-) diff --git a/py/htm/encoders/eye.py b/py/htm/encoders/eye.py index b6f8a69694..4e1fec429b 100644 --- a/py/htm/encoders/eye.py +++ b/py/htm/encoders/eye.py @@ -361,12 +361,18 @@ def randomize_view(self, scale_range=None): def _crop_roi(self): """ Crop to Region Of Interest (ROI) which contains the whole field of view. - Note that the size of the ROI is (eye.output_diameter * - eye.resolution_factor). + Adds a black circular boarder to mask out areas which the eye can't see. + + Note: size of the ROI is (eye.output_diameter * eye.resolution_factor). + Note: the circular boarder is actually a bit too far out, playing with + eye.fovea_scale can hide areas which this ROI image will show. Arguments: eye.scale, eye.position, eye.image - Returns RGB image. + Returns RGB image (diameter * diameter, but effectively cropped to an + inner circle - FOV). + + See also, @see make_roi_pretty() """ assert(self.image is not None) @@ -375,12 +381,14 @@ def _crop_roi(self): x = int(round(x)) y = int(round(y)) x_max, y_max, color_depth = self.image.shape + # Find the boundary of the ROI and slice out the image. x_low = max(0, x-r) x_high = min(x_max, x+r) y_low = max(0, y-r) y_high = min(y_max, y+r) image_slice = self.image[x_low : x_high, y_low : y_high] + # Make the ROI and insert the image into it. roi = np.zeros((2*r, 2*r, 3,), dtype=np.uint8) if x-r < 0: @@ -393,10 +401,18 @@ def _crop_roi(self): y_offset = 0 x_shape, y_shape, color_depth = image_slice.shape roi[x_offset:x_offset+x_shape, y_offset:y_offset+y_shape] = image_slice + # Rescale the ROI to remove the scaling effect. roi = np.array(Image.fromarray(roi).resize( (self.retina_diameter, self.retina_diameter))) + + # Mask out areas the eye can't see by drawing a circle boarder. + center = int(roi.shape[0] / 2) + circle_mask = np.zeros(roi.shape, dtype=np.uint8) + cv2.circle(circle_mask, (center, center), center, thickness = -1, color=(255,255,255)) + roi = np.minimum(roi, circle_mask) return roi + def compute(self, position=None, rotation=None, scale=None): """ Arguments position, rotation, scale: optional, if not None, the self.xxx is overriden @@ -460,6 +476,8 @@ def compute(self, position=None, rotation=None, scale=None): self.magno_sdr.dense = m.flatten() self.parvo_sdr.dense = p.flatten() + assert(len(self.magno_sdr.sparse) > 0) + assert(len(self.parvo_sdr.sparse) > 0) return (self.parvo_sdr, self.magno_sdr) @@ -467,12 +485,10 @@ def compute(self, position=None, rotation=None, scale=None): def make_roi_pretty(self, roi=None): """ Makes the eye's view look more presentable. - - Adds a black circular boarder to mask out areas which the eye can't see - Note that this boarder is actually a bit too far out, playing with - eye.fovea_scale can hide areas which this ROI image will show. - Adds 5 dots to the center of the image to show where the fovea is. Returns an RGB image. + See _crop_roi() """ if roi is None: roi = self.roi @@ -484,13 +500,8 @@ def make_roi_pretty(self, roi=None): M = cv2.getRotationMatrix2D((cols / 2, rows / 2), angle, 1) roi = cv2.warpAffine(roi, M, (cols,rows)) - # Mask out areas the eye can't see by drawing a circle boarder. - center = int(roi.shape[0] / 2) - circle_mask = np.zeros(roi.shape, dtype=np.uint8) - cv2.circle(circle_mask, (center, center), center, thickness = -1, color=(255,255,255)) - roi = np.minimum(roi, circle_mask) - # Invert 5 pixels in the center to show where the fovea is located. + center = int(roi.shape[0] / 2) roi[center, center] = np.full(3, 255) - roi[center, center] roi[center+2, center+2] = np.full(3, 255) - roi[center+2, center+2] roi[center-2, center+2] = np.full(3, 255) - roi[center-2, center+2] @@ -498,7 +509,8 @@ def make_roi_pretty(self, roi=None): roi[center+2, center-2] = np.full(3, 255) - roi[center+2, center-2] return roi - def show_view(self, window_name='Eye'): + + def plot(self, window_name='Eye', delay=1000): roi = self.make_roi_pretty() cv2.imshow('Region Of Interest', roi) if self.color: @@ -510,25 +522,8 @@ def show_view(self, window_name='Eye'): cv2.imshow('Parvo SDR', idx) idx = self.magno_sdr.dense.astype(np.uint8).reshape(self.output_diameter, self.output_diameter)*255 cv2.imshow('Magno SDR', idx) - cv2.waitKey(1000) + cv2.waitKey(delay) - def input_space_sample_points(self, npoints): - """ - Returns a sampling of coordinates which the eye is currently looking at. - Use the result to determine the actual label of the image in the area - where the eye is looking. - """ - # Find the retina's radius in the image. - r = int(round(self.scale * self.retina_diameter / 2)) - # Shrink the retina's radius so that sample points are nearer the fovea. - # Also shrink radius B/C this does not account for the diagonal - # distance, just the manhattan distance. - r = r * 2/3 - # Generate points. - coords = np.random.random_integers(-r, r, size=(npoints, 2)) - # Add this position offset. - coords += np.array(np.rint(self.position), dtype=np.int).reshape(1, 2) - return coords def small_random_movement(self): """returns small difference in position, rotation, scale. @@ -591,10 +586,11 @@ def _get_images(path): print("Loading image %s"%img_path) eye.new_image(img_path) eye.scale = 1 + eye.center_view() for i in range(10): pos,rot,sc = eye.small_random_movement() (sdrParvo, sdrMagno) = eye.compute(pos,rot,sc) #TODO derive from Encoder - eye.show_view() + eye.plot(500) print("Sparsity parvo: {}".format(len(eye.parvo_sdr.sparse)/np.product(eye.parvo_sdr.dimensions))) print("Sparsity magno: {}".format(len(eye.magno_sdr.sparse)/np.product(eye.magno_sdr.dimensions))) print("All images seen.") From ad3e11900e7cdcae64f1f7fb91ebc865e1df5085 Mon Sep 17 00:00:00 2001 From: Marek Otahal Date: Tue, 22 Oct 2019 02:04:22 +0200 Subject: [PATCH 27/49] Eye: replace PIL with cv2 which is needed anyway, can drop PIL/Pillow --- py/htm/encoders/eye.py | 10 +++++----- setup.py | 1 - 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/py/htm/encoders/eye.py b/py/htm/encoders/eye.py index 4e1fec429b..7d943207c3 100644 --- a/py/htm/encoders/eye.py +++ b/py/htm/encoders/eye.py @@ -23,7 +23,6 @@ import numpy as np import cv2 # pip install opencv-contrib-python -from PIL import Image # pip Pillow from htm.bindings.sdr import SDR @@ -321,7 +320,8 @@ def new_image(self, image): """ # Load image if needed. if isinstance(image, str): - self.image = np.array(Image.open(image), copy=False) + self.image = cv2.imread(image) + self.image = cv2.cvtColor(self.image, cv2.COLOR_BGR2RGB) else: self.image = image # Get the image into the right format. @@ -403,7 +403,7 @@ def _crop_roi(self): roi[x_offset:x_offset+x_shape, y_offset:y_offset+y_shape] = image_slice # Rescale the ROI to remove the scaling effect. - roi = np.array(Image.fromarray(roi).resize( (self.retina_diameter, self.retina_diameter))) + roi.resize( (self.retina_diameter, self.retina_diameter, 3)) # Mask out areas the eye can't see by drawing a circle boarder. center = int(roi.shape[0] / 2) @@ -445,14 +445,14 @@ def compute(self, position=None, rotation=None, scale=None): center = (center, center), M = M, flags = cv2.WARP_FILL_OUTLIERS) - parvo = np.array(Image.fromarray(parvo).resize( (self.output_diameter, self.output_diameter))) + parvo = cv2.resize(parvo, dsize=(self.output_diameter, self.output_diameter), interpolation = cv2.INTER_CUBIC) if self.magno_enc is not None: magno = cv2.logPolar(magno, center = (center, center), M = M, flags = cv2.WARP_FILL_OUTLIERS) - magno = np.array(Image.fromarray(magno).resize( (self.output_diameter, self.output_diameter))) + magno = cv2.resize(magno, dsize=(self.output_diameter, self.output_diameter), interpolation = cv2.INTER_CUBIC) # Apply rotation by rolling the images around axis 1. rotation = self.output_diameter * self.orientation / (2 * math.pi) diff --git a/setup.py b/setup.py index 82f9b1dfee..c6ac7e045f 100644 --- a/setup.py +++ b/setup.py @@ -401,7 +401,6 @@ def configure(platform, build_type): 'matplotlib':'examples', 'scipy':'examples', 'opencv-contrib-python': 'examples', #for cv2.bioinspired for RetinaEncoder - 'Pillow': 'examples' }, zip_safe=False, cmdclass={ From d5e33029fe223581a64edea7da564748654818b4 Mon Sep 17 00:00:00 2001 From: Marek Otahal Date: Tue, 22 Oct 2019 03:37:25 +0200 Subject: [PATCH 28/49] Eye: fixes --- py/htm/encoders/eye.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/py/htm/encoders/eye.py b/py/htm/encoders/eye.py index 7d943207c3..2fff41f23c 100644 --- a/py/htm/encoders/eye.py +++ b/py/htm/encoders/eye.py @@ -231,8 +231,6 @@ def __init__(self, object's properties (shape,...) and are used for image classification. For biologically accurate details see eg. https://foundationsofvision.stanford.edu/chapter-5-the-retinal-representation/ - The sparsity of the retinal output is the sum of sparsityParvo + sparsityMagno, - which is represented in `Eye.sparsity` and represents sparsity of the output SDR. Note: biologically, the ratio between P/M-cells is about 8(P):1(M):(1 rest), see https://www.pnas.org/content/94/11/5900 Argument `sparsityMagno` - sparsity of the magno-cellular (M-cells) pathway, which @@ -253,11 +251,13 @@ def __init__(self, assert(output_diameter // 2 * 2 == output_diameter) # Diameter must be an even number. assert(self.retina_diameter // 2 * 2 == self.retina_diameter) # (Resolution Factor X Diameter) must be an even number. assert(sparsityParvo >= 0 and sparsityParvo <= 1.0) + if sparsityParvo > 0: + assert(sparsityParvo * (self.retina_diameter **2) > 0) self.sparsityParvo = sparsityParvo assert(sparsityMagno >= 0 and sparsityMagno <= 1.0) + if sparsityMagno > 0: + assert(sparsityMagno * (self.retina_diameter **2) > 0) self.sparsityMagno = sparsityMagno - self.sparsity = sparsityParvo + sparsityMagno - assert(self.sparsity > 0 and self.sparsity <= 1.0) if color is True: assert(sparsityParvo > 0) self.color = color @@ -421,7 +421,7 @@ def compute(self, position=None, rotation=None, scale=None): """ # set position if position is not None: - self.position = pos + self.position = position if rotation is not None: self.orientation=rotation if scale is not None: From 32a4e499f18b240d5e36bac13f4a23f3cdbcf8b3 Mon Sep 17 00:00:00 2001 From: Marek Otahal Date: Tue, 22 Oct 2019 03:40:32 +0200 Subject: [PATCH 29/49] Eye: add test --- py/tests/encoders/eye_test.py | 30 ++++++++++++++++++++++++++++ py/tests/encoders/ronja_the_cat.jpg | Bin 0 -> 211281 bytes requirements.txt | 1 + 3 files changed, 31 insertions(+) create mode 100644 py/tests/encoders/eye_test.py create mode 100644 py/tests/encoders/ronja_the_cat.jpg diff --git a/py/tests/encoders/eye_test.py b/py/tests/encoders/eye_test.py new file mode 100644 index 0000000000..7a084c2334 --- /dev/null +++ b/py/tests/encoders/eye_test.py @@ -0,0 +1,30 @@ + +""" Unit tests for retina encoder. """ + +import unittest +import os +import numpy as np + +from htm.encoders.eye import Eye + +class EyeEncoderTest(unittest.TestCase): + """ Unit tests for Eye encoder class. """ + + def testBasicUsage(self): + eye = Eye() + eye.reset() + FILE=os.path.join('py','tests','encoders','ronja_the_cat.jpg') + eye.new_image(FILE) + eye.scale = 0.5 + eye.center_view() + for _ in range(10): + pos,rot,sc = eye.small_random_movement() + (sdrParvo, sdrMagno) = eye.compute(pos,rot,sc) + try: + eye.plot(500) + except: + pass + print("Sparsity parvo: {}".format(len(eye.parvo_sdr.sparse)/np.product(eye.parvo_sdr.dimensions))) + print("Sparsity magno: {}".format(len(eye.magno_sdr.sparse)/np.product(eye.magno_sdr.dimensions))) + + diff --git a/py/tests/encoders/ronja_the_cat.jpg b/py/tests/encoders/ronja_the_cat.jpg new file mode 100644 index 0000000000000000000000000000000000000000..fb462a3080a603959e55c1e2244000b5f915ef7c GIT binary patch literal 211281 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 z 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

ID?c=Xz_Ybs8+Je^JC*NvaKIj{yA{U> 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 N@l(hHTf)^L3T&KMQGITKTt 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 Q7ejmmj-!!xca>59*(F|2_y7O^ literal 0 HcmV?d00001 diff --git a/requirements.txt b/requirements.txt index 45e15a7bbb..9b1da6e139 100644 --- a/requirements.txt +++ b/requirements.txt @@ -9,3 +9,4 @@ prettytable>=0.7.2 # for monitor-mixin in htm.advanced (+its tests) ## optional dependencies, such as for visualizations, running examples # should be placed in setup.py section extras_require. Install those by # pip install htm.core[examples] +opencv-contrib-python #for cv2.bioinspired for RetinaEncoder From 45eb075b41f7f3aa22a0e1223591f979e3914fdc Mon Sep 17 00:00:00 2001 From: Marek Otahal Date: Tue, 22 Oct 2019 10:33:54 +0200 Subject: [PATCH 30/49] CI: split tests to c++/bindings/python --- .github/workflows/build.yml | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index ac3e18387e..a09f893bc9 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -65,8 +65,17 @@ jobs: - name: build htmcore with setup.py run: python setup.py install --user --force - - name: C++ & Python Tests - run: python setup.py test + - name: C++ Tests + shell: bash + run: ./build/Release/bin/unit_tests + + - name: Python Bindings Tests + shell: bash + run: pytest bindings/py/tests + + - name: Python (pure) Tests + shell: bash + run: pytest py/tests - name: Memory leaks check (valgrind) if: matrix.os == 'ubuntu-18.04' From 7f90a1af31d3eb221996f5d19529bad86b479911 Mon Sep 17 00:00:00 2001 From: Marek Otahal Date: Wed, 23 Oct 2019 00:05:05 +0200 Subject: [PATCH 31/49] Eye: review --- py/htm/encoders/eye.py | 36 ++++++++++++++++++++++-------------- 1 file changed, 22 insertions(+), 14 deletions(-) diff --git a/py/htm/encoders/eye.py b/py/htm/encoders/eye.py index 2fff41f23c..4084dc627b 100644 --- a/py/htm/encoders/eye.py +++ b/py/htm/encoders/eye.py @@ -49,7 +49,7 @@ class ChannelEncoder: 1. Semantic similarity happens when two inputs which are similar have similar SDR representations. This encoder design does two things to cause semantic similarity: (1) SDR bits are responsive to a range of input values, - and (2) topology allows near by bits to represent similar things. + (2) topology allows near by bits to represent similar things. Many encoders apply thresholds to real valued input data to convert the input into Boolean outputs. In this encoder uses two thresholds to form ranges which @@ -86,8 +86,10 @@ class ChannelEncoder: which are active in all input SDRs. For example, to make an encoder with 8 bits per pixel and a sparsity of 1/8: create three encoders with 8 bits per pixel and a sparsity of 1/2. - - ~see more Numenta forums. + + For more on encoding see: + Encoding Data for HTM Systems, Scott Purdy 2016, + https://arxiv.org/abs/1602.05925 """ def __init__(self, input_shape, num_samples, sparsity, @@ -118,7 +120,8 @@ def __init__(self, input_shape, num_samples, sparsity, self.input_shape = tuple(input_shape) self.num_samples = int(round(num_samples)) self.sparsity = sparsity - self.output_shape = self.input_shape + (self.num_samples,) + self.dimensions = self.input_shape + (self.num_samples,) # output shape + self.size = np.prod(self.dimensions) self.dtype = dtype self.drange = drange self.len_drange = max(drange) - min(drange) @@ -132,7 +135,7 @@ def __init__(self, input_shape, num_samples, sparsity, # truncated near the edges. centers = np.random.uniform(min(self.drange) + radius, max(self.drange) - radius, - size=self.output_shape) + size=self.dimensions) else: # Buckets near the edges of the datarange are OK. They will not # respond to a full range of input values but are needed to @@ -149,7 +152,7 @@ def __init__(self, input_shape, num_samples, sparsity, radius = len_pad_drange * self.sparsity / 2 centers = np.random.uniform(min(pad_drange), max(pad_drange), - size=self.output_shape) + size=self.dimensions) # Make the lower and upper bounds of the ranges. low = np.clip(centers - radius, min(self.drange), max(self.drange)) high = np.clip(centers + radius, min(self.drange), max(self.drange)) @@ -157,7 +160,12 @@ def __init__(self, input_shape, num_samples, sparsity, self.high = np.array(high, dtype=self.dtype) def encode(self, img): - """Returns a dense boolean np.ndarray.""" + """ + Argument img - ndarray + Returns a SDR + """ + #assert(isinstance(inp, SDR)) #TODO make Channel take SDR as input too + #img = np.ndarray(inp.dense).reshape(self.input_shape) assert(img.shape == self.input_shape) assert(img.dtype == self.dtype) if self.wrap: @@ -168,7 +176,10 @@ def encode(self, img): img %= self.len_drange img += min(self.drange) img = img.reshape(img.shape + (1,)) - return np.logical_and(self.low <= img, img <= self.high) + img = np.logical_and(self.low <= img, img <= self.high) + enc = SDR(self.dimensions) + enc.dense = img + return enc class Eye: @@ -463,19 +474,16 @@ def compute(self, position=None, rotation=None, scale=None): self.magno_img = np.roll(magno, rotation, axis=0) # Encode images into SDRs. - p = [] - m = [] if self.parvo_enc is not None: - p = self.parvo_enc.encode(parvo) + p = self.parvo_enc.encode(parvo).dense if self.color: pr, pg, pb = np.dsplit(p, 3) p = np.logical_and(np.logical_and(pr, pg), pb) p = np.expand_dims(np.squeeze(p), axis=2) + self.parvo_sdr.dense = p.flatten() if self.magno_enc is not None: - m = self.magno_enc.encode(magno) + self.magno_sdr = self.magno_enc.encode(magno) - self.magno_sdr.dense = m.flatten() - self.parvo_sdr.dense = p.flatten() assert(len(self.magno_sdr.sparse) > 0) assert(len(self.parvo_sdr.sparse) > 0) From 8025d82784f1db616be50ac898359e6008ea0a9a Mon Sep 17 00:00:00 2001 From: Marek Otahal Date: Wed, 23 Oct 2019 01:00:34 +0200 Subject: [PATCH 32/49] Eye: improve test --- py/tests/encoders/eye_test.py | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/py/tests/encoders/eye_test.py b/py/tests/encoders/eye_test.py index 7a084c2334..398cceaf01 100644 --- a/py/tests/encoders/eye_test.py +++ b/py/tests/encoders/eye_test.py @@ -16,14 +16,12 @@ def testBasicUsage(self): FILE=os.path.join('py','tests','encoders','ronja_the_cat.jpg') eye.new_image(FILE) eye.scale = 0.5 - eye.center_view() + #eye.center_view() + eye.position = (400,400) for _ in range(10): pos,rot,sc = eye.small_random_movement() (sdrParvo, sdrMagno) = eye.compute(pos,rot,sc) - try: - eye.plot(500) - except: - pass + #eye.plot(delay=500) print("Sparsity parvo: {}".format(len(eye.parvo_sdr.sparse)/np.product(eye.parvo_sdr.dimensions))) print("Sparsity magno: {}".format(len(eye.magno_sdr.sparse)/np.product(eye.magno_sdr.dimensions))) From 4e8b7813508e48a8f685dbc5524e17965ac8044b Mon Sep 17 00:00:00 2001 From: Marek Otahal Date: Wed, 23 Oct 2019 13:55:28 +0200 Subject: [PATCH 33/49] Eye: provide dimensions, size for compatibility with encoders --- py/htm/encoders/eye.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/py/htm/encoders/eye.py b/py/htm/encoders/eye.py index 4084dc627b..c3ca5ee930 100644 --- a/py/htm/encoders/eye.py +++ b/py/htm/encoders/eye.py @@ -320,6 +320,8 @@ def __init__(self, self.magno_img = None self.parvo_sdr = SDR((output_diameter, output_diameter,)) # parvo/magno cellular representation (SDR) self.magno_sdr = SDR((output_diameter, output_diameter,)) + self.dimensions = self.retina.getOutputSize() + self.size = np.prod(self.dimensions) def new_image(self, image): @@ -599,6 +601,6 @@ def _get_images(path): pos,rot,sc = eye.small_random_movement() (sdrParvo, sdrMagno) = eye.compute(pos,rot,sc) #TODO derive from Encoder eye.plot(500) - print("Sparsity parvo: {}".format(len(eye.parvo_sdr.sparse)/np.product(eye.parvo_sdr.dimensions))) - print("Sparsity magno: {}".format(len(eye.magno_sdr.sparse)/np.product(eye.magno_sdr.dimensions))) + print("Sparsity parvo: {}".format(len(eye.parvo_sdr.sparse)/np.product(eye.dimensions))) + print("Sparsity magno: {}".format(len(eye.magno_sdr.sparse)/np.product(eye.dimensions))) print("All images seen.") From 854b4501a43244f73303d7b689958de273f4950e Mon Sep 17 00:00:00 2001 From: Marek Otahal Date: Wed, 23 Oct 2019 13:57:12 +0200 Subject: [PATCH 34/49] MNIST: use Retina image encoder WIP --- py/htm/examples/mnist.py | 16 ++++++++++++---- py/tests/encoders/eye_test.py | 1 + 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/py/htm/examples/mnist.py b/py/htm/examples/mnist.py index 72a45ef730..84cf61af99 100644 --- a/py/htm/examples/mnist.py +++ b/py/htm/examples/mnist.py @@ -24,6 +24,7 @@ from htm.bindings.algorithms import SpatialPooler, Classifier from htm.bindings.sdr import SDR, Metrics +from htm.encoders.eye import Eye def load_ds(name, num_test, shape=None): @@ -90,9 +91,13 @@ def main(parameters=default_parameters, argv=None, verbose=True): random.shuffle(training_data) # Setup the AI. - enc = SDR(train_images[0].shape) + encoder = Eye(output_diameter=train_images[0].shape[0], + sparsityParvo = 0.2, + sparsityMagno = 0.0, + color = True) + sp = SpatialPooler( - inputDimensions = enc.dimensions, + inputDimensions = encoder.dimensions, columnDimensions = parameters['columnDimensions'], potentialRadius = parameters['potentialRadius'], potentialPct = parameters['potentialPct'], @@ -115,7 +120,10 @@ def main(parameters=default_parameters, argv=None, verbose=True): # Training Loop for i in range(len(train_images)): img, lbl = random.choice(training_data) - encode(img, enc) + encoder.new_image(img) + (enc, _) = encoder.compute() + if i == 1: + encoder.plot() sp.compute( enc, True, columns ) sdrc.learn( columns, lbl ) #TODO SDRClassifier could accept string as a label, currently must be int @@ -125,7 +133,7 @@ def main(parameters=default_parameters, argv=None, verbose=True): # Testing Loop score = 0 for img, lbl in test_data: - encode(img, enc) + enc = encode(img) sp.compute( enc, False, columns ) if lbl == np.argmax( sdrc.infer( columns ) ): score += 1 diff --git a/py/tests/encoders/eye_test.py b/py/tests/encoders/eye_test.py index 398cceaf01..42d2591cc5 100644 --- a/py/tests/encoders/eye_test.py +++ b/py/tests/encoders/eye_test.py @@ -24,5 +24,6 @@ def testBasicUsage(self): #eye.plot(delay=500) print("Sparsity parvo: {}".format(len(eye.parvo_sdr.sparse)/np.product(eye.parvo_sdr.dimensions))) print("Sparsity magno: {}".format(len(eye.magno_sdr.sparse)/np.product(eye.magno_sdr.dimensions))) + assert(eye.dimensions == (1,1)) From ede598df019da9f5d25857524b93de5ef3d5eebc Mon Sep 17 00:00:00 2001 From: Marek Otahal Date: Wed, 23 Oct 2019 14:57:21 +0200 Subject: [PATCH 35/49] Eye: fix dimensions --- py/htm/encoders/eye.py | 14 +++++++------- py/tests/encoders/eye_test.py | 2 +- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/py/htm/encoders/eye.py b/py/htm/encoders/eye.py index c3ca5ee930..95ef40a286 100644 --- a/py/htm/encoders/eye.py +++ b/py/htm/encoders/eye.py @@ -314,14 +314,14 @@ def __init__(self, self.magno_enc = None # output variables: - self.image = None # the current input RGB image - self.roi = None # self.image cropped to region of interest - self.parvo_img = None # output visualization of parvo/magno cells - self.magno_img = None - self.parvo_sdr = SDR((output_diameter, output_diameter,)) # parvo/magno cellular representation (SDR) - self.magno_sdr = SDR((output_diameter, output_diameter,)) - self.dimensions = self.retina.getOutputSize() + self.dimensions = (output_diameter, output_diameter,) self.size = np.prod(self.dimensions) + self.image = None # the current input RGB image + self.roi = np.zeros(self.dimensions) # self.image cropped to region of interest + self.parvo_img = None # output visualization of parvo/magno cells + self.magno_img = None + self.parvo_sdr = SDR(self.dimensions) # parvo/magno cellular representation (SDR) + self.magno_sdr = SDR(self.dimensions) def new_image(self, image): diff --git a/py/tests/encoders/eye_test.py b/py/tests/encoders/eye_test.py index 42d2591cc5..50f14befe3 100644 --- a/py/tests/encoders/eye_test.py +++ b/py/tests/encoders/eye_test.py @@ -24,6 +24,6 @@ def testBasicUsage(self): #eye.plot(delay=500) print("Sparsity parvo: {}".format(len(eye.parvo_sdr.sparse)/np.product(eye.parvo_sdr.dimensions))) print("Sparsity magno: {}".format(len(eye.magno_sdr.sparse)/np.product(eye.magno_sdr.dimensions))) - assert(eye.dimensions == (1,1)) + assert(eye.dimensions == eye.parvo_sdr.dimensions) From f93c9049095113c6b1d5cc034a9b8b2b27ba01a4 Mon Sep 17 00:00:00 2001 From: Marek Otahal Date: Wed, 23 Oct 2019 15:06:14 +0200 Subject: [PATCH 36/49] Eye: fix test --- py/tests/encoders/eye_test.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/py/tests/encoders/eye_test.py b/py/tests/encoders/eye_test.py index 50f14befe3..7e7689c23d 100644 --- a/py/tests/encoders/eye_test.py +++ b/py/tests/encoders/eye_test.py @@ -24,6 +24,6 @@ def testBasicUsage(self): #eye.plot(delay=500) print("Sparsity parvo: {}".format(len(eye.parvo_sdr.sparse)/np.product(eye.parvo_sdr.dimensions))) print("Sparsity magno: {}".format(len(eye.magno_sdr.sparse)/np.product(eye.magno_sdr.dimensions))) - assert(eye.dimensions == eye.parvo_sdr.dimensions) + assert(eye.dimensions == (200,200)) From aa3aea8ece351148c014de13ebb44df5673cb820 Mon Sep 17 00:00:00 2001 From: Marek Otahal Date: Wed, 23 Oct 2019 15:58:02 +0200 Subject: [PATCH 37/49] Eye: fixes for running only one of parvo/magno --- py/htm/encoders/eye.py | 31 ++++++++++++++++--------------- 1 file changed, 16 insertions(+), 15 deletions(-) diff --git a/py/htm/encoders/eye.py b/py/htm/encoders/eye.py index 95ef40a286..977dbe0629 100644 --- a/py/htm/encoders/eye.py +++ b/py/htm/encoders/eye.py @@ -339,9 +339,6 @@ def new_image(self, image): self.image = image # Get the image into the right format. assert(isinstance(self.image, np.ndarray)) - if self.image.dtype != np.uint8: - raise TypeError('Image "%s" dtype is not unsigned 8 bit integer, image.dtype is %s.'%( - self.image.dtype)) # Ensure there are three color channels. if len(self.image.shape) == 2 or self.image.shape[2] == 1: self.image = np.dstack([self.image] * 3) @@ -483,11 +480,11 @@ def compute(self, position=None, rotation=None, scale=None): p = np.logical_and(np.logical_and(pr, pg), pb) p = np.expand_dims(np.squeeze(p), axis=2) self.parvo_sdr.dense = p.flatten() + assert(len(self.parvo_sdr.sparse) > 0) + if self.magno_enc is not None: self.magno_sdr = self.magno_enc.encode(magno) - - assert(len(self.magno_sdr.sparse) > 0) - assert(len(self.parvo_sdr.sparse) > 0) + assert(len(self.magno_sdr.sparse) > 0) return (self.parvo_sdr, self.magno_sdr) @@ -523,15 +520,19 @@ def make_roi_pretty(self, roi=None): def plot(self, window_name='Eye', delay=1000): roi = self.make_roi_pretty() cv2.imshow('Region Of Interest', roi) - if self.color: - cv2.imshow('Parvocellular', self.parvo_img[:,:,::-1]) - else: - cv2.imshow('Parvocellular', self.parvo_img) - cv2.imshow('Magnocellular', self.magno_img) - idx = self.parvo_sdr.dense.astype(np.uint8).reshape(self.output_diameter, self.output_diameter)*255 - cv2.imshow('Parvo SDR', idx) - idx = self.magno_sdr.dense.astype(np.uint8).reshape(self.output_diameter, self.output_diameter)*255 - cv2.imshow('Magno SDR', idx) + if self.sparsityParvo > 0: # parvo enabled + if self.color: + cv2.imshow('Parvocellular', self.parvo_img[:,:,::-1]) + else: + cv2.imshow('Parvocellular', self.parvo_img) + idx = self.parvo_sdr.dense.astype(np.uint8).reshape(self.output_diameter, self.output_diameter)*255 + cv2.imshow('Parvo SDR', idx) + + if self.sparsityMagno > 0: # magno enabled + cv2.imshow('Magnocellular', self.magno_img) + idx = self.magno_sdr.dense.astype(np.uint8).reshape(self.output_diameter, self.output_diameter)*255 + cv2.imshow('Magno SDR', idx) + cv2.waitKey(delay) From 2c94dbb33c87a39621db56f24eb9420ec7558411 Mon Sep 17 00:00:00 2001 From: Marek Otahal Date: Wed, 23 Oct 2019 17:37:40 +0200 Subject: [PATCH 38/49] fixes in mnist + eye --- py/htm/examples/mnist.py | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/py/htm/examples/mnist.py b/py/htm/examples/mnist.py index 84cf61af99..923b902d58 100644 --- a/py/htm/examples/mnist.py +++ b/py/htm/examples/mnist.py @@ -18,6 +18,7 @@ import random import numpy as np import sys +import cv2 # fetch datasets from www.openML.org/ from sklearn.datasets import fetch_openml @@ -85,16 +86,18 @@ def main(parameters=default_parameters, argv=None, verbose=True): # Load data. train_labels, train_images, test_labels, test_images = load_ds('mnist_784', 10000, shape=[28,28]) # HTM: ~95.6% #train_labels, train_images, test_labels, test_images = load_ds('Fashion-MNIST', 10000, shape=[28,28]) # HTM baseline: ~83% + cv2.imshow('orig', train_images[1]) training_data = list(zip(train_images, train_labels)) test_data = list(zip(test_images, test_labels)) random.shuffle(training_data) - # Setup the AI. - encoder = Eye(output_diameter=train_images[0].shape[0], + # Setup the AI + enc = SDR(train_images[0].shape) + encoder = Eye(output_diameter=train_images[0].shape[0]-8, #28-3 sparsityParvo = 0.2, - sparsityMagno = 0.0, - color = True) + sparsityMagno = 0.0, #disabled + color = False) sp = SpatialPooler( inputDimensions = encoder.dimensions, @@ -133,7 +136,8 @@ def main(parameters=default_parameters, argv=None, verbose=True): # Testing Loop score = 0 for img, lbl in test_data: - enc = encode(img) + encoder.new_image(img) + (enc, _) = encoder.compute() sp.compute( enc, False, columns ) if lbl == np.argmax( sdrc.infer( columns ) ): score += 1 From a063676e0c26320f8b0a3ed3c6b465295ea2149d Mon Sep 17 00:00:00 2001 From: Marek Otahal Date: Wed, 23 Oct 2019 19:04:53 +0200 Subject: [PATCH 39/49] Eye: improve main example --- py/htm/encoders/eye.py | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/py/htm/encoders/eye.py b/py/htm/encoders/eye.py index 977dbe0629..7beab74f1d 100644 --- a/py/htm/encoders/eye.py +++ b/py/htm/encoders/eye.py @@ -591,17 +591,22 @@ def _get_images(path): if not images: print('No images found at file path "%s"!'%args.IMAGE) else: - eye = Eye() + eye = Eye(output_diameter=200, + sparsityParvo=0.2, + sparsityMagno=0.02, + color=True) for img_path in images: eye.reset() print("Loading image %s"%img_path) eye.new_image(img_path) - eye.scale = 1 - eye.center_view() + #eye.fovea_scale = 0.0177 #TODO find which value? + #eye.center_view() + eye.position=(400,400) for i in range(10): pos,rot,sc = eye.small_random_movement() + sc=1.0 #FIXME broken (only plot?) whenever scale != 1.0 (sdrParvo, sdrMagno) = eye.compute(pos,rot,sc) #TODO derive from Encoder - eye.plot(500) + eye.plot(delay=5000) print("Sparsity parvo: {}".format(len(eye.parvo_sdr.sparse)/np.product(eye.dimensions))) print("Sparsity magno: {}".format(len(eye.magno_sdr.sparse)/np.product(eye.dimensions))) print("All images seen.") From 80747f5a9c8202afa8ee16a4b488852b3bd8bacc Mon Sep 17 00:00:00 2001 From: Marek Otahal Date: Wed, 23 Oct 2019 19:27:42 +0200 Subject: [PATCH 40/49] Eye: doc parameters `: --- py/htm/encoders/eye.py | 26 +++++++++++++++++--------- 1 file changed, 17 insertions(+), 9 deletions(-) diff --git a/py/htm/encoders/eye.py b/py/htm/encoders/eye.py index 7beab74f1d..67d040165d 100644 --- a/py/htm/encoders/eye.py +++ b/py/htm/encoders/eye.py @@ -257,7 +257,8 @@ def __init__(self, # is reduced by this factor back to the output_diameter. self.resolution_factor = 3 self.retina_diameter = int(self.resolution_factor * output_diameter) - # Argument fovea_scale ... represents "zoom" aka distance from the object/image. + # Argument fovea_scale ... proportion of the image (ROI) which will be covered (seen) by + # high-res fovea (parvo pathway) self.fovea_scale = 0.177 assert(output_diameter // 2 * 2 == output_diameter) # Diameter must be an even number. assert(self.retina_diameter // 2 * 2 == self.retina_diameter) # (Resolution Factor X Diameter) must be an even number. @@ -313,16 +314,21 @@ def __init__(self, else: self.magno_enc = None - # output variables: + ## output variables: self.dimensions = (output_diameter, output_diameter,) self.size = np.prod(self.dimensions) self.image = None # the current input RGB image - self.roi = np.zeros(self.dimensions) # self.image cropped to region of interest + self.roi = np.zeros(self.dimensions) # self.image cropped to region of interest, what encoder processes ("sees") self.parvo_img = None # output visualization of parvo/magno cells self.magno_img = None self.parvo_sdr = SDR(self.dimensions) # parvo/magno cellular representation (SDR) self.magno_sdr = SDR(self.dimensions) + ## motor-control variables (must be user specified) + self.position = (0,0) # can use self.center_view(), self.random_view() + self.scale = 1.0 # represents "zoom" aka distance from the object/image #FIXME broken for != 1.0 + self.orientation= 0 # angle between image/object and camera, in deg + def new_image(self, image): """ @@ -415,11 +421,6 @@ def _crop_roi(self): # Rescale the ROI to remove the scaling effect. roi.resize( (self.retina_diameter, self.retina_diameter, 3)) - # Mask out areas the eye can't see by drawing a circle boarder. - center = int(roi.shape[0] / 2) - circle_mask = np.zeros(roi.shape, dtype=np.uint8) - cv2.circle(circle_mask, (center, center), center, thickness = -1, color=(255,255,255)) - roi = np.minimum(roi, circle_mask) return roi @@ -514,6 +515,13 @@ def make_roi_pretty(self, roi=None): roi[center-2, center+2] = np.full(3, 255) - roi[center-2, center+2] roi[center-2, center-2] = np.full(3, 255) - roi[center-2, center-2] roi[center+2, center-2] = np.full(3, 255) - roi[center+2, center-2] + + # Mask out areas the eye can't see by drawing a circle boarder. + center = int(roi.shape[0] / 2) + circle_mask = np.zeros(roi.shape, dtype=np.uint8) + cv2.circle(circle_mask, (center, center), center, thickness = -1, color=(255,255,255)) + roi = np.minimum(roi, circle_mask) + return roi @@ -599,7 +607,7 @@ def _get_images(path): eye.reset() print("Loading image %s"%img_path) eye.new_image(img_path) - #eye.fovea_scale = 0.0177 #TODO find which value? + eye.fovea_scale = 0.177 #TODO find which value? #eye.center_view() eye.position=(400,400) for i in range(10): From 2e506352a41d81fc1ef9428155b852fbe8b276b9 Mon Sep 17 00:00:00 2001 From: Marek Otahal Date: Wed, 23 Oct 2019 19:33:11 +0200 Subject: [PATCH 41/49] Eye: crop ROI to circle --- py/htm/encoders/eye.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/py/htm/encoders/eye.py b/py/htm/encoders/eye.py index 67d040165d..28a36b2fa7 100644 --- a/py/htm/encoders/eye.py +++ b/py/htm/encoders/eye.py @@ -421,6 +421,13 @@ def _crop_roi(self): # Rescale the ROI to remove the scaling effect. roi.resize( (self.retina_diameter, self.retina_diameter, 3)) + # Mask out areas the eye can't see by drawing a circle boarder. + # this represents the "shape" of the sensor/eye (comment out to leave rectangural) + center = int(roi.shape[0] / 2) + circle_mask = np.zeros(roi.shape, dtype=np.uint8) + cv2.circle(circle_mask, (center, center), center, thickness = -1, color=(255,255,255)) + roi = np.minimum(roi, circle_mask) + return roi @@ -516,12 +523,6 @@ def make_roi_pretty(self, roi=None): roi[center-2, center-2] = np.full(3, 255) - roi[center-2, center-2] roi[center+2, center-2] = np.full(3, 255) - roi[center+2, center-2] - # Mask out areas the eye can't see by drawing a circle boarder. - center = int(roi.shape[0] / 2) - circle_mask = np.zeros(roi.shape, dtype=np.uint8) - cv2.circle(circle_mask, (center, center), center, thickness = -1, color=(255,255,255)) - roi = np.minimum(roi, circle_mask) - return roi From 362ba32b4226884edb74509dde227c2e23f68d01 Mon Sep 17 00:00:00 2001 From: Marek Otahal Date: Wed, 23 Oct 2019 19:39:26 +0200 Subject: [PATCH 42/49] Eye: draw boundary around fovea in helper visualization --- py/htm/encoders/eye.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/py/htm/encoders/eye.py b/py/htm/encoders/eye.py index 28a36b2fa7..3c146a5876 100644 --- a/py/htm/encoders/eye.py +++ b/py/htm/encoders/eye.py @@ -523,6 +523,9 @@ def make_roi_pretty(self, roi=None): roi[center-2, center-2] = np.full(3, 255) - roi[center-2, center-2] roi[center+2, center-2] = np.full(3, 255) - roi[center+2, center-2] + # Draw a red circle where fovea (=high resolution parvocellular vision) boundary is + cv2.circle(roi, (center, center), radius=int(self.retina_diameter*self.fovea_scale), color=(255,0,0), thickness=3) + return roi From fddbcba4edaf2079b21f121269407285f9012709 Mon Sep 17 00:00:00 2001 From: Marek Otahal Date: Wed, 23 Oct 2019 19:55:05 +0200 Subject: [PATCH 43/49] Eye: apply rotation before retina processing --- py/htm/encoders/eye.py | 28 +++++++++++++--------------- 1 file changed, 13 insertions(+), 15 deletions(-) diff --git a/py/htm/encoders/eye.py b/py/htm/encoders/eye.py index 3c146a5876..a6f4cbe56d 100644 --- a/py/htm/encoders/eye.py +++ b/py/htm/encoders/eye.py @@ -445,8 +445,9 @@ def compute(self, position=None, rotation=None, scale=None): if scale is not None: self.scale=scale - # apply field of view (FOV) + # apply field of view (FOV), rotation self.roi = self._crop_roi() + self.roi = self.rotate_(self.roi, self.orientation) # Retina image transforms (Parvo & Magnocellular). self.retina.run(self.roi) @@ -455,6 +456,7 @@ def compute(self, position=None, rotation=None, scale=None): if self.magno_enc is not None: magno = self.retina.getMagno() + # Log Polar Transform. center = self.retina_diameter / 2 M = self.retina_diameter * self.fovea_scale @@ -472,13 +474,8 @@ def compute(self, position=None, rotation=None, scale=None): flags = cv2.WARP_FILL_OUTLIERS) magno = cv2.resize(magno, dsize=(self.output_diameter, self.output_diameter), interpolation = cv2.INTER_CUBIC) - # Apply rotation by rolling the images around axis 1. - rotation = self.output_diameter * self.orientation / (2 * math.pi) - rotation = int(round(rotation)) - if self.parvo_enc is not None: - self.parvo_img = np.roll(parvo, rotation, axis=0) - if self.magno_enc is not None: - self.magno_img = np.roll(magno, rotation, axis=0) + self.parvo_img = parvo + self.magno_img = magno # Encode images into SDRs. if self.parvo_enc is not None: @@ -508,13 +505,6 @@ def make_roi_pretty(self, roi=None): if roi is None: roi = self.roi - # Show the ROI, first rotate it like the eye is rotated. - angle = self.orientation * 360 / (2 * math.pi) - roi = self.roi[:,:,::-1] - rows, cols, color_depth = roi.shape - M = cv2.getRotationMatrix2D((cols / 2, rows / 2), angle, 1) - roi = cv2.warpAffine(roi, M, (cols,rows)) - # Invert 5 pixels in the center to show where the fovea is located. center = int(roi.shape[0] / 2) roi[center, center] = np.full(3, 255) - roi[center, center] @@ -528,6 +518,14 @@ def make_roi_pretty(self, roi=None): return roi + def rotate_(self, img, angle): + """ + rotate the image img, by angle in degrees + """ + angle = angle * 360 / (2 * math.pi) + rows, cols, color_depth = img.shape + M = cv2.getRotationMatrix2D((cols / 2, rows / 2), angle, 1) + return cv2.warpAffine(img, M, (cols,rows)) def plot(self, window_name='Eye', delay=1000): roi = self.make_roi_pretty() From 565e69e9ec90cc8f53ee5c2123754dbb2a9b072e Mon Sep 17 00:00:00 2001 From: Marek Otahal Date: Wed, 23 Oct 2019 20:06:51 +0200 Subject: [PATCH 44/49] Eye: use Retina's log sampling --- py/htm/encoders/eye.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/py/htm/encoders/eye.py b/py/htm/encoders/eye.py index a6f4cbe56d..5729566d0a 100644 --- a/py/htm/encoders/eye.py +++ b/py/htm/encoders/eye.py @@ -278,7 +278,8 @@ def __init__(self, self.retina = cv2.bioinspired.Retina_create( inputSize = (self.retina_diameter, self.retina_diameter), colorMode = color, - colorSamplingMethod = cv2.bioinspired.RETINA_COLOR_BAYER,) + colorSamplingMethod = cv2.bioinspired.RETINA_COLOR_BAYER, + useRetinaLogSampling = True,) print(self.retina.printSetup()) print() @@ -466,6 +467,7 @@ def compute(self, position=None, rotation=None, scale=None): M = M, flags = cv2.WARP_FILL_OUTLIERS) parvo = cv2.resize(parvo, dsize=(self.output_diameter, self.output_diameter), interpolation = cv2.INTER_CUBIC) + self.parvo_img = parvo if self.magno_enc is not None: magno = cv2.logPolar(magno, @@ -473,9 +475,7 @@ def compute(self, position=None, rotation=None, scale=None): M = M, flags = cv2.WARP_FILL_OUTLIERS) magno = cv2.resize(magno, dsize=(self.output_diameter, self.output_diameter), interpolation = cv2.INTER_CUBIC) - - self.parvo_img = parvo - self.magno_img = magno + self.magno_img = magno # Encode images into SDRs. if self.parvo_enc is not None: @@ -530,9 +530,11 @@ def rotate_(self, img, angle): def plot(self, window_name='Eye', delay=1000): roi = self.make_roi_pretty() cv2.imshow('Region Of Interest', roi) + if self.sparsityParvo > 0: # parvo enabled if self.color: cv2.imshow('Parvocellular', self.parvo_img[:,:,::-1]) + cv2.imshow('Parvo retinal representation', self.retina.getParvo()[:,:,::-1]) else: cv2.imshow('Parvocellular', self.parvo_img) idx = self.parvo_sdr.dense.astype(np.uint8).reshape(self.output_diameter, self.output_diameter)*255 From 116a91a74dc0e05d407c91d3e94ae660abd79d8b Mon Sep 17 00:00:00 2001 From: Marek Otahal Date: Wed, 23 Oct 2019 20:07:48 +0200 Subject: [PATCH 45/49] Eye: run example --- py/htm/encoders/eye.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/py/htm/encoders/eye.py b/py/htm/encoders/eye.py index 5729566d0a..4bdc05e7a4 100644 --- a/py/htm/encoders/eye.py +++ b/py/htm/encoders/eye.py @@ -337,6 +337,9 @@ def new_image(self, image): If String, will load image from file path. If numpy.ndarray, will attempt to cast to correct data type and dimensions. + + For demo, run: + python py/htm/encoders/eye.py py/tests/encoders/ronja_the_cat.jpg """ # Load image if needed. if isinstance(image, str): From be40a5dfa74c87588620e83b6dff5200450ea675 Mon Sep 17 00:00:00 2001 From: Marek Otahal Date: Thu, 24 Oct 2019 01:48:57 +0200 Subject: [PATCH 46/49] Eye: fix scaling where plot was broken with frational scaling. Using cv2.resize() rather than numpy's roi.resize() fixes the issue (numerical problems) --- py/htm/encoders/eye.py | 45 ++++++++++++++++++++++++++++-------------- 1 file changed, 30 insertions(+), 15 deletions(-) diff --git a/py/htm/encoders/eye.py b/py/htm/encoders/eye.py index 4bdc05e7a4..950edca52b 100644 --- a/py/htm/encoders/eye.py +++ b/py/htm/encoders/eye.py @@ -255,13 +255,13 @@ def __init__(self, # Argument resolution_factor is used to expand the sensor array so that # the fovea has adequate resolution. After log-polar transform image # is reduced by this factor back to the output_diameter. - self.resolution_factor = 3 + self.resolution_factor = 2 self.retina_diameter = int(self.resolution_factor * output_diameter) # Argument fovea_scale ... proportion of the image (ROI) which will be covered (seen) by # high-res fovea (parvo pathway) self.fovea_scale = 0.177 - assert(output_diameter // 2 * 2 == output_diameter) # Diameter must be an even number. - assert(self.retina_diameter // 2 * 2 == self.retina_diameter) # (Resolution Factor X Diameter) must be an even number. + assert(output_diameter % 2 == 0) # Diameter must be an even number. + assert(self.retina_diameter % 2 == 0) # (Resolution Factor X Diameter) must be an even number. assert(sparsityParvo >= 0 and sparsityParvo <= 1.0) if sparsityParvo > 0: assert(sparsityParvo * (self.retina_diameter **2) > 0) @@ -327,7 +327,7 @@ def __init__(self, ## motor-control variables (must be user specified) self.position = (0,0) # can use self.center_view(), self.random_view() - self.scale = 1.0 # represents "zoom" aka distance from the object/image #FIXME broken for != 1.0 + self.scale = 1.0 # represents "zoom" aka distance from the object/image self.orientation= 0 # angle between image/object and camera, in deg @@ -360,12 +360,16 @@ def new_image(self, image): assert(self.image.shape[2] == 3) # Color images only. self.reset() self.center_view() + self.roi = None + assert(min(self.image.shape[:2]) >= self.retina_diameter) + def center_view(self): """Center the view over the image""" self.orientation = 0 self.position = (self.image.shape[0]/2., self.image.shape[1]/2.) self.scale = np.min(np.divide(self.image.shape[:2], self.retina_diameter)) + self.roi = None #changing center breaks prev ROI def randomize_view(self, scale_range=None): """Set the eye's view point to a random location""" @@ -377,8 +381,10 @@ def randomize_view(self, scale_range=None): roi_radius = self.scale * self.retina_diameter / 2 self.position = [random.uniform(roi_radius, dim - roi_radius) for dim in self.image.shape[:2]] + self.roi = None + - def _crop_roi(self): + def _crop_roi(image, position, diameter, scale): """ Crop to Region Of Interest (ROI) which contains the whole field of view. Adds a black circular boarder to mask out areas which the eye can't see. @@ -394,20 +400,23 @@ def _crop_roi(self): See also, @see make_roi_pretty() """ - assert(self.image is not None) + assert(isinstance(image, np.ndarray)) + assert(diameter > 0) + assert(scale > 0) - r = int(round(self.scale * self.retina_diameter / 2)) - x, y = self.position + r = int(round(scale * diameter / 2)) + assert(r > 0) + x, y = position x = int(round(x)) y = int(round(y)) - x_max, y_max, color_depth = self.image.shape + x_max, y_max, color_depth = image.shape # Find the boundary of the ROI and slice out the image. x_low = max(0, x-r) x_high = min(x_max, x+r) y_low = max(0, y-r) y_high = min(y_max, y+r) - image_slice = self.image[x_low : x_high, y_low : y_high] + image_slice = image[x_low : x_high, y_low : y_high] # Make the ROI and insert the image into it. roi = np.zeros((2*r, 2*r, 3,), dtype=np.uint8) @@ -423,7 +432,7 @@ def _crop_roi(self): roi[x_offset:x_offset+x_shape, y_offset:y_offset+y_shape] = image_slice # Rescale the ROI to remove the scaling effect. - roi.resize( (self.retina_diameter, self.retina_diameter, 3)) + roi = cv2.resize(roi, (diameter, diameter), interpolation = cv2.INTER_AREA) # Mask out areas the eye can't see by drawing a circle boarder. # this represents the "shape" of the sensor/eye (comment out to leave rectangural) @@ -441,6 +450,7 @@ def compute(self, position=None, rotation=None, scale=None): with the provided value. Returns tuple (SDR parvo, SDR magno) """ + assert(self.image is not None) # set position if position is not None: self.position = position @@ -450,8 +460,9 @@ def compute(self, position=None, rotation=None, scale=None): self.scale=scale # apply field of view (FOV), rotation - self.roi = self._crop_roi() - self.roi = self.rotate_(self.roi, self.orientation) + self.roi = self.rotate_(self.image, self.orientation) + print(self.scale) + self.roi = Eye._crop_roi(self.roi, self.position, self.retina_diameter, self.scale) # Retina image transforms (Parvo & Magnocellular). self.retina.run(self.roi) @@ -464,6 +475,7 @@ def compute(self, position=None, rotation=None, scale=None): # Log Polar Transform. center = self.retina_diameter / 2 M = self.retina_diameter * self.fovea_scale + M = min(M, self.output_diameter) if self.parvo_enc is not None: parvo = cv2.logPolar(parvo, center = (center, center), @@ -507,6 +519,7 @@ def make_roi_pretty(self, roi=None): """ if roi is None: roi = self.roi + assert(roi is not None) # Invert 5 pixels in the center to show where the fovea is located. center = int(roi.shape[0] / 2) @@ -521,15 +534,18 @@ def make_roi_pretty(self, roi=None): return roi + def rotate_(self, img, angle): """ rotate the image img, by angle in degrees """ + assert(isinstance(img, np.ndarray)) angle = angle * 360 / (2 * math.pi) rows, cols, color_depth = img.shape M = cv2.getRotationMatrix2D((cols / 2, rows / 2), angle, 1) return cv2.warpAffine(img, M, (cols,rows)) + def plot(self, window_name='Eye', delay=1000): roi = self.make_roi_pretty() cv2.imshow('Region Of Interest', roi) @@ -560,7 +576,7 @@ def small_random_movement(self): self.position[0] + random.gauss(1, .75), self.position[1] + random.gauss(1, .75),) self.orientation += random.uniform(-max_change_angle, max_change_angle) - self.scale = 1 + self.scale += random.gauss(0, 0.1) return (self.position, self.orientation, self.scale) @@ -619,7 +635,6 @@ def _get_images(path): eye.position=(400,400) for i in range(10): pos,rot,sc = eye.small_random_movement() - sc=1.0 #FIXME broken (only plot?) whenever scale != 1.0 (sdrParvo, sdrMagno) = eye.compute(pos,rot,sc) #TODO derive from Encoder eye.plot(delay=5000) print("Sparsity parvo: {}".format(len(eye.parvo_sdr.sparse)/np.product(eye.dimensions))) From d7c898ecc0d432b92612700afd6f3d038a1ab8be Mon Sep 17 00:00:00 2001 From: Marek Otahal Date: Thu, 24 Oct 2019 01:54:51 +0200 Subject: [PATCH 47/49] Eye: bigger steps in random walk --- py/htm/encoders/eye.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/py/htm/encoders/eye.py b/py/htm/encoders/eye.py index 950edca52b..5f0b9d27ec 100644 --- a/py/htm/encoders/eye.py +++ b/py/htm/encoders/eye.py @@ -461,7 +461,6 @@ def compute(self, position=None, rotation=None, scale=None): # apply field of view (FOV), rotation self.roi = self.rotate_(self.image, self.orientation) - print(self.scale) self.roi = Eye._crop_roi(self.roi, self.position, self.retina_diameter, self.scale) # Retina image transforms (Parvo & Magnocellular). @@ -571,10 +570,10 @@ def small_random_movement(self): """returns small difference in position, rotation, scale. This is naive "saccadic" movements. """ - max_change_angle = (2*3.14159) / 500 + max_change_angle = (2*math.pi) / 100 self.position = ( - self.position[0] + random.gauss(1, .75), - self.position[1] + random.gauss(1, .75),) + self.position[0] + random.gauss(1.2, .75), + self.position[1] + random.gauss(1.2, .75),) self.orientation += random.uniform(-max_change_angle, max_change_angle) self.scale += random.gauss(0, 0.1) return (self.position, self.orientation, self.scale) @@ -630,7 +629,7 @@ def _get_images(path): eye.reset() print("Loading image %s"%img_path) eye.new_image(img_path) - eye.fovea_scale = 0.177 #TODO find which value? + eye.fovea_scale = 0.077 #TODO find which value? #eye.center_view() eye.position=(400,400) for i in range(10): From b4100a2f865e95ba0fcbd6ea605892d613a5dcfd Mon Sep 17 00:00:00 2001 From: Marek Otahal Date: Thu, 24 Oct 2019 01:57:44 +0200 Subject: [PATCH 48/49] eye: plot also the whole scene --- py/htm/encoders/eye.py | 1 + 1 file changed, 1 insertion(+) diff --git a/py/htm/encoders/eye.py b/py/htm/encoders/eye.py index 5f0b9d27ec..57108c6407 100644 --- a/py/htm/encoders/eye.py +++ b/py/htm/encoders/eye.py @@ -548,6 +548,7 @@ def rotate_(self, img, angle): def plot(self, window_name='Eye', delay=1000): roi = self.make_roi_pretty() cv2.imshow('Region Of Interest', roi) + cv2.imshow('Whole image', self.image) if self.sparsityParvo > 0: # parvo enabled if self.color: From 3a6461c12048c0b9ddc76102363d8829f585f0e9 Mon Sep 17 00:00:00 2001 From: Marek Otahal Date: Thu, 24 Oct 2019 02:12:43 +0200 Subject: [PATCH 49/49] Eye: compute has image is argument --- py/htm/encoders/eye.py | 18 +++++++++--------- py/tests/encoders/eye_test.py | 4 +--- 2 files changed, 10 insertions(+), 12 deletions(-) diff --git a/py/htm/encoders/eye.py b/py/htm/encoders/eye.py index 57108c6407..953d457205 100644 --- a/py/htm/encoders/eye.py +++ b/py/htm/encoders/eye.py @@ -331,7 +331,7 @@ def __init__(self, self.orientation= 0 # angle between image/object and camera, in deg - def new_image(self, image): + def _new_image(self, image): """ Argument image ... If String, will load image from file path. @@ -362,6 +362,7 @@ def new_image(self, image): self.center_view() self.roi = None assert(min(self.image.shape[:2]) >= self.retina_diameter) + return self.image def center_view(self): @@ -444,13 +445,16 @@ def _crop_roi(image, position, diameter, scale): return roi - def compute(self, position=None, rotation=None, scale=None): + def compute(self, img, position=None, rotation=None, scale=None): """ + Argument img - image to load. String/data, see _new_image() Arguments position, rotation, scale: optional, if not None, the self.xxx is overriden with the provided value. Returns tuple (SDR parvo, SDR magno) """ + self.image = self._new_image(img) assert(self.image is not None) + # set position if position is not None: self.position = position @@ -508,7 +512,7 @@ def compute(self, position=None, rotation=None, scale=None): return (self.parvo_sdr, self.magno_sdr) - def make_roi_pretty(self, roi=None): + def _make_roi_pretty(self, roi): """ Makes the eye's view look more presentable. - Adds 5 dots to the center of the image to show where the fovea is. @@ -516,8 +520,6 @@ def make_roi_pretty(self, roi=None): Returns an RGB image. See _crop_roi() """ - if roi is None: - roi = self.roi assert(roi is not None) # Invert 5 pixels in the center to show where the fovea is located. @@ -546,7 +548,7 @@ def rotate_(self, img, angle): def plot(self, window_name='Eye', delay=1000): - roi = self.make_roi_pretty() + roi = self._make_roi_pretty(self.roi) cv2.imshow('Region Of Interest', roi) cv2.imshow('Whole image', self.image) @@ -628,14 +630,12 @@ def _get_images(path): color=True) for img_path in images: eye.reset() - print("Loading image %s"%img_path) - eye.new_image(img_path) eye.fovea_scale = 0.077 #TODO find which value? #eye.center_view() eye.position=(400,400) for i in range(10): pos,rot,sc = eye.small_random_movement() - (sdrParvo, sdrMagno) = eye.compute(pos,rot,sc) #TODO derive from Encoder + (sdrParvo, sdrMagno) = eye.compute(img_path, pos,rot,sc) #TODO derive from Encoder eye.plot(delay=5000) print("Sparsity parvo: {}".format(len(eye.parvo_sdr.sparse)/np.product(eye.dimensions))) print("Sparsity magno: {}".format(len(eye.magno_sdr.sparse)/np.product(eye.dimensions))) diff --git a/py/tests/encoders/eye_test.py b/py/tests/encoders/eye_test.py index 7e7689c23d..11d85f3781 100644 --- a/py/tests/encoders/eye_test.py +++ b/py/tests/encoders/eye_test.py @@ -14,13 +14,11 @@ def testBasicUsage(self): eye = Eye() eye.reset() FILE=os.path.join('py','tests','encoders','ronja_the_cat.jpg') - eye.new_image(FILE) - eye.scale = 0.5 #eye.center_view() eye.position = (400,400) for _ in range(10): pos,rot,sc = eye.small_random_movement() - (sdrParvo, sdrMagno) = eye.compute(pos,rot,sc) + (sdrParvo, sdrMagno) = eye.compute(FILE, pos,rot,sc) #eye.plot(delay=500) print("Sparsity parvo: {}".format(len(eye.parvo_sdr.sparse)/np.product(eye.parvo_sdr.dimensions))) print("Sparsity magno: {}".format(len(eye.magno_sdr.sparse)/np.product(eye.magno_sdr.dimensions)))