Cameras¶
__init__(self, width=64, height=32, fov=30, colorsFadeWithDistance=1.0, sizeFadeWithDistance=0.8, reflectGround=True, reflectSky=False, max_range=1000, name='camera', samples=1, **kwargs)
special
¶
A camera device.
Parameters:
Name | Type | Description | Default |
---|---|---|---|
width |
int |
width of camera in pixels |
64 |
height |
int |
height of camera in pixels |
32 |
fov |
float |
width of camera field of view in degrees. Can be 180 or even 360 for wide angle cameras. |
30 |
colorsFadeWithDistance |
float |
colors get darker faster with larger value |
1.0 |
sizeFadeWithDistance |
float |
size gets smaller faster with with larger value |
0.8 |
reflectGround |
bool |
ground reflects for 3D point cloud |
True |
reflectSky |
bool |
sky reflects for 3D point cloud |
False |
max_range |
int |
maximum range of camera |
1000 |
name |
str |
the name of the camera |
'camera' |
samples |
int |
how many pixels should it sample |
1 |
Source code in robots/devices/cameras.py
def __init__(
self,
width=64,
height=32,
fov=30,
colorsFadeWithDistance=1.0,
sizeFadeWithDistance=0.8,
reflectGround=True,
reflectSky=False,
max_range=1000,
name="camera",
samples=1,
**kwargs
):
"""
A camera device.
Args:
width (int): width of camera in pixels
height (int): height of camera in pixels
fov (float): width of camera field of view in degrees. Can be
180 or even 360 for wide angle cameras.
colorsFadeWithDistance (float): colors get darker
faster with larger value
sizeFadeWithDistance (float): size gets smaller faster
with with larger value
reflectGround (bool): ground reflects for 3D point cloud
reflectSky (bool): sky reflects for 3D point cloud
max_range (int): maximum range of camera
name (str): the name of the camera
samples (int): how many pixels should it sample
Note: currently the camera faces forward.
"""
config = {
"width": width,
"height": height,
"fov": fov,
"colorsFadeWithDistance": colorsFadeWithDistance,
"sizeFadeWithDistance": sizeFadeWithDistance,
"reflectGround": reflectGround,
"reflectSky": reflectSky,
"max_range": max_range,
"name": name,
"samples": samples,
}
config.update(kwargs)
self._watcher = None
self.robot = None
self.initialize()
self.from_json(config)
display(self, type='color')
¶
Display an image of the camera.
Parameters:
Name | Type | Description | Default |
---|---|---|---|
type |
str |
the return type of image. Can be "color" or "depth" |
'color' |
Source code in robots/devices/cameras.py
def display(self, type="color"):
"""
Display an image of the camera.
Args:
type (str): the return type of image. Can be "color"
or "depth"
"""
image = self.get_image(type=type)
display(image)
draw(self, backend)
¶
Draw the device on the backend.
Parameters:
Name | Type | Description | Default |
---|---|---|---|
backend |
Backend |
an aitk drawing backend |
required |
Source code in robots/devices/cameras.py
def draw(self, backend):
"""
Draw the device on the backend.
Args:
backend (Backend): an aitk drawing backend
NOTE: Currently, cameras are fixed at 0,0 and face forwards.
"""
backend.set_fill(Color(0, 64, 0))
backend.strokeStyle(None, 0)
backend.draw_rect(5.0, -3.33, 1.33, 6.33)
# Note angle in sim world is opposite in graphics:
hits = self.robot.cast_ray(
self.robot.x,
self.robot.y,
PI_OVER_2 - self.robot.a + self.fov / 2,
self.max_range,
)
if hits:
p = rotate_around(0, 0, hits[-1].distance, -self.fov / 2,)
else:
p = rotate_around(0, 0, self.max_range, -self.fov / 2,)
backend.draw_line(0, 0, p[0], p[1])
# Note angle in sim world is opposite in graphics:
hits = self.robot.cast_ray(
self.robot.x,
self.robot.y,
PI_OVER_2 - self.robot.a - self.fov / 2,
self.max_range,
)
if hits:
p = rotate_around(0, 0, hits[-1].distance, self.fov / 2,)
else:
p = rotate_around(0, 0, self.max_range, self.fov / 2,)
backend.draw_line(0, 0, p[0], p[1])
from_json(self, config)
¶
Set the settings from a device config.
Parameters:
Name | Type | Description | Default |
---|---|---|---|
config |
dict |
a config dictionary |
required |
Source code in robots/devices/cameras.py
def from_json(self, config):
"""
Set the settings from a device config.
Args:
config (dict): a config dictionary
"""
valid_keys = set([
"width", "name", "height", "colorsFadeWithDistance",
"sizeFadeWithDistance", "reflectGround", "reflectSky",
"fov", "max_range", "samples", "position", "class"
])
self.verify_config(valid_keys, config)
if "width" in config:
self.cameraShape[0] = config["width"]
if "height" in config:
self.cameraShape[1] = config["height"]
if "colorsFadeWithDistance" in config:
self.colorsFadeWithDistance = config["colorsFadeWithDistance"]
if "sizeFadeWithDistance" in config:
self.sizeFadeWithDistance = config["sizeFadeWithDistance"]
if "reflectGround" in config:
self.reflectGround = config["reflectGround"]
if "reflectSky" in config:
self.reflectSky = config["reflectSky"]
if "fov" in config:
self.set_fov(config["fov"]) # degrees
if "max_range" in config:
self.max_range = config["max_range"]
if "samples" in config:
self.samples = config["samples"]
if "name" in config:
self.name = config["name"]
if "position" in config:
self.position = config["position"]
get_fov(self)
¶
Get the field of view angle in degrees.
Source code in robots/devices/cameras.py
def get_fov(self):
"""
Get the field of view angle in degrees.
"""
return self.fov * ONE80_OVER_PI
get_height(self)
¶
Get the height in pixels of the camera.
Source code in robots/devices/cameras.py
def get_height(self):
"""
Get the height in pixels of the camera.
"""
return self.cameraShape[1]
get_image(self, type='color')
¶
Get an image of the camera.
Parameters:
Name | Type | Description | Default |
---|---|---|---|
type |
str |
the return type of image. Can be "color" or "depth" |
'color' |
Source code in robots/devices/cameras.py
def get_image(self, type="color"):
"""
Get an image of the camera.
Args:
type (str): the return type of image. Can be "color"
or "depth"
"""
try:
from PIL import Image
except ImportError:
print("Pillow (PIL) module not available; get_image() unavailable")
return
# Lazy; only get the data when we need it:
self._update()
if self.robot.world._ground_image is not None:
area = list(self._get_visible_area())
else:
area = None
pic = Image.new("RGBA", (self.cameraShape[0], self.cameraShape[1]))
pic_pixels = pic.load()
# FIXME: probably should have a specific size rather than scale it to world
size = max(self.robot.world.width, self.robot.world.height)
hcolor = None
# draw non-robot walls first:
for i in range(self.cameraShape[0]):
hits = [hit for hit in self.hits[i] if hit.height == 1.0] # only walls
if len(hits) == 0:
continue
hit = hits[-1] # get closest
high = None
hcolor = None
if hit:
if self.fov < PI_OVER_2:
# Orthoginal distance to camera:
angle = hit.angle
hit_distance = abs(hit.distance * math.sin(angle))
else:
hit_distance = hit.distance
distance_ratio = 1.0 - hit_distance / size
s = distance_ratio * self.sizeFadeWithDistance
sc = distance_ratio * self.colorsFadeWithDistance
if type == "color":
r = hit.color.red * sc
g = hit.color.green * sc
b = hit.color.blue * sc
elif type == "depth":
r = 255 * distance_ratio
g = 255 * distance_ratio
b = 255 * distance_ratio
else:
avg = (hit.color.red + hit.color.green + hit.color.blue) / 3.0
r = avg * sc
g = avg * sc
b = avg * sc
hcolor = Color(r, g, b)
high = (1.0 - s) * self.cameraShape[1]
else:
high = 0
horizon = self.cameraShape[1] / 2
for j in range(self.cameraShape[1]):
dist = max(min(abs(j - horizon) / horizon, 1.0), 0.0)
if j < high / 2: # sky
if type == "depth":
if self.reflectSky:
color = Color(255 * dist)
else:
color = Color(0)
elif type == "color":
color = Color(0, 0, 128)
else:
color = Color(128 / 3)
pic_pixels[i, j] = color.to_tuple()
elif j < self.cameraShape[1] - high / 2: # hit
if hcolor is not None:
pic_pixels[i, j] = hcolor.to_tuple()
else: # ground
if type == "depth":
if self.reflectGround:
color = Color(255 * dist)
else:
color = Color(0)
elif type == "color":
color = self._get_ground_color(area, i, j)
else:
color = Color(128 / 3)
pic_pixels[i, j] = color.to_tuple()
# Other robots, draw on top of walls:
self.obstacles = {}
for i in range(self.cameraShape[0]):
closest_wall_dist = self._find_closest_wall(self.hits[i])
hits = [hit for hit in self.hits[i] if hit.height < 1.0] # obstacles
for hit in hits:
if hit.distance > closest_wall_dist:
# Behind wall, try next
continue
if self.fov < PI_OVER_2:
angle = hit.angle
hit_distance = abs(hit.distance * math.sin(angle))
else: # perspective
hit_distance = hit.distance
distance_ratio = 4.0 - hit_distance / size
s = distance_ratio * self.sizeFadeWithDistance
sc = distance_ratio * self.colorsFadeWithDistance
distance_to = self.cameraShape[1] / 2 * (4.0 - sc)
# scribbler was 30, so 0.23 height ratio
# height is ratio, 0 to 1
height = round(hit.height * self.cameraShape[1] / 2.0 * s)
if type == "color":
r = hit.color.red * sc
g = hit.color.green * sc
b = hit.color.blue * sc
elif type == "depth":
r = 255 * distance_ratio
g = 255 * distance_ratio
b = 255 * distance_ratio
else:
avg = (hit.color.red + hit.color.green + hit.color.blue) / 3.0
r = avg * sc
g = avg * sc
b = avg * sc
hcolor = Color(r, g, b)
horizon = self.cameraShape[1] / 2
self._record_obstacle(
hit.robot,
i,
self.cameraShape[1] - 1 - round(distance_to),
self.cameraShape[1] - height - 1 - 1 - round(distance_to),
)
if not hit.robot.has_image():
for j in range(height):
pic_pixels[
i, self.cameraShape[1] - j - 1 - round(distance_to)
] = hcolor.to_tuple()
if self.bulbs_are_visible:
self._show_bulbs(pic)
if self.food_is_visible:
self._show_food(pic)
self._show_obstacles(pic)
return pic
get_max(self)
¶
Get the maximum distance in CM the camera can see.
Source code in robots/devices/cameras.py
def get_max(self):
"""
Get the maximum distance in CM the camera can see.
"""
return self.max_range
get_name(self)
¶
Get the name of the camera.
Source code in robots/devices/cameras.py
def get_name(self):
"""
Get the name of the camera.
"""
return self.name
get_point_cloud(self)
¶
Get a 3D point cloud from the camera data.
Returns a list of [x, y, distance, red, green, blue] for each (x,y) of the camera.
Source code in robots/devices/cameras.py
def get_point_cloud(self):
"""
Get a 3D point cloud from the camera data.
Returns a list of [x, y, distance, red, green, blue]
for each (x,y) of the camera.
"""
depth_pic = self.get_image("depth")
depth_pixels = depth_pic.load()
color_pic = self.get_image("color")
color_pixels = color_pic.load()
points = []
for x in range(self.cameraShape[0]):
for y in range(self.cameraShape[1]):
dist_color = depth_pixels[x, y]
color = color_pixels[x, y]
if dist_color[0] != 255:
points.append(
[
self.cameraShape[0] - x - 1,
self.cameraShape[1] - y - 1,
dist_color[0],
color[0],
color[1],
color[2],
]
)
return points
get_widget(self, **kwargs)
¶
Return the dynamically updating widget.
Parameters:
Name | Type | Description | Default |
---|---|---|---|
title |
str |
title of device |
required |
Source code in robots/devices/cameras.py
def get_widget(self, **kwargs):
"""
Return the dynamically updating widget.
Args:
title (str): title of device
"""
from ..watchers import CameraWatcher
if self._watcher is None:
self._watcher = CameraWatcher(self, **kwargs)
self.robot.world._watchers.append(self._watcher)
return self._watcher.get_widget()
else:
return self._watcher.get_widget(**kwargs)
get_width(self)
¶
Get the width in pixels of the camera.
Source code in robots/devices/cameras.py
def get_width(self):
"""
Get the width in pixels of the camera.
"""
return self.cameraShape[0]
initialize(self)
¶
Internal method to set all settings to default values.
Source code in robots/devices/cameras.py
def initialize(self):
"""
Internal method to set all settings to default values.
"""
# FIXME: camera is fixed at (0,0) facing forward
self.type = "camera"
self.bulbs_are_visible = True
self.food_is_visible = True
self.time = 0.0
self.cameraShape = [64, 32]
self.position = [0, 0]
self.max_range = 1000
self.samples = 1
self.name = "camera"
# 0 = no fade, 1.0 = max fade
self.colorsFadeWithDistance = 0.9
self.sizeFadeWithDistance = 1.0
self.reflectGround = True
self.reflectSky = False
self.set_fov(60) # degrees
self.reset()
reset(self)
¶
Internal method to reset the camera data.
Source code in robots/devices/cameras.py
def reset(self):
"""
Internal method to reset the camera data.
"""
self.hits = [[] for i in range(self.cameraShape[0])]
set_fov(self, angle)
¶
Set the field of view angle in degrees of the camera.
Parameters:
Name | Type | Description | Default |
---|---|---|---|
angle |
float |
angle in degrees of field of view |
required |
Source code in robots/devices/cameras.py
def set_fov(self, angle):
"""
Set the field of view angle in degrees of the camera.
Args:
angle (float): angle in degrees of field of view
"""
# given in degrees
# save in radians
# scale = min(max(angle / 6.0, 0.0), 1.0)
self.fov = angle * PI_OVER_180
# self.sizeFadeWithDistance = scale
self.reset()
set_height(self, height)
¶
Set the height of the camera in pixels.
Parameters:
Name | Type | Description | Default |
---|---|---|---|
height |
int |
height of camera in pixels |
required |
Source code in robots/devices/cameras.py
def set_height(self, height):
"""
Set the height of the camera in pixels.
Args:
height (int): height of camera in pixels
"""
self.cameraShape[1] = height
self.reset()
set_max(self, max_range)
¶
Set the maximum distance the camera can see.
Parameters:
Name | Type | Description | Default |
---|---|---|---|
max_range |
float |
distance (in CM) the camera can see |
required |
Source code in robots/devices/cameras.py
def set_max(self, max_range):
"""
Set the maximum distance the camera can see.
Args:
max_range (float): distance (in CM) the camera can see
"""
self.max_range = max_range
set_name(self, name)
¶
Set the name of the camera.
Parameters:
Name | Type | Description | Default |
---|---|---|---|
name |
str |
the name of the camera |
required |
Source code in robots/devices/cameras.py
def set_name(self, name):
"""
Set the name of the camera.
Args:
name (str): the name of the camera
"""
self.name = name
set_size(self, width, height)
¶
Set the height and width of the camera in pixels.
Parameters:
Name | Type | Description | Default |
---|---|---|---|
width |
int |
width of camera in pixels |
required |
height |
int |
height of camera in pixels |
required |
Source code in robots/devices/cameras.py
def set_size(self, width, height):
"""
Set the height and width of the camera in pixels.
Args:
width (int): width of camera in pixels
height (int): height of camera in pixels
"""
self.cameraShape[0] = width
self.cameraShape[1] = height
self.reset()
set_width(self, width)
¶
Set the width of the camera in pixels.
Parameters:
Name | Type | Description | Default |
---|---|---|---|
width |
int |
width of camera in pixels |
required |
Source code in robots/devices/cameras.py
def set_width(self, width):
"""
Set the width of the camera in pixels.
Args:
width (int): width of camera in pixels
"""
self.cameraShape[0] = width
self.reset()
to_json(self)
¶
Save the internal settings to a config dictionary.
Source code in robots/devices/cameras.py
def to_json(self):
"""
Save the internal settings to a config dictionary.
"""
return {
"class": self.__class__.__name__,
"width": self.cameraShape[0],
"height": self.cameraShape[1],
"colorsFadeWithDistance": self.colorsFadeWithDistance,
"sizeFadeWithDistance": self.sizeFadeWithDistance,
"reflectGround": self.reflectGround,
"reflectSky": self.reflectSky,
"fov": self.fov * ONE80_OVER_PI, # save in degrees
"max_range": self.max_range,
"samples": self.samples,
"name": self.name,
"position": self.position,
}
update(self, draw_list=None)
¶
Cameras operate in a lazy way: they don't actually update until needed because they are so expensive.
Parameters:
Name | Type | Description | Default |
---|---|---|---|
draw_list |
list |
optional. If given, then the method can add to it for drawing later. |
None |
Source code in robots/devices/cameras.py
def update(self, draw_list=None):
"""
Cameras operate in a lazy way: they don't actually update
until needed because they are so expensive.
Args:
draw_list (list): optional. If given, then the
method can add to it for drawing later.
"""
pass
watch(self, **kwargs)
¶
Create a dynamically updating view of this device.
Parameters:
Name | Type | Description | Default |
---|---|---|---|
title |
str |
title of device |
required |
Source code in robots/devices/cameras.py
def watch(self, **kwargs):
"""
Create a dynamically updating view
of this device.
Args:
title (str): title of device
"""
if self.robot is None or self.robot.world is None:
raise Exception("can't watch device until added to robot, and robot is in world")
# Make the watcher if doesn't exist:
self.get_widget(**kwargs)
self._watcher.watch()
__init__(self, width=15, height=15, name='ground-camera', **kwargs)
special
¶
A downward-facing camera device.
Parameters:
Name | Type | Description | Default |
---|---|---|---|
width |
int |
width of camera in pixels |
15 |
height |
int |
height of camera in pixels |
15 |
name |
str |
the name of the camera |
'ground-camera' |
Source code in robots/devices/cameras.py
def __init__(self, width=15, height=15, name="ground-camera", **kwargs):
"""
A downward-facing camera device.
Args:
width (int): width of camera in pixels
height (int): height of camera in pixels
name (str): the name of the camera
"""
config = {
"width": width,
"height": height,
"name": name,
}
self._watcher = None
self.robot = None
self.initialize()
self.from_json(config)
draw(self, backend)
¶
Draw the device on the backend.
Parameters:
Name | Type | Description | Default |
---|---|---|---|
backend |
Backend |
an aitk drawing backend |
required |
Source code in robots/devices/cameras.py
def draw(self, backend):
"""
Draw the device on the backend.
Args:
backend (Backend): an aitk drawing backend
"""
left = -(self.cameraShape[0] // 2) / self.robot.world.scale
upper = -(self.cameraShape[1] // 2) / self.robot.world.scale
right = (self.cameraShape[0] // 2) / self.robot.world.scale
lower = (self.cameraShape[1] // 2) / self.robot.world.scale
backend.strokeStyle(Color(128), 1)
backend.draw_line(left, upper, right, upper)
backend.draw_line(right, upper, right, lower)
backend.draw_line(right, lower, left, lower)
backend.draw_line(left, lower, left, upper)
from_json(self, config)
¶
Set the settings from a device config.
Parameters:
Name | Type | Description | Default |
---|---|---|---|
config |
dict |
a config dictionary |
required |
Source code in robots/devices/cameras.py
def from_json(self, config):
"""
Set the settings from a device config.
Args:
config (dict): a config dictionary
"""
if "width" in config:
self.cameraShape[0] = config["width"]
if "height" in config:
self.cameraShape[1] = config["height"]
if "name" in config:
self.name = config["name"]
get_image(self, type=None)
¶
Get an image of the camera.
Parameters:
Name | Type | Description | Default |
---|---|---|---|
type |
str |
the return type of image. Can be "color" or "depth" |
None |
Source code in robots/devices/cameras.py
def get_image(self, type=None):
"""
Get an image of the camera.
Args:
type (str): the return type of image. Can be "color"
or "depth"
"""
# FIXME: would be faster to trim image down
# before rotating
center = (
self.robot.x * self.robot.world.scale,
self.robot.y * self.robot.world.scale,
)
rotated_image = self.robot.world._ground_image.rotate(
(self.robot.a - math.pi / 4 * 6) * (ONE80_OVER_PI), center=center,
)
left = center[0] - self.cameraShape[0] // 2
right = center[0] + self.cameraShape[0] // 2
upper = center[1] - self.cameraShape[1] // 2
lower = center[1] + self.cameraShape[1] // 2
return rotated_image.crop((left, upper, right, lower))
initialize(self)
¶
Internal method to set all settings to default values.
Source code in robots/devices/cameras.py
def initialize(self):
"""
Internal method to set all settings to default values.
"""
self.type = "ground-camera"
self.time = 0.0
self.cameraShape = [15, 15]
self.name = "ground-camera"
to_json(self)
¶
Save the internal settings to a config dictionary.
Source code in robots/devices/cameras.py
def to_json(self):
"""
Save the internal settings to a config dictionary.
"""
return {
"class": self.__class__.__name__,
"width": self.cameraShape[0],
"height": self.cameraShape[1],
"name": self.name,
}
update(self, draw_list=None)
¶
Update the device.
Parameters:
Name | Type | Description | Default |
---|---|---|---|
draw_list |
list |
optional. If given, then the method can add to it for drawing later. |
None |
Cameras operate in a lazy way: they don't actually update until needed because they are so expensive.
Source code in robots/devices/cameras.py
def update(self, draw_list=None):
"""
Update the device.
Args:
draw_list (list): optional. If given, then the
method can add to it for drawing later.
Cameras operate in a lazy way: they don't actually update
until needed because they are so expensive.
"""
pass