Merge pull request #239 from 3b1b/alt-calc

Alt calc
This commit is contained in:
Grant Sanderson 2018-05-21 12:14:09 -07:00 committed by GitHub
commit 10c607af73
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
23 changed files with 484 additions and 801 deletions

View file

@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
from big_ol_pile_of_manim_imports import *
@ -317,7 +316,7 @@ class NumberlineTransformationScene(ZoomedScene):
zcbr_group.add(mini_line_copy, mini_line)
anims += [FadeIn(mini_line), FadeIn(mini_line_copy)]
# Add tiny coordinates
# Add tiny coordiantes
if local_coordinate_values is None:
local_coordinate_values = [x]
local_coordinates = self.get_local_coordinates(
@ -661,7 +660,7 @@ class StartingCalc101(PiCreatureScene):
circumference = TexMobject("2\\pi r")
circumference.move_to(unfilled_circle)
equation = TexMobject(
"{d (\\pi r^2) \\over dr} = 2\\pi r",
"{d (\\pi r^2) \\over dx} = 2\\pi r",
tex_to_color_map={
"\\pi r^2": BLUE_D,
"2\\pi r": YELLOW,
@ -876,8 +875,8 @@ class Wrapper(Scene):
}
def construct(self):
rect = self.rect = ScreenRectangle(height=self.screen_height)
title = self.title = TextMobject(self.title, **self.title_kwargs)
rect = ScreenRectangle(height=self.screen_height)
title = TextMobject(self.title, **self.title_kwargs)
title.to_edge(UP)
rect.next_to(title, DOWN)
@ -1563,11 +1562,11 @@ class TalkThroughXSquaredExample(IntroduceTransformationView):
la, ra = ra, la
if factor < 0:
kwargs = {
"path_arc": np.pi,
"path_arc": -np.pi,
"use_rectangular_stem": False,
}
la = Arrow(UP, DOWN, **kwargs)
ra = Arrow(DOWN, UP, **kwargs)
la = Arrow(DOWN, UP, **kwargs)
ra = Arrow(UP, DOWN, **kwargs)
for arrow in la, ra:
arrow.pointwise_become_partial(arrow, 0, 0.9)
arrow.tip.scale(2)
@ -1872,8 +1871,9 @@ class ZoomInMoreAndMoreToZero(ZoomInOnXSquaredNearZero):
frame.scale_to_fit_height(factor * zoomed_display_height)
self.local_coordinate_num_decimal_places = int(-np.log10(factor))
zoom_words = TextMobject(
"Zoomed", "{:,}x \\\\".format(int(1.0 / factor)),
"near 0",
"Zoomed ", "{:,}".format(int(1.0 / factor)),
"x \\\\", "near 0",
arg_separator=""
)
zoom_words.next_to(self.zoomed_display, DOWN)
@ -1954,14 +1954,6 @@ class XSquaredForNegativeInput(TalkThroughXSquaredExample):
sample_dots.set_fill(opacity=0.8)
self.play(LaggedStart(DrawBorderThenFill, sample_dots))
self.play(LaggedStart(
ApplyFunction, sample_dots[len(sample_dots) / 2:0:-1],
lambda mob: (
lambda m: m.scale(2).shift(SMALL_BUFF * UP).set_color(PINK),
mob,
),
rate_func=there_and_back,
))
self.add_sample_dot_ghosts(sample_dots)
self.apply_function(self.func, sample_dots=sample_dots)
self.wait()
@ -3215,7 +3207,6 @@ class StabilityAndInstability(AnalyzeFunctionWithTransformations):
CONFIG = {
"num_initial_applications": 0,
}
def construct(self):
self.force_skipping()
self.add_function_title()
@ -3246,10 +3237,9 @@ class StabilityAndInstability(AnalyzeFunctionWithTransformations):
self.all_arrows
)).copy()
arrows.set_fill(PINK, 1)
arrows.set_stroke(PINK, 3)
arrows.second_anim = LaggedStart(
ApplyMethod, arrows,
lambda m: (m.set_color, YELLOW),
lambda m: (m.set_fill, YELLOW, 1),
rate_func=there_and_back_with_pause,
lag_ratio=0.7,
run_time=2,
@ -3279,87 +3269,11 @@ class StabilityAndInstability(AnalyzeFunctionWithTransformations):
def write_derivative_fact(self):
stable_label = self.stable_label
unstable_label = self.unstable_label
labels = VGroup(stable_label, unstable_label)
phi_arrows = self.phi_arrows
phi_bro_arrows = self.phi_bro_arrows
arrow_groups = VGroup(phi_arrows, phi_bro_arrows)
deriv_labels = VGroup()
for char, label in zip("<>", labels):
deriv_label = TexMobject(
"\\big|", "\\frac{df}{dx}(", "x", ")", "\\big|",
char, "1"
)
deriv_label.get_parts_by_tex("\\big|").match_height(
deriv_label, stretch=True
)
deriv_label.set_color_by_tex("x", YELLOW, substring=False)
deriv_label.next_to(label, UP)
deriv_labels.add(deriv_label)
dot_groups = VGroup()
for arrow_group in arrow_groups:
dot_group = VGroup()
for arrow in arrow_group:
start_point, end_point = [
line.number_to_point(line.point_to_number(p))
for line, p in [
(self.input_line, arrow.get_start()),
(self.output_line, arrow.get_end()),
]
]
dot = Dot(start_point, radius=0.05)
dot.set_color(YELLOW)
dot.generate_target()
dot.target.move_to(end_point)
dot_group.add(dot)
dot_groups.add(dot_group)
for deriv_label, dot_group in zip(deriv_labels, dot_groups):
self.play(FadeInFromDown(deriv_label))
self.play(LaggedStart(GrowFromCenter, dot_group))
self.play(*map(MoveToTarget, dot_group), run_time=2)
self.wait()
arrow_groups = [phi_arrows, phi_bro_arrows]
class StaticAlgebraicObject(Scene):
def construct(self):
frac = get_phi_continued_fraction(40)
frac.scale_to_fit_width(FRAME_WIDTH - 1)
# frac.shift(2 * DOWN)
frac.to_edge(DOWN)
frac.set_stroke(WHITE, width=0.5)
title = TexMobject(
"\\infty \\ne \\lim",
tex_to_color_map={"\\ne": RED}
)
title.scale(1.5)
title.to_edge(UP)
polynomial = TexMobject("x^2 - x - 1 = 0")
polynomial.move_to(title)
self.add(title)
self.play(LaggedStart(
GrowFromCenter, frac,
lag_ratio=0.1,
run_time=3
))
self.wait()
factor = 1.1
self.play(frac.scale, factor, run_time=0.5)
self.play(
frac.scale, 1 / factor,
frac.set_color, LIGHT_GREY,
run_time=0.5, rate_func=lambda t: t**5,
)
self.wait()
self.play(
FadeOut(title),
FadeIn(polynomial)
)
self.wait(2)
class NotBetterThanGraphs(TeacherStudentsScene):
@ -3391,23 +3305,6 @@ class NotBetterThanGraphs(TeacherStudentsScene):
self.wait(3)
class WhatComesAfterWrapper(Wrapper):
CONFIG = {"title": "Beyond the first year"}
def construct(self):
Wrapper.construct(self)
new_title = TextMobject("Next video")
new_title.set_color(BLUE)
new_title.move_to(self.title)
self.play(
FadeInFromDown(new_title),
self.title.shift, UP,
self.title.fade, 1,
)
self.wait(3)
class TopicsAfterSingleVariable(PiCreatureScene, MoreTopics):
CONFIG = {
"pi_creatures_start_on_screen": False,
@ -3459,83 +3356,6 @@ class TopicsAfterSingleVariable(PiCreatureScene, MoreTopics):
self.wait(4)
class ShowJacobianZoomedIn(LinearTransformationScene, ZoomedScene):
CONFIG = {
"show_basis_vectors": False,
"show_coordinates": True,
"zoom_factor": 0.05,
}
def setup(self):
LinearTransformationScene.setup(self)
ZoomedScene.setup(self)
def construct(self):
def example_function(point):
x, y, z = point
return np.array([
x + np.sin(y),
y + np.sin(x),
0
])
zoomed_camera = self.zoomed_camera
zoomed_display = self.zoomed_display
frame = zoomed_camera.frame
frame.move_to(3 * LEFT + 1 * UP)
frame.set_color(YELLOW)
zoomed_display.display_frame.set_color(YELLOW)
zd_rect = BackgroundRectangle(
zoomed_display,
fill_opacity=1,
buff=MED_SMALL_BUFF,
)
self.add_foreground_mobject(zd_rect)
zd_rect.anim = UpdateFromFunc(
zd_rect,
lambda rect: rect.replace(zoomed_display).scale(1.1)
)
zd_rect.next_to(FRAME_HEIGHT * UP, UP)
tiny_grid = NumberPlane(
x_radius=2,
y_radius=2,
color=BLUE_E,
secondary_color=DARK_GREY,
)
tiny_grid.replace(frame)
jacobian_words = TextMobject("Jacobian")
jacobian_words.add_background_rectangle()
jacobian_words.scale(1.5)
jacobian_words.move_to(zoomed_display, UP)
zoomed_display.next_to(jacobian_words, DOWN)
self.play(self.get_zoom_in_animation())
self.activate_zooming()
self.play(
self.get_zoomed_display_pop_out_animation(),
zd_rect.anim
)
self.play(
ShowCreation(tiny_grid),
Write(jacobian_words),
run_time=2
)
self.add_transformable_mobject(tiny_grid)
self.add_foreground_mobject(jacobian_words)
self.wait()
self.apply_nonlinear_transformation(
example_function,
added_anims=[MaintainPositionRelativeTo(
zoomed_camera.frame, tiny_grid,
)],
run_time=5
)
self.wait()
# Video 2
class ComplexAnalysisOverlay(Scene):
def construct(self):
words = TextMobject("Complex analysis")
@ -3545,7 +3365,6 @@ class ComplexAnalysisOverlay(Scene):
self.add(words)
self.wait()
class CompelxAnalyticFluidFlow(ComplexTransformationScene, MovingCameraScene):
CONFIG = {
"num_anchors_to_add_per_line": 200,
@ -3623,7 +3442,6 @@ class AnalyzeZSquared(ComplexTransformationScene, ZoomedScene):
CONFIG = {
"num_anchors_to_add_per_line": 20,
"complex_homotopy": lambda z, t: z**(1.0 + t),
"zoom_factor": 0.05,
}
def setup(self):
@ -3633,8 +3451,8 @@ class AnalyzeZSquared(ComplexTransformationScene, ZoomedScene):
def construct(self):
self.edit_background_plane()
self.add_title()
# self.add_transforming_planes()
# self.preview_some_numbers()
self.add_transforming_planes()
self.preview_some_numbers()
self.zoom_in_to_one_plus_half_i()
self.write_derivative()
@ -3730,44 +3548,9 @@ class AnalyzeZSquared(ComplexTransformationScene, ZoomedScene):
self.play(FadeOut(dot_groups))
self.wait()
self.play(FadeOut(self.plane))
self.transformable_mobjects.remove(self.plane)
def zoom_in_to_one_plus_half_i(self):
z = complex(1, 0.5)
point = self.background.number_to_point(z)
point_mob = VectorizedPoint(point)
frame = self.zoomed_camera.frame
frame.move_to(point)
tiny_plane = NumberPlane(
x_radius=2, y_radius=2,
color=GREEN,
secondary_color=GREEN_E
)
tiny_plane.replace(frame)
plane = self.get_plane()
words = TextMobject("What does this look like")
words.add_background_rectangle()
words.next_to(self.zoomed_display, LEFT, aligned_edge=UP)
arrow = Arrow(words.get_bottom(), self.zoomed_display.get_left())
VGroup(words, arrow).set_color(YELLOW)
self.play(FadeIn(plane))
self.activate_zooming(animate=True)
self.play(ShowCreation(tiny_plane))
self.wait()
self.add_transformable_mobjects(plane, tiny_plane, point_mob)
self.add_foreground_mobjects(words, arrow)
self.apply_complex_homotopy(
self.complex_homotopy,
added_anims=[
Write(words),
GrowArrow(arrow),
MaintainPositionRelativeTo(frame, point_mob)
]
)
self.wait(2)
pass
def write_derivative(self):
pass
@ -3784,244 +3567,3 @@ class AnalyzeZSquared(ComplexTransformationScene, ZoomedScene):
top_plane.next_to(ORIGIN, UP, buff=tiny_tiny_buff)
bottom_plane.next_to(ORIGIN, DOWN, buff=tiny_tiny_buff)
return VGroup(top_plane, bottom_plane)
class PrinciplesOverlay(PiCreatureScene):
CONFIG = {
"default_pi_creature_kwargs": {
"color": GREY_BROWN,
"flip_at_start": True,
},
"default_pi_creature_start_corner": DR,
}
def construct(self):
morty = self.pi_creature
q_marks = VGroup(*[TexMobject("?") for x in range(40)])
q_marks.arrange_submobjects_in_grid(4, 10)
q_marks.space_out_submobjects(1.4)
for mark in q_marks:
mark.shift(
random.random() * RIGHT,
random.random() * UP,
)
mark.scale(1.5)
mark.set_stroke(BLACK, 1)
q_marks.next_to(morty, UP)
q_marks.shift_onto_screen()
q_marks.sort_submobjects(
lambda p: np.linalg.norm(p - morty.get_top())
)
self.play(morty.change, "pondering")
self.wait(2)
self.play(morty.change, "raise_right_hand")
self.wait()
self.play(morty.change, "thinking")
self.wait(4)
self.play(FadeInFromDown(q_marks[0]))
self.wait(2)
self.play(LaggedStart(
FadeInFromDown, q_marks[1:],
run_time=3
))
self.wait(3)
class ManyInfiniteExpressions(Scene):
def construct(self):
frac = get_phi_continued_fraction(10)
frac.scale_to_fit_height(2)
frac.to_corner(UL)
n = 9
radical_str_parts = [
"%d + \\sqrt{" % d
for d in range(1, n + 1)
]
radical_str_parts += ["\\cdots"]
radical_str_parts += ["}"] * n
radical = TexMobject("".join(radical_str_parts))
radical.to_corner(UR)
radical.to_edge(DOWN)
radical.set_color_by_gradient(YELLOW, RED)
n = 12
power_tower = TexMobject(
*["\\sqrt{2}^{"] * n + ["\\dots"] + ["}"] * n
)
power_tower.to_corner(UR)
power_tower.set_color_by_gradient(BLUE, GREEN)
self.play(*[
LaggedStart(
GrowFromCenter, group,
lag_ratio=0.1,
run_time=8,
)
for group in frac, radical, power_tower
])
self.wait(2)
class HoldUpPromo(PrinciplesOverlay):
def construct(self):
morty = self.pi_creature
url = TextMobject("https://brilliant.org/3b1b/")
url.to_corner(UL)
rect = ScreenRectangle(height=5.5)
rect.next_to(url, DOWN)
rect.to_edge(LEFT)
self.play(
Write(url),
self.pi_creature.change, "raise_right_hand"
)
self.play(ShowCreation(rect))
self.wait(2)
self.change_mode("thinking")
self.wait()
self.look_at(url)
self.wait(10)
self.change_mode("happy")
self.wait(10)
self.change_mode("raise_right_hand")
self.wait(10)
self.play(FadeOut(rect), FadeOut(url))
self.play(morty.change, "raise_right_hand")
self.wait()
self.play(morty.change, "hooray")
self.wait(3)
class EndScreen(PatreonEndScreen):
CONFIG = {
"specific_patrons": [
"Juan Benet",
"Keith Smith",
"Chloe Zhou",
"Desmos",
"Burt Humburg",
"CrypticSwarm",
"Andrew Sachs",
"Ho\\`ang T\\`ung L\\^am",
# "Hoàng Tùng Lâm",
"Devin Scott",
"Akash Kumar",
"Felix Tripier",
"Arthur Zey",
"David Kedmey",
"Ali Yahya",
"Mayank M. Mehrotra",
"Lukas Biewald",
"Yana Chernobilsky",
"Kaustuv DeBiswas",
"Yu Jun",
"dave nicponski",
"Damion Kistler",
"Jordan Scales",
"Markus Persson",
"Fela",
"Fred Ehrsam",
"Britt Selvitelle",
"Jonathan Wilson",
"Ryan Atallah",
"Joseph John Cox",
"Luc Ritchie",
"Matt Roveto",
"Jamie Warner",
"Marek Cirkos",
"Magister Mugit",
"Stevie Metke",
"Cooper Jones",
"James Hughes",
"John V Wertheim",
"Chris Giddings",
"Song Gao",
"Alexander Feldman",
"Matt Langford",
"Max Mitchell",
"Richard Burgmann",
"John Griffith",
"Chris Connett",
"Steven Tomlinson",
"Jameel Syed",
"Bong Choung",
"Ignacio Freiberg",
"Zhilong Yang",
"Giovanni Filippi",
"Eric Younge",
"Prasant Jagannath",
"Cody Brocious",
"James H. Park",
"Norton Wang",
"Kevin Le",
"Tianyu Ge",
"David MacCumber",
"Oliver Steele",
"Yaw Etse",
"Dave B",
"Waleed Hamied",
"George Chiesa",
"supershabam",
"Delton Ding",
"Thomas Tarler",
"Isak Hietala",
"1st ViewMaths",
"Jacob Magnuson",
"Mark Govea",
"Clark Gaebel",
"Mathias Jansson",
"David Clark",
"Michael Gardner",
"Mads Elvheim",
"Awoo",
"Dr . David G. Stork",
"Ted Suzman",
"Linh Tran",
"Andrew Busey",
"John Haley",
"Ankalagon",
"Eric Lavault",
"Boris Veselinovich",
"Julian Pulgarin",
"Jeff Linse",
"Robert Teed",
"Jason Hise",
"Meshal Alshammari",
"Bernd Sing",
"James Thornton",
"Mustafa Mahdi",
"Mathew Bramson",
"Jerry Ling",
"Sh\\`im\\'in Ku\\=ang",
"Rish Kundalia",
"Achille Brighton",
"Ripta Pasay",
]
}
# class Thumbnail(GraphicalIntuitions):
class Thumbnail(AnalyzeFunctionWithTransformations):
CONFIG = {
"x_axis_width": 12,
"graph_origin": 1.5 * DOWN + 4 * LEFT,
"num_initial_applications": 1,
"input_line_zero_point": 2 * UP,
}
def construct(self):
self.add_function_title()
self.title.fade(1)
self.titles.fade(1)
self.repeatedly_apply_function()
full_rect = FullScreenFadeRectangle()
cross = Cross(full_rect)
cross.set_stroke(width=40)
self.add(cross)

View file

@ -206,7 +206,7 @@ class LeaveItToComputers(TeacherStudentsScene):
class PrerequisiteKnowledge(TeacherStudentsScene):
CONFIG = {
"camera_config": {"background_alpha": 255}
"camera_config": {"background_opacity": 1}
}
def construct(self):

View file

@ -140,6 +140,13 @@ class ApplyPointwiseFunction(ApplyMethod):
)
class ApplyPointwiseFunctionToCenter(ApplyPointwiseFunction):
def __init__(self, function, mobject, **kwargs):
ApplyMethod.__init__(
self, mobject.move_to, function(mobject.get_center()), **kwargs
)
class FadeToColor(ApplyMethod):
def __init__(self, mobject, color, **kwargs):
ApplyMethod.__init__(self, mobject.set_color, color, **kwargs)

View file

@ -1,5 +1,6 @@
import itertools as it
import numpy as np
import operator as op
import aggdraw
import copy
@ -7,9 +8,10 @@ import time
from PIL import Image
from colour import Color
from scipy.spatial.distance import pdist
from constants import *
from mobject.types.image_mobject import ImageMobject
from mobject.types.image_mobject import AbstractImageMobject
from mobject.mobject import Mobject
from mobject.types.point_cloud_mobject import PMobject
from mobject.types.vectorized_mobject import VMobject
@ -21,22 +23,26 @@ from utils.iterables import batch_by_property
from utils.iterables import list_difference_update
from utils.iterables import remove_list_redundancies
from utils.simple_functions import fdiv
from utils.space_ops import angle_of_vector
class Camera(object):
CONFIG = {
"background_image": None,
"pixel_shape": (DEFAULT_PIXEL_HEIGHT, DEFAULT_PIXEL_WIDTH),
# Note: frame_shape will be resized to match pixel_shape
"frame_shape": (FRAME_HEIGHT, FRAME_WIDTH),
"space_center": ORIGIN,
"pixel_height": DEFAULT_PIXEL_HEIGHT,
"pixel_width": DEFAULT_PIXEL_WIDTH,
# Note: frame height and width will be resized to match
# the pixel aspect ratio
"frame_height": FRAME_HEIGHT,
"frame_width": FRAME_WIDTH,
"frame_center": ORIGIN,
"background_color": BLACK,
"background_opacity": 0,
# Points in vectorized mobjects with norm greater
# than this value will be rescaled.
"max_allowable_norm": FRAME_WIDTH,
"image_mode": "RGBA",
"n_rgb_coords": 4,
"background_alpha": 0, # Out of rgb_max_val
"pixel_array_dtype": 'uint8',
"use_z_coordinate_for_display_order": False,
# z_buff_func is only used if the flag above is set to True.
@ -58,36 +64,72 @@ class Camera(object):
self.canvas = None
return copy.copy(self)
def reset_pixel_shape(self, new_height, new_width):
self.pixel_width = new_width
self.pixel_height = new_height
self.init_background()
self.resize_frame_shape()
self.reset()
def get_pixel_height(self):
return self.pixel_height
def get_pixel_width(self):
return self.pixel_width
def get_frame_height(self):
return self.frame_height
def get_frame_width(self):
return self.frame_width
def get_frame_center(self):
return self.frame_center
def set_frame_height(self, frame_height):
self.frame_height = frame_height
def set_frame_width(self, frame_width):
self.frame_width = frame_width
def set_frame_center(self, frame_center):
self.frame_center = frame_center
def resize_frame_shape(self, fixed_dimension=0):
"""
Changes frame_shape to match the aspect ratio
of pixel_shape, where fixed_dimension determines
whether frame_shape[0] (height) or frame_shape[1] (width)
of the pixels, where fixed_dimension determines
whether frame_height or frame_width
remains fixed while the other changes accordingly.
"""
aspect_ratio = float(self.pixel_shape[1]) / self.pixel_shape[0]
frame_width, frame_height = self.frame_shape
pixel_height = self.get_pixel_height()
pixel_width = self.get_pixel_width()
frame_height = self.get_frame_height()
frame_width = self.get_frame_width()
aspect_ratio = fdiv(pixel_width, pixel_height)
if fixed_dimension == 0:
frame_height = aspect_ratio * frame_width
frame_height = frame_width / aspect_ratio
else:
frame_width = frame_height / aspect_ratio
self.frame_shape = (frame_width, frame_height)
frame_width = aspect_ratio * frame_height
self.set_frame_height(frame_height)
self.set_frame_width(frame_width)
def init_background(self):
height = self.get_pixel_height()
width = self.get_pixel_width()
if self.background_image is not None:
path = get_full_raster_image_path(self.background_image)
image = Image.open(path).convert(self.image_mode)
height, width = self.pixel_shape
# TODO, how to gracefully handle backgrounds
# with different sizes?
self.background = np.array(image)[:height, :width]
self.background = self.background.astype(self.pixel_array_dtype)
else:
background_rgba = color_to_int_rgba(
self.background_color, alpha=self.background_alpha
self.background_color, self.background_opacity
)
self.background = np.zeros(
list(self.pixel_shape) + [self.n_rgb_coords],
(height, width, self.n_rgb_coords),
dtype=self.pixel_array_dtype
)
self.background[:, :] = background_rgba
@ -105,10 +147,10 @@ class Camera(object):
retval = np.array(pixel_array)
if convert_from_floats:
retval = np.apply_along_axis(
lambda f: (
f * self.rgb_max_val).astype(self.pixel_array_dtype),
lambda f: (f * self.rgb_max_val).astype(self.pixel_array_dtype),
2,
retval)
retval
)
return retval
def set_pixel_array(self, pixel_array, convert_from_floats=False):
@ -148,6 +190,7 @@ class Camera(object):
def reset(self):
self.set_pixel_array(self.background)
return self
####
@ -201,7 +244,7 @@ class Camera(object):
type_func_pairs = [
(VMobject, self.display_multiple_vectorized_mobjects),
(PMobject, self.display_multiple_point_cloud_mobjects),
(ImageMobject, self.display_multiple_image_mobjects),
(AbstractImageMobject, self.display_multiple_image_mobjects),
(Mobject, lambda batch: batch), # Do nothing
]
@ -301,8 +344,7 @@ class Camera(object):
# points = self.adjust_out_of_range_points(points)
if len(points) == 0:
continue
aligned_points = self.align_points_to_camera(points)
coords = self.points_to_pixel_coords(aligned_points)
coords = self.points_to_pixel_coords(points)
coord_strings = coords.flatten().astype(str)
# Start new path string with M
coord_strings[0] = "M" + coord_strings[0]
@ -342,7 +384,6 @@ class Camera(object):
def display_point_cloud(self, points, rgbas, thickness):
if len(points) == 0:
return
points = self.align_points_to_camera(points)
pixel_coords = self.points_to_pixel_coords(points)
pixel_coords = self.thickened_coordinates(
pixel_coords, thickness
@ -358,7 +399,8 @@ class Camera(object):
pixel_coords = pixel_coords[on_screen_indices]
rgbas = rgbas[on_screen_indices]
ph, pw = self.pixel_shape
ph = self.get_pixel_height()
pw = self.get_pixel_width()
flattener = np.array([1, pw], dtype='int')
flattener = flattener.reshape((2, 1))
@ -378,94 +420,56 @@ class Camera(object):
ul_coords, ur_coords, dl_coords = corner_coords
right_vect = ur_coords - ul_coords
down_vect = dl_coords - ul_coords
center_coords = ul_coords + (right_vect + down_vect) / 2
impa = image_mobject.pixel_array
oh, ow = self.pixel_array.shape[:2] # Outer width and height
ih, iw = impa.shape[:2] # inner with and height
rgb_len = self.pixel_array.shape[2]
image = np.zeros((oh, ow, rgb_len), dtype=self.pixel_array_dtype)
if right_vect[1] == 0 and down_vect[0] == 0:
rv0 = right_vect[0]
dv1 = down_vect[1]
x_indices = np.arange(rv0, dtype='int') * iw / rv0
y_indices = np.arange(dv1, dtype='int') * ih / dv1
stretched_impa = impa[y_indices][:, x_indices]
x0, x1 = ul_coords[0], ur_coords[0]
y0, y1 = ul_coords[1], dl_coords[1]
if x0 >= ow or x1 < 0 or y0 >= oh or y1 < 0:
return
siy0 = max(-y0, 0) # stretched_impa y0
siy1 = dv1 - max(y1 - oh, 0)
six0 = max(-x0, 0)
six1 = rv0 - max(x1 - ow, 0)
x0 = max(x0, 0)
y0 = max(y0, 0)
image[y0:y1, x0:x1] = stretched_impa[siy0:siy1, six0:six1]
else:
# Alternate (slower) tactic if image is tilted
# List of all coordinates of pixels, given as (x, y),
# which matches the return type of points_to_pixel_coords,
# even though np.array indexing naturally happens as (y, x)
all_pixel_coords = np.zeros((oh * ow, 2), dtype='int')
a = np.arange(oh * ow, dtype='int')
all_pixel_coords[:, 0] = a % ow
all_pixel_coords[:, 1] = a / ow
recentered_coords = all_pixel_coords - ul_coords
with np.errstate(divide='ignore'):
ix_coords, iy_coords = [
np.divide(
dim * np.dot(recentered_coords, vect),
np.dot(vect, vect),
)
for vect, dim in (right_vect, iw), (down_vect, ih)
]
to_change = reduce(op.and_, [
ix_coords >= 0, ix_coords < iw,
iy_coords >= 0, iy_coords < ih,
])
inner_flat_coords = iw * \
iy_coords[to_change] + ix_coords[to_change]
flat_impa = impa.reshape((iw * ih, rgb_len))
target_rgbas = flat_impa[inner_flat_coords, :]
image = image.reshape((ow * oh, rgb_len))
image[to_change] = target_rgbas
image = image.reshape((oh, ow, rgb_len))
self.overlay_rgba_array(image)
def overlay_rgba_array(self, arr):
fg = arr
bg = self.pixel_array
# rgba_max_val = self.rgb_max_val
src_rgb, src_a, dst_rgb, dst_a = [
a.astype(np.float32) / self.rgb_max_val
for a in fg[..., :3], fg[..., 3], bg[..., :3], bg[..., 3]
]
out_a = src_a + dst_a * (1.0 - src_a)
# When the output alpha is 0 for full transparency,
# we have a choice over what RGB value to use in our
# output representation. We choose 0 here.
out_rgb = fdiv(
src_rgb * src_a[..., None] +
dst_rgb * dst_a[..., None] * (1.0 - src_a[..., None]),
out_a[..., None],
zero_over_zero_value=0
sub_image = Image.fromarray(
image_mobject.get_pixel_array(),
mode="RGBA"
)
self.pixel_array[..., :3] = out_rgb * self.rgb_max_val
self.pixel_array[..., 3] = out_a * self.rgb_max_val
# Reshape
pixel_width = int(pdist([ul_coords, ur_coords]))
pixel_height = int(pdist([ul_coords, dl_coords]))
sub_image = sub_image.resize(
(pixel_width, pixel_height), resample=Image.BICUBIC
)
def align_points_to_camera(self, points):
# This is where projection should live
return points - self.space_center
# Rotate
angle = angle_of_vector(right_vect)
adjusted_angle = -int(360 * angle / TAU)
if adjusted_angle != 0:
sub_image = sub_image.rotate(
adjusted_angle, resample=Image.BICUBIC, expand=1
)
# TODO, there is no accounting for a shear...
# Paste into an image as large as the camear's pixel array
full_image = Image.fromarray(
np.zeros((self.get_pixel_height(), self.get_pixel_width())),
mode="RGBA"
)
new_ul_coords = center_coords - np.array(sub_image.size) / 2
full_image.paste(
sub_image,
box=(
new_ul_coords[0],
new_ul_coords[1],
new_ul_coords[0] + sub_image.size[0],
new_ul_coords[1] + sub_image.size[1],
)
)
# Paint on top of existing pixel array
self.overlay_PIL_image(full_image)
def overlay_rgba_array(self, arr):
self.overlay_PIL_image(Image.fromarray(arr, mode="RGBA"))
def overlay_PIL_image(self, image):
self.pixel_array = np.array(
Image.alpha_composite(self.get_image(), image)
)
def adjust_out_of_range_points(self, points):
if not np.any(points > self.max_allowable_norm):
@ -483,31 +487,43 @@ class Camera(object):
return points
def points_to_pixel_coords(self, points):
shifted_points = points - self.get_frame_center()
result = np.zeros((len(points), 2))
ph, pw = self.pixel_shape
sh, sw = self.frame_shape
width_mult = pw / sw
width_add = pw / 2
height_mult = ph / sh
height_add = ph / 2
pixel_height = self.get_pixel_height()
pixel_width = self.get_pixel_width()
frame_height = self.get_frame_height()
frame_width = self.get_frame_width()
width_mult = pixel_width / frame_width
width_add = pixel_width / 2
height_mult = pixel_height / frame_height
height_add = pixel_height / 2
# Flip on y-axis as you go
height_mult *= -1
result[:, 0] = points[:, 0] * width_mult + width_add
result[:, 1] = points[:, 1] * height_mult + height_add
result[:, 0] = shifted_points[:, 0] * width_mult + width_add
result[:, 1] = shifted_points[:, 1] * height_mult + height_add
return result.astype('int')
def on_screen_pixels(self, pixel_coords):
return reduce(op.and_, [
pixel_coords[:, 0] >= 0,
pixel_coords[:, 0] < self.pixel_shape[1],
pixel_coords[:, 0] < self.get_pixel_width(),
pixel_coords[:, 1] >= 0,
pixel_coords[:, 1] < self.pixel_shape[0],
pixel_coords[:, 1] < self.get_pixel_height(),
])
def adjusted_thickness(self, thickness):
big_shape = PRODUCTION_QUALITY_CAMERA_CONFIG["pixel_shape"]
factor = sum(big_shape) / sum(self.pixel_shape)
# TODO: This seems...unsystematic
big_sum = op.add(
PRODUCTION_QUALITY_CAMERA_CONFIG["pixel_height"],
PRODUCTION_QUALITY_CAMERA_CONFIG["pixel_width"],
)
this_sum = op.add(
self.get_pixel_height(),
self.get_pixel_width(),
)
factor = fdiv(big_sum, this_sum)
return 1 + (thickness - 1) / factor
def get_thickening_nudges(self, thickness):
@ -525,13 +541,20 @@ class Camera(object):
def get_coords_of_all_pixels(self):
# These are in x, y order, to help me keep things straight
full_space_dims = np.array(self.frame_shape)[::-1]
full_pixel_dims = np.array(self.pixel_shape)[::-1]
full_space_dims = np.array([
self.get_frame_width(),
self.get_frame_height()
])
full_pixel_dims = np.array([
self.get_pixel_width(),
self.get_pixel_height()
])
# These are addressed in the same y, x order as in pixel_array, but the values in them
# are listed in x, y order
uncentered_pixel_coords = np.indices(self.pixel_shape)[
::-1].transpose(1, 2, 0)
uncentered_pixel_coords = np.indices(
[self.get_pixel_height(), self.get_pixel_width()]
)[::-1].transpose(1, 2, 0)
uncentered_space_coords = fdiv(
uncentered_pixel_coords * full_space_dims,
full_pixel_dims)
@ -541,7 +564,8 @@ class Camera(object):
# overflow is unlikely to be a problem)
centered_space_coords = (
uncentered_space_coords - fdiv(full_space_dims, 2))
uncentered_space_coords - fdiv(full_space_dims, 2)
)
# Have to also flip the y coordinates to account for pixel array being listed in
# top-to-bottom order, opposite of screen coordinate convention

View file

@ -43,8 +43,9 @@ class MappingCamera(Camera):
# TODO: Add optional separator borders between cameras (or perhaps peel this off into a
# CameraPlusOverlay class)
# TODO, the classes below should likely be deleted
class MultiCamera(Camera):
class OldMultiCamera(Camera):
def __init__(self, *cameras_with_start_positions, **kwargs):
self.shifted_cameras = [
DictAsObject(
@ -52,8 +53,8 @@ class MultiCamera(Camera):
"camera": camera_with_start_positions[0],
"start_x": camera_with_start_positions[1][1],
"start_y": camera_with_start_positions[1][0],
"end_x": camera_with_start_positions[1][1] + camera_with_start_positions[0].pixel_shape[1],
"end_y": camera_with_start_positions[1][0] + camera_with_start_positions[0].pixel_shape[0],
"end_x": camera_with_start_positions[1][1] + camera_with_start_positions[0].get_pixel_width(),
"end_y": camera_with_start_positions[1][0] + camera_with_start_positions[0].get_pixel_height(),
})
for camera_with_start_positions in cameras_with_start_positions
]
@ -92,23 +93,23 @@ class MultiCamera(Camera):
for shifted_camera in self.shifted_cameras:
shifted_camera.camera.init_background()
# A MultiCamera which, when called with two full-size cameras, initializes itself
# A OldMultiCamera which, when called with two full-size cameras, initializes itself
# as a splitscreen, also taking care to resize each individual camera within it
class SplitScreenCamera(MultiCamera):
class SplitScreenCamera(OldMultiCamera):
def __init__(self, left_camera, right_camera, **kwargs):
digest_config(self, kwargs)
self.left_camera = left_camera
self.right_camera = right_camera
half_width = self.pixel_shape[1] / 2
half_width = self.get_pixel_width() / 2
for camera in [self.left_camera, self.right_camera]:
# TODO: Round up on one if width is odd
camera.pixel_shape = (self.pixel_shape[0], half_width)
camera.init_background()
camera.resize_frame_shape()
camera.reset()
camera.reset_pixel_shape(camera.get_pixel_height(), half_width)
MultiCamera.__init__(self, (left_camera, (0, 0)),
(right_camera, (0, half_width)))
OldMultiCamera.__init__(
self,
(left_camera, (0, 0)),
(right_camera, (0, half_width)),
)

View file

@ -1,40 +1,77 @@
from __future__ import absolute_import
from constants import FRAME_HEIGHT
from constants import WHITE
from camera.camera import Camera
from mobject.frame import ScreenRectangle
from utils.config_ops import digest_config
class MovingCamera(Camera):
"""
Stays in line with the height, width and position
of a given mobject
Stays in line with the height, width and position of it's 'frame', which is a Rectangle
"""
CONFIG = {
"aligned_dimension": "width" # or height
"fixed_dimension": 0, # width
"default_frame_stroke_color": WHITE,
"default_frame_stroke_width": 0,
}
def __init__(self, frame=None, **kwargs):
"""
frame is a Mobject, (should be a rectangle) determining
which region of space the camera displys
frame is a Mobject, (should almost certainly be a rectangle)
determining which region of space the camera displys
"""
digest_config(self, kwargs)
if frame is None:
frame = ScreenRectangle(height=FRAME_HEIGHT)
frame.fade(1)
frame.set_stroke(
self.default_frame_stroke_color,
self.default_frame_stroke_width,
)
self.frame = frame
Camera.__init__(self, **kwargs)
def capture_mobjects(self, *args, **kwargs):
self.space_center = self.frame.get_center()
self.realign_frame_shape()
Camera.capture_mobjects(self, *args, **kwargs)
# TODO, make these work for a rotated frame
def get_frame_height(self):
return self.frame.get_height()
def realign_frame_shape(self):
height, width = self.frame_shape
if self.aligned_dimension == "height":
self.frame_shape = (self.frame.get_height(), width)
else:
self.frame_shape = (height, self.frame.get_width())
self.resize_frame_shape(0 if self.aligned_dimension == "height" else 1)
def get_frame_width(self):
return self.frame.get_width()
def get_frame_center(self):
return self.frame.get_center()
def set_frame_height(self, frame_height):
self.frame.stretch_to_fit_height(frame_height)
def set_frame_width(self, frame_width):
self.frame.stretch_to_fit_width(frame_width)
def set_frame_center(self, frame_center):
self.frame.move_to(frame_center)
def capture_mobjects(self, mobjects, **kwargs):
# self.reset_frame_center()
# self.realign_frame_shape()
Camera.capture_mobjects(self, mobjects, **kwargs)
# def reset_frame_center(self):
# self.frame_center = self.frame.get_center()
# def realign_frame_shape(self):
# height, width = self.frame_shape
# if self.fixed_dimension == 0:
# self.frame_shape = (height, self.frame.get_width())
# else:
# self.frame_shape = (self.frame.get_height(), width)
# self.resize_frame_shape(fixed_dimension=self.fixed_dimension)
def get_mobjects_indicating_movement(self):
"""
Returns all mobjets whose movement implies that the camera
should think of all other mobjects on the screen as moving
"""
return [self.frame]

59
camera/multi_camera.py Normal file
View file

@ -0,0 +1,59 @@
from __future__ import absolute_import
from camera.moving_camera import MovingCamera
from utils.iterables import list_difference_update
class MultiCamera(MovingCamera):
CONFIG = {
"allow_cameras_to_capture_their_own_display": False,
}
def __init__(self, *image_mobjects_from_cameras, **kwargs):
self.image_mobjects_from_cameras = []
for imfc in image_mobjects_from_cameras:
self.add_image_mobject_from_camera(imfc)
MovingCamera.__init__(self, **kwargs)
def add_image_mobject_from_camera(self, image_mobject_from_camera):
# A silly method to have right now, but maybe there are things
# we want to guarantee about any imfc's added later.
imfc = image_mobject_from_camera
assert(isinstance(imfc.camera, MovingCamera))
self.image_mobjects_from_cameras.append(imfc)
def update_sub_cameras(self):
""" Reshape sub_camera pixel_arrays """
for imfc in self.image_mobjects_from_cameras:
pixel_height, pixel_width = self.get_pixel_array().shape[:2]
imfc.camera.frame_shape = (
imfc.camera.frame.get_height(),
imfc.camera.frame.get_width(),
)
imfc.camera.reset_pixel_shape(
int(pixel_height * imfc.get_height() / self.get_frame_height()),
int(pixel_width * imfc.get_width() / self.get_frame_width()),
)
def reset(self):
for imfc in self.image_mobjects_from_cameras:
imfc.camera.reset()
MovingCamera.reset(self)
return self
def capture_mobjects(self, mobjects, **kwargs):
self.update_sub_cameras()
for imfc in self.image_mobjects_from_cameras:
to_add = list(mobjects)
if not self.allow_cameras_to_capture_their_own_display:
to_add = list_difference_update(
to_add, imfc.submobject_family()
)
imfc.camera.capture_mobjects(to_add, **kwargs)
MovingCamera.capture_mobjects(self, mobjects, **kwargs)
def get_mobjects_indicating_movement(self):
return [self.frame] + [
imfc.camera.frame
for imfc in self.image_mobjects_from_cameras
]

View file

@ -12,6 +12,8 @@ from utils.bezier import interpolate
from utils.space_ops import rotation_about_z
from utils.space_ops import rotation_matrix
# TODO: Make sure this plays well with latest camera updates
class CameraWithPerspective(Camera):
CONFIG = {
@ -49,7 +51,7 @@ class ThreeDCamera(CameraWithPerspective):
self.rotation_mobject = VectorizedPoint()
# moving_center lives in the x-y-z space
# It representes the center of rotation
self.moving_center = VectorizedPoint(self.space_center)
self.moving_center = VectorizedPoint(self.frame_center)
self.set_position(self.phi, self.theta, self.distance)
def modified_rgb(self, vmobject, rgb):
@ -163,7 +165,7 @@ class ThreeDCamera(CameraWithPerspective):
center_of_rotation = self.get_center_of_rotation(
center_x, center_y, center_z)
self.moving_center.move_to(center_of_rotation)
self.space_center = self.moving_center.points[0]
self.frame_center = self.moving_center.points[0]
def get_view_transformation_matrix(self):
return (self.default_distance / self.get_distance()) * np.dot(
@ -174,6 +176,6 @@ class ThreeDCamera(CameraWithPerspective):
def points_to_pixel_coords(self, points):
matrix = self.get_view_transformation_matrix()
new_points = np.dot(points, matrix.T)
self.space_center = self.moving_center.points[0]
self.frame_center = self.moving_center.points[0]
return Camera.points_to_pixel_coords(self, new_points)

View file

@ -17,17 +17,20 @@ LOW_QUALITY_FRAME_DURATION = 1. / 15
MEDIUM_QUALITY_FRAME_DURATION = 1. / 30
PRODUCTION_QUALITY_FRAME_DURATION = 1. / 60
# There might be other configuration than pixel_shape later...
# There might be other configuration than pixel shape later...
PRODUCTION_QUALITY_CAMERA_CONFIG = {
"pixel_shape": (DEFAULT_PIXEL_HEIGHT, DEFAULT_PIXEL_WIDTH),
"pixel_height": DEFAULT_PIXEL_HEIGHT,
"pixel_width": DEFAULT_PIXEL_WIDTH,
}
MEDIUM_QUALITY_CAMERA_CONFIG = {
"pixel_shape": (720, 1280),
"pixel_height": 720,
"pixel_width": 1280,
}
LOW_QUALITY_CAMERA_CONFIG = {
"pixel_shape": (480, 854),
"pixel_height": 480,
"pixel_width": 854,
}
DEFAULT_POINT_DENSITY_2D = 25

View file

@ -224,11 +224,8 @@ def get_module_posix(file_name):
module_name = file_name.replace(".py", "")
last_module = imp.load_module(".", *imp.find_module("."))
for part in module_name.split(os.sep):
try:
load_args = imp.find_module(part, last_module.__path__)
last_module = imp.load_module(part, *load_args)
except ImportError:
continue
load_args = imp.find_module(part, last_module.__path__)
last_module = imp.load_module(part, *load_args)
return last_module

View file

@ -199,6 +199,15 @@ class Mobject(Container):
)
return self
def apply_function_to_position(self, function):
self.move_to(function(self.get_center()))
return self
def apply_function_to_submobject_positions(self, function):
for submob in self.submobjects:
submob.apply_function_to_position(function)
return self
def apply_matrix(self, matrix, **kwargs):
# Default to applying matrix about the origin, not mobjects center
if len(kwargs) == 0:

View file

@ -7,25 +7,51 @@ from PIL import Image
from constants import *
from mobject.mobject import Mobject
from mobject.shape_matchers import SurroundingRectangle
from utils.bezier import interpolate
from utils.color import color_to_int_rgb
from utils.config_ops import digest_config
from utils.images import get_full_raster_image_path
class ImageMobject(Mobject):
class AbstractImageMobject(Mobject):
"""
Automatically filters out black pixels
"""
CONFIG = {
"filter_color": "black",
"invert": False,
# "use_cache" : True,
"height": 2.0,
"image_mode": "RGBA",
"pixel_array_dtype": "uint8",
}
def get_pixel_array(self):
raise Exception("Not implemented")
def set_color(self):
# Likely to be implemented in subclasses, but no obgligation
pass
def init_points(self):
# Corresponding corners of image are fixed to these 3 points
self.points = np.array([
UP + LEFT,
UP + RIGHT,
DOWN + LEFT,
])
self.center()
h, w = self.get_pixel_array().shape[:2]
self.stretch_to_fit_height(self.height)
self.stretch_to_fit_width(self.height * w / h)
def copy(self):
return self.deepcopy()
class ImageMobject(AbstractImageMobject):
CONFIG = {
"invert": False,
"image_mode": "RGBA",
}
def __init__(self, filename_or_array, **kwargs):
digest_config(self, kwargs)
if isinstance(filename_or_array, str):
@ -37,7 +63,7 @@ class ImageMobject(Mobject):
self.change_to_rgba_array()
if self.invert:
self.pixel_array[:, :, :3] = 255 - self.pixel_array[:, :, :3]
Mobject.__init__(self, **kwargs)
AbstractImageMobject.__init__(self, **kwargs)
def change_to_rgba_array(self):
pa = self.pixel_array
@ -53,6 +79,9 @@ class ImageMobject(Mobject):
pa = np.append(pa, alphas, axis=2)
self.pixel_array = pa
def get_pixel_array(self):
return self.pixel_array
def set_color(self, color, alpha=None, family=True):
rgb = color_to_int_rgb(color)
self.pixel_array[:, :, :3] = rgb
@ -63,19 +92,6 @@ class ImageMobject(Mobject):
self.color = color
return self
def init_points(self):
# Corresponding corners of image are fixed to these
# Three points
self.points = np.array([
UP + LEFT,
UP + RIGHT,
DOWN + LEFT,
])
self.center()
h, w = self.pixel_array.shape[:2]
self.stretch_to_fit_height(self.height)
self.stretch_to_fit_width(self.height * w / h)
def set_opacity(self, alpha):
self.pixel_array[:, :, 3] = int(255 * alpha)
return self
@ -90,5 +106,30 @@ class ImageMobject(Mobject):
mobject1.pixel_array, mobject2.pixel_array, alpha
).astype(self.pixel_array_dtype)
def copy(self):
return self.deepcopy()
# TODO, add the ability to have the dimensions/orientation of this
# mobject more strongly tied to the frame of the camera it contains,
# in the case where that's a MovingCamera
class ImageMobjectFromCamera(AbstractImageMobject):
CONFIG = {
"default_display_frame_config": {
"stroke_width": 3,
"stroke_color": WHITE,
"buff": 0,
}
}
def __init__(self, camera, **kwargs):
self.camera = camera
AbstractImageMobject.__init__(self, **kwargs)
def get_pixel_array(self):
return self.camera.get_pixel_array()
def add_display_frame(self, **kwargs):
config = dict(self.default_display_frame_config)
config.update(kwargs)
self.display_frame = SurroundingRectangle(self, **config)
self.add(self.display_frame)
return self

View file

@ -668,7 +668,7 @@ class ColorMappedByFuncScene(Scene):
# We hash just based on output image
# Thus, multiple scenes with same output image can re-use it
# without recomputation
full_hash = hash((func_hash, self.camera.pixel_shape))
full_hash = hash((func_hash, self.camera.get_pixel_width()))
self.background_image_file = self.short_path_to_long_path(
"color_mapped_bg_hash_" + str(full_hash) + ".png"
)

View file

@ -2884,7 +2884,7 @@ class ZeroFoundOnBoundary(Scene):
class AllOfTheVideos(Scene):
CONFIG = {
"camera_config" : {
"background_alpha" : 255,
"background_opacity" : 1,
}
}
def construct(self):
@ -3274,5 +3274,6 @@ class Thumbnail(SearchSpacePerimeterVsArea):

View file

@ -2056,7 +2056,7 @@ class IPTScene1(PiCreatureScene):
# use the following for the zoomed inset
if show_detail:
self.camera.frame_shape = (0.02 * FRAME_HEIGHT, 0.02 * FRAME_WIDTH)
self.camera.space_center = C
self.camera.frame_center = C
SCREEN_SCALE = 0.01
SCREEN_THICKNESS = 0.02

View file

@ -1479,7 +1479,7 @@ class AskAboutLayers(PreviewMNistNetwork):
class BreakUpMacroPatterns(IntroduceEachLayer):
CONFIG = {
"camera_config" : {"background_alpha" : 255},
"camera_config" : {"background_opacity" : 1},
"prefixes" : [
"nine", "eight", "four",
"upper_loop", "right_line",
@ -1868,7 +1868,7 @@ class BreakUpMicroPatterns(BreakUpMacroPatterns):
class SecondLayerIsLittleEdgeLayer(IntroduceEachLayer):
CONFIG = {
"camera_config" : {
"background_alpha" : 255,
"background_opacity" : 1,
},
"network_mob_config" : {
"layer_to_layer_buff" : 2,
@ -2081,7 +2081,7 @@ class SecondLayerIsLittleEdgeLayer(IntroduceEachLayer):
class EdgeDetection(Scene):
CONFIG = {
"camera_config" : {"background_alpha" : 255}
"camera_config" : {"background_opacity" : 1}
}
def construct(self):
lion = ImageMobject("Lion")

View file

@ -468,7 +468,7 @@ class TauFalls(Scene):
class EulerWrites628(Scene):
CONFIG = {
"camera_config" : {
"background_alpha" : 255,
"background_opacity" : 1,
}
}
def construct(self):
@ -548,7 +548,7 @@ class EulerWrites628(Scene):
class HeroAndVillain(Scene):
CONFIG = {
"camera_config" : {
"background_alpha" : 255,
"background_opacity" : 1,
}
}
def construct(self):
@ -987,7 +987,7 @@ class SpecialThanks(Scene):
class EndScene(PatreonEndScreen):
CONFIG = {
"camera_config" : {
"background_alpha" : 255,
"background_opacity" : 1,
}
}
def construct(self):

View file

@ -4958,7 +4958,7 @@ class KeeperAndSailorForSineProduct(KeeperAndSailor):
class Conclusion(TeacherStudentsScene):
CONFIG = {
"camera_config": {"background_alpha": 255},
"camera_config": {"background_opacity": 1},
}
def construct(self):

View file

@ -1,26 +1,32 @@
from __future__ import absolute_import
from constants import *
from scene.scene import Scene
from camera.moving_camera import MovingCamera
from mobject.frame import ScreenRectangle
from utils.iterables import list_update
class MovingCameraScene(Scene):
CONFIG = {
"camera_class": MovingCamera
}
def setup(self):
self.camera_frame = ScreenRectangle(height=FRAME_HEIGHT)
self.camera_frame.set_stroke(width=0)
self.camera = MovingCamera(
self.camera_frame, **self.camera_config
)
Scene.setup(self)
assert(isinstance(self.camera, MovingCamera))
self.camera_frame = self.camera.frame
# Hmm, this currently relies on the fact that MovingCamera
# willd default to a full-sized frame. Is that okay?
return self
def get_moving_mobjects(self, *animations):
# TODO: Code repetition from ZoomedScene
moving_mobjects = Scene.get_moving_mobjects(self, *animations)
if self.camera_frame in moving_mobjects:
# When the camera is moving, so is everything,
return self.mobjects
else:
return moving_mobjects
all_moving_mobjects = self.camera.extract_mobject_family_members(
moving_mobjects
)
movement_indicators = self.camera.get_mobjects_indicating_movement()
for movement_indicator in movement_indicators:
if movement_indicator in all_moving_mobjects:
# When one of these is moving, the camera should
# consider all mobjects to be moving
return list_update(self.mobjects, moving_mobjects)
return moving_mobjects

View file

@ -572,7 +572,8 @@ class Scene(Container):
self.args_to_rename_file = (temp_file_path, file_path)
fps = int(1 / self.frame_duration)
height, width = self.camera.pixel_shape
height = self.camera.get_pixel_height()
width = self.camera.get_pixel_width()
command = [
FFMPEG_BIN,

View file

@ -1,20 +1,19 @@
from __future__ import absolute_import
import numpy as np
from scene.scene import Scene
from animation.creation import FadeIn
from scene.moving_camera_scene import MovingCameraScene
from camera.moving_camera import MovingCamera
from mobject.geometry import Rectangle
from camera.multi_camera import MultiCamera
from mobject.types.image_mobject import ImageMobjectFromCamera
from utils.simple_functions import fdiv
from constants import *
from animation.transform import ApplyMethod
# Note, any scenes from old videos using ZoomedScene will almost certainly
# break, as it was restructured.
class ZoomedScene(Scene):
"""
Move around self.little_rectangle to determine
which part of the screen is zoomed in on.
"""
class ZoomedScene(MovingCameraScene):
CONFIG = {
"camera_class": MultiCamera,
"zoomed_display_height": 3,
@ -33,109 +32,62 @@ class ZoomedScene(Scene):
"zoom_activated": False,
}
def activate_zooming(self):
self.generate_big_rectangle()
self.setup_zoomed_canvas()
self.setup_zoomed_camera()
self.zoom_activated = True
def animate_activate_zooming(self):
self.activate_zooming()
self.play(*map(FadeIn, [
self.little_rectangle, self.big_rectangle
]))
def disactivate_zooming(self):
self.remove(self.big_rectangle, self.little_rectangle)
self.zoom_activated = False
def get_zoomed_camera_mobject(self):
return self.little_rectangle
def get_zoomed_screen(self):
return self.big_rectangle
def generate_big_rectangle(self):
height, width = self.zoomed_canvas_frame_shape
self.big_rectangle = Rectangle(
height=height,
width=width,
color=self.square_color
def setup(self):
MovingCameraScene.setup(self)
# Initialize camera and display
zoomed_camera = MovingCamera(**self.zoomed_camera_config)
zoomed_display = ImageMobjectFromCamera(
zoomed_camera, **self.zoomed_camera_image_mobject_config
)
if self.zoomed_canvas_center is not None:
self.big_rectangle.shift(self.zoomed_canvas_center)
elif self.zoomed_canvas_corner is not None:
self.big_rectangle.to_corner(
self.zoomed_canvas_corner,
buff=self.zoomed_canvas_corner_buff
)
self.add(self.big_rectangle)
zoomed_display.add_display_frame()
for mob in zoomed_camera.frame, zoomed_display:
mob.stretch_to_fit_height(self.zoomed_display_height)
mob.stretch_to_fit_width(self.zoomed_display_width)
zoomed_camera.frame.scale(self.zoom_factor)
def setup_zoomed_canvas(self):
upper_left = self.big_rectangle.get_corner(UP + LEFT)
lower_right = self.big_rectangle.get_corner(DOWN + RIGHT)
pixel_coords = self.camera.points_to_pixel_coords(
np.array([upper_left, lower_right])
)
self.zoomed_canvas_pixel_indices = pixel_coords
(up, left), (down, right) = pixel_coords
self.zoomed_canvas_pixel_shape = (
right - left,
down - up,
)
def setup_zoomed_camera(self):
self.little_rectangle = self.big_rectangle.copy()
self.little_rectangle.scale(1. / self.zoom_factor)
self.little_rectangle.move_to(
self.little_rectangle_start_position
)
self.zoomed_camera = MovingCamera(
self.little_rectangle,
pixel_shape=self.zoomed_canvas_pixel_shape,
background=self.zoomed_camera_background
)
self.add(self.little_rectangle)
# TODO, is there a better way to hanld this?
self.zoomed_camera.adjusted_thickness = lambda x: x
def get_frame(self):
frame = Scene.get_frame(self)
if self.zoom_activated:
(up, left), (down, right) = self.zoomed_canvas_pixel_indices
frame[left:right, up:down, :] = self.zoomed_camera.get_image()
return frame
def set_camera_pixel_array(self, pixel_array):
self.camera.set_pixel_array(pixel_array)
if self.zoom_activated:
(up, left), (down, right) = self.zoomed_canvas_pixel_indices
self.zoomed_camera.set_pixel_array(
pixel_array[left:right, up:down])
def set_camera_background(self, background):
self.set_camera_pixel_array(self, background)
# TODO, check this...
def reset_camera(self):
self.camera.reset()
if self.zoom_activated:
self.zoomed_camera.reset()
def capture_mobjects_in_camera(self, mobjects, **kwargs):
self.camera.capture_mobjects(mobjects, **kwargs)
if self.zoom_activated:
if self.big_rectangle in mobjects:
mobjects = list(mobjects)
mobjects.remove(self.big_rectangle)
self.zoomed_camera.capture_mobjects(
mobjects, **kwargs
)
def get_moving_mobjects(self, *animations):
moving_mobjects = Scene.get_moving_mobjects(self, *animations)
if self.zoom_activated and self.little_rectangle in moving_mobjects:
# When the camera is moving, so is everything,
return self.mobjects
# Position camera and display
zoomed_camera.frame.move_to(self.zoomed_camera_frame_starting_position)
if self.zoomed_display_center is not None:
zoomed_display.move_to(self.zoomed_display_center)
else:
return moving_mobjects
zoomed_display.to_corner(
self.zoomed_display_corner,
buff=self.zoomed_display_corner_buff
)
self.zoomed_camera = zoomed_camera
self.zoomed_display = zoomed_display
def activate_zooming(self, animate=False):
self.zoom_activated = True
self.camera.add_image_mobject_from_camera(self.zoomed_display)
if animate:
self.play(self.get_zoom_in_animation())
self.play(self.get_zoomed_display_pop_out_animation())
self.add_foreground_mobjects(
self.zoomed_camera.frame,
self.zoomed_display,
)
def get_zoom_in_animation(self, run_time=2, **kwargs):
frame = self.zoomed_camera.frame
full_frame_height = self.camera.get_frame_height()
full_frame_width = self.camera.get_frame_width()
frame.save_state()
frame.stretch_to_fit_width(full_frame_width)
frame.stretch_to_fit_height(full_frame_height)
frame.center()
frame.set_stroke(width=0)
return ApplyMethod(frame.restore, run_time=run_time, **kwargs)
def get_zoomed_display_pop_out_animation(self, **kwargs):
display = self.zoomed_display
display.save_state(use_deepcopy=True)
display.replace(self.zoomed_camera.frame, stretch=True)
return ApplyMethod(display.restore)
def get_zoom_factor(self):
return fdiv(
self.zoomed_camera.frame.get_height(),
self.zoomed_display.get_height()
)

View file

@ -39,7 +39,8 @@ def color_to_int_rgb(color):
return (255 * color_to_rgb(color)).astype('uint8')
def color_to_int_rgba(color, alpha=255):
def color_to_int_rgba(color, opacity=1.0):
alpha = int(255 * opacity)
return np.append(color_to_int_rgb(color), alpha)

View file

@ -34,7 +34,7 @@ def get_scene_output_directory(scene_class):
def get_movie_output_directory(scene_class, camera_config, frame_duration):
directory = get_scene_output_directory(scene_class)
sub_dir = "%dp%d" % (
camera_config["pixel_shape"][0],
camera_config["pixel_height"],
int(1.0 / frame_duration)
)
return guarantee_existance(os.path.join(directory, sub_dir))