From 87b7635988715ac52057b0d2d949e4786156eee1 Mon Sep 17 00:00:00 2001 From: Ben Hambrecht Date: Wed, 7 Feb 2018 07:35:18 +0100 Subject: [PATCH 01/25] bug fix session with Grant --- active_projects/basel.py | 43 ++++++++++++++++++++-------------------- mobject/mobject.py | 3 ++- topics/light.py | 3 +-- 3 files changed, 24 insertions(+), 25 deletions(-) diff --git a/active_projects/basel.py b/active_projects/basel.py index f2cc4e33..e3371005 100644 --- a/active_projects/basel.py +++ b/active_projects/basel.py @@ -643,8 +643,8 @@ class SingleLighthouseScene(PiCreatureScene): self.light_source.set_max_opacity_spotlight(0.001) self.add(self.light_source.spotlight) - updater = ScreenTracker(self.light_source) - self.add(updater) + self.screen_tracker = ScreenTracker(self.light_source) + self.add(self.screen_tracker) self.wait() @@ -730,12 +730,12 @@ class SingleLighthouseScene(PiCreatureScene): sun_position = ORIGIN #[-100,0,0] - self.play( - FadeOut(self.angle_arc), - FadeOut(self.angle_indicator) - ) + # self.play( + # FadeOut(self.angle_arc), + # FadeOut(self.angle_indicator) + # ) - self.sun = self.light_source.deepcopy() + #self.sun = self.light_source.deepcopy() #self.sun.ambient_light.opacity_function = inverse_quadratic(1,2,1) #self.sun.num_levels = NUM_LEVELS, @@ -744,18 +744,17 @@ class SingleLighthouseScene(PiCreatureScene): - # self.sun.spotlight.change_opacity_function(lambda r: 0.5) # self.sun.set_radius(150) - self.sun.move_source_to(sun_position) + #self.sun.move_source_to(sun_position) # self.sun.update() # self.add(self.sun) # temporarily remove the screen tracker while we move the source - #self.remove(self.screen_tracker) + self.remove(self.screen_tracker) - print self.sun.spotlight.source_point + #print self.sun.spotlight.source_point self.play( self.light_source.spotlight.move_source_to,sun_position, @@ -921,7 +920,7 @@ class ScreenShapingScene(ThreeDScene): self.right_shift_screen_while_showing_light_indicator_and_distance_arrow() self.left_shift_again() - self.morph_into_3d() + #self.morph_into_3d() def setup_elements(self): @@ -952,16 +951,16 @@ class ScreenShapingScene(ThreeDScene): self.lighthouse = self.light_source.lighthouse screen_tracker = ScreenTracker(self.light_source) - self.add(screen_tracker) + self.add(screen_tracker,self.light_source.shadow) - self.add_foreground_mobject(self.light_source.shadow) + #self.add_foreground_mobject(self.light_source.shadow) # Morty self.morty = Mortimer().scale(0.3).next_to(self.screen, RIGHT, buff = 0.5) # Add everything to the scene self.add(self.ambient_light, self.lighthouse) - self.add_foreground_mobject(self.morty) + #self.add_foreground_mobject(self.morty) self.wait() self.play(FadeIn(self.screen)) @@ -971,13 +970,13 @@ class ScreenShapingScene(ThreeDScene): dimmed_ambient_light = self.ambient_light.copy() dimmed_ambient_light.dimming(AMBIENT_DIMMED) - self.light_source.set_max_opacity_spotlight(0.001) - + #self.light_source.set_max_opacity_spotlight(0.001) + print self.light_source.shadow.get_center() self.play( self.light_source.set_max_opacity_spotlight,1.0, # this hides Morty for a moment, why? + Transform(self.ambient_light,dimmed_ambient_light), FadeIn(self.light_source.shadow), - Transform(self.ambient_light,dimmed_ambient_light) - ) + ) self.wait() @@ -1313,7 +1312,7 @@ class BackToEulerSumScene(PiCreatureScene): self.play(FadeIn(indicator)) indicator_copy = indicator.deepcopy() - self.add(indicator_copy) + self.add_foreground_mobject(indicator_copy) self.play(indicator_copy.move_to, bubble) moving_light_source = light_source.deepcopy() @@ -1330,7 +1329,7 @@ class BackToEulerSumScene(PiCreatureScene): indicator_copy = indicator.deepcopy() indicator_copy.move_to(previous_point) - self.add(indicator_copy) + self.add_foreground_mobject(indicator_copy) indicator_target = indicator.deepcopy() indicator_target.move_to(point) @@ -1349,7 +1348,7 @@ class BackToEulerSumScene(PiCreatureScene): bubble_indicator_target.reading.scale_to_fit_height(0.8*indicator.get_height()) bubble_indicator_target.move_to(bubble) - self.add(bubble_indicator) + self.add_foreground_mobject(bubble_indicator) self.play( Transform(bubble_indicator,bubble_indicator_target) diff --git a/mobject/mobject.py b/mobject/mobject.py index 53509d0e..d1e77b32 100644 --- a/mobject/mobject.py +++ b/mobject/mobject.py @@ -103,7 +103,8 @@ class Mobject(Container): def copy(self): #TODO, either justify reason for shallow copy, or #remove this redundancy everywhere - # return self.deepcopy() + return self.deepcopy() + copy_mobject = copy.copy(self) copy_mobject.points = np.array(self.points) copy_mobject.submobjects = [ diff --git a/topics/light.py b/topics/light.py index 2512be6e..6c4860a2 100644 --- a/topics/light.py +++ b/topics/light.py @@ -21,7 +21,7 @@ from scipy.spatial import ConvexHull LIGHT_COLOR = YELLOW -SHADOW_COLOR = BLACK +SHADOW_COLOR = RED SWITCH_ON_RUN_TIME = 1.5 FAST_SWITCH_ON_RUN_TIME = 0.1 NUM_LEVELS = 30 @@ -169,7 +169,6 @@ class LightSource(VMobject): for point in self.screen.get_anchors(): projected_screen_points.append(self.spotlight.project(point)) - print "projected", self.screen.get_anchors(), "onto", projected_screen_points From 8e0c84946f78ec9053a72ea37a4dd0d9119bc9cd Mon Sep 17 00:00:00 2001 From: Ben Hambrecht Date: Wed, 7 Feb 2018 09:45:36 +0100 Subject: [PATCH 02/25] added getter and setter for VectorizedPoint's location --- mobject/vectorized_mobject.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/mobject/vectorized_mobject.py b/mobject/vectorized_mobject.py index 7c840f8e..5377eb44 100644 --- a/mobject/vectorized_mobject.py +++ b/mobject/vectorized_mobject.py @@ -457,6 +457,12 @@ class VectorizedPoint(VMobject): def get_height(self): return self.artificial_height + def get_location(self): + return self.get_anchors()[0] + + def set_location(self,new_loc): + self.set_points(np.array([new_loc])) + class BackgroundColoredVMobject(VMobject): CONFIG = { # Can be set to None, using set_background_array to initialize instead From 2c4fa36073f1e9da1223046cd24fc788d2652b12 Mon Sep 17 00:00:00 2001 From: Ben Hambrecht Date: Wed, 7 Feb 2018 09:46:01 +0100 Subject: [PATCH 03/25] bug fixes in light. Finally working again! --- topics/light.py | 74 ++++++++++++++++++++++++++++++------------------- 1 file changed, 46 insertions(+), 28 deletions(-) diff --git a/topics/light.py b/topics/light.py index db0e9ff3..43182761 100644 --- a/topics/light.py +++ b/topics/light.py @@ -21,7 +21,7 @@ from scipy.spatial import ConvexHull LIGHT_COLOR = YELLOW -SHADOW_COLOR = RED +SHADOW_COLOR = BLACK SWITCH_ON_RUN_TIME = 1.5 FAST_SWITCH_ON_RUN_TIME = 0.1 NUM_LEVELS = 30 @@ -53,7 +53,7 @@ class LightSource(VMobject): # a spotlight # and a shadow CONFIG = { - "source_point": ORIGIN, + "source_point": VectorizedPoint(location = ORIGIN, stroke_width = 0, fill_opacity = 0), "color": LIGHT_COLOR, "num_levels": 10, "radius": 5, @@ -64,9 +64,12 @@ class LightSource(VMobject): } def generate_points(self): + + self.add(self.source_point) + self.lighthouse = Lighthouse() self.ambient_light = AmbientLight( - source_point = self.source_point, + source_point = VectorizedPoint(location = self.get_source_point()), color = self.color, num_levels = self.num_levels, radius = self.radius, @@ -75,7 +78,7 @@ class LightSource(VMobject): ) if self.has_screen(): self.spotlight = Spotlight( - source_point = self.source_point, + source_point = VectorizedPoint(location = self.get_source_point()), color = self.color, num_levels = self.num_levels, radius = self.radius, @@ -87,11 +90,11 @@ class LightSource(VMobject): self.spotlight = Spotlight() self.shadow = VMobject(fill_color = SHADOW_COLOR, fill_opacity = 1.0, stroke_color = BLACK) - self.lighthouse.next_to(self.source_point,DOWN,buff = 0) - self.ambient_light.move_source_to(self.source_point) + self.lighthouse.next_to(self.get_source_point(),DOWN,buff = 0) + self.ambient_light.move_source_to(self.get_source_point()) if self.has_screen(): - self.spotlight.move_source_to(self.source_point) + self.spotlight.move_source_to(self.get_source_point()) self.update_shadow() self.add(self.ambient_light,self.spotlight,self.lighthouse, self.shadow) @@ -118,24 +121,24 @@ class LightSource(VMobject): self.spotlight.screen = new_screen else: # Note: See below - # index = self.submobjects.index(self.spotlight) + index = self.submobjects.index(self.spotlight) self.remove(self.spotlight) self.spotlight = Spotlight( - source_point = self.source_point, + source_point = VectorizedPoint(location = self.get_source_point()), color = self.color, num_levels = self.num_levels, radius = self.radius, screen = new_screen ) - self.spotlight.move_source_to(self.source_point) + self.spotlight.move_source_to(self.get_source_point()) # Note: This line will make spotlight show up at the end # of the submojects list, which can make it show up on # top of the shadow. To make it show up in the # same spot, you could try the following line, # where "index" is what I defined above: - # self.submobjects.insert(index, self.spotlight) - self.add(self.spotlight) + self.submobjects.insert(index, self.spotlight) + #self.add(self.spotlight) # in any case self.screen = new_screen @@ -145,12 +148,12 @@ class LightSource(VMobject): def move_source_to(self,point): apoint = np.array(point) - v = apoint - self.source_point + v = apoint - self.get_source_point() # Note: As discussed, things stand to behave better if source # point is a submobject, so that it automatically interpolates # during an animation, and other updates can be defined wrt # that source point's location - self.source_point = apoint + self.source_point.set_location(apoint) self.lighthouse.next_to(apoint,DOWN,buff = 0) self.ambient_light.move_source_to(apoint) if self.has_screen(): @@ -167,10 +170,12 @@ class LightSource(VMobject): self.spotlight.update_sectors() self.update_shadow() + def get_source_point(self): + return self.source_point.get_location() def update_shadow(self): - point = self.source_point + point = self.get_source_point() projected_screen_points = [] if not self.has_screen(): return @@ -178,7 +183,7 @@ class LightSource(VMobject): projected_screen_points.append(self.spotlight.project(point)) - projected_source = project_along_vector(self.source_point,self.spotlight.projection_direction()) + projected_source = project_along_vector(self.get_source_point(),self.spotlight.projection_direction()) projected_point_cloud_3d = np.append( projected_screen_points, @@ -196,7 +201,7 @@ class LightSource(VMobject): hull = [] # we also need the projected source point - source_point_2d = np.dot(self.spotlight.project(self.source_point),back_rotation_matrix.T)[:2] + source_point_2d = np.dot(self.spotlight.project(self.get_source_point()),back_rotation_matrix.T)[:2] index = 0 for point in point_cloud_2d[hull_2d.vertices]: @@ -291,7 +296,7 @@ class AmbientLight(VMobject): # * the number of subdivisions (levels, annuli) CONFIG = { - "source_point" : ORIGIN, + "source_point": VectorizedPoint(location = ORIGIN, stroke_width = 0, fill_opacity = 0), "opacity_function" : lambda r : 1.0/(r+1.0)**2, "color" : LIGHT_COLOR, "max_opacity" : 1.0, @@ -300,8 +305,6 @@ class AmbientLight(VMobject): } def generate_points(self): - self.source_point = np.array(self.source_point) - # in theory, this method is only called once, right? # so removing submobs shd not be necessary # @@ -311,6 +314,8 @@ class AmbientLight(VMobject): for submob in self.submobjects: self.remove(submob) + self.add(self.source_point) + # create annuli self.radius = float(self.radius) dr = self.radius / self.num_levels @@ -322,22 +327,28 @@ class AmbientLight(VMobject): color = self.color, fill_opacity = alpha ) - annulus.move_arc_center_to(self.source_point) + annulus.move_arc_center_to(self.get_source_point()) self.add(annulus) def move_source_to(self,point): # Note: Best to rewrite in terms of VectorizedPoint source_point - v = np.array(point) - self.source_point - self.source_point = np.array(point) - self.shift(v) + self.source_point.set_location(np.array(point)) + self.move_to(point) return self + def get_source_point(self): + return self.source_point.get_location() + + + + + def dimming(self,new_alpha): @@ -352,7 +363,7 @@ class AmbientLight(VMobject): class Spotlight(VMobject): CONFIG = { - "source_point" : ORIGIN, + "source_point": VectorizedPoint(location = ORIGIN, stroke_width = 0, fill_opacity = 0), "opacity_function" : lambda r : 1.0/(r/2+1.0)**2, "color" : LIGHT_COLOR, "max_opacity" : 1.0, @@ -378,10 +389,17 @@ class Spotlight(VMobject): w = project_along_vector(point,v) return w + + def get_source_point(self): + return self.source_point.get_location() + + def generate_points(self): self.submobjects = [] + self.add(self.source_point) + if self.screen != None: # look for the screen and create annular sectors lower_angle, upper_angle = self.viewing_angles(self.screen) @@ -417,14 +435,14 @@ class Spotlight(VMobject): projected_RIGHT = self.project(RIGHT) omega = angle_between_vectors(rotated_RIGHT,projected_RIGHT) annular_sector.rotate(omega, axis = self.projection_direction()) - annular_sector.move_arc_center_to(self.source_point) + annular_sector.move_arc_center_to(self.get_source_point()) return annular_sector def viewing_angle_of_point(self,point): # as measured from the positive x-axis v1 = self.project(RIGHT) - v2 = self.project(np.array(point) - self.source_point) + v2 = self.project(np.array(point) - self.get_source_point()) absolute_angle = angle_between_vectors(v1, v2) # determine the angle's sign depending on their plane's # choice of orientation. That choice is set by the camera @@ -473,7 +491,7 @@ class Spotlight(VMobject): return u def move_source_to(self,point): - self.source_point = np.array(point) + self.source_point.set_location(np.array(point)) self.update_sectors() return self From 082d67ec84d8bf9e5479d35ee4c68fa11368a507 Mon Sep 17 00:00:00 2001 From: Ben Hambrecht Date: Wed, 7 Feb 2018 18:09:36 +0100 Subject: [PATCH 04/25] incremental progress --- active_projects/basel.py | 69 ++++++++++++++++++++-------------------- 1 file changed, 35 insertions(+), 34 deletions(-) diff --git a/active_projects/basel.py b/active_projects/basel.py index e3371005..473fabd0 100644 --- a/active_projects/basel.py +++ b/active_projects/basel.py @@ -1211,7 +1211,7 @@ class BackToEulerSumScene(PiCreatureScene): def construct(self): - #self.remove(self.get_primary_pi_creature()) + self.remove(self.get_primary_pi_creature()) NUM_CONES = 7 NUM_VISIBLE_CONES = 6 @@ -1232,7 +1232,7 @@ class BackToEulerSumScene(PiCreatureScene): ) self.number_line.label_direction = DOWN - self.number_line.shift(3*UP) + #self.number_line.shift(3*UP) self.number_line_labels = self.number_line.get_number_mobjects() self.add(self.number_line,self.number_line_labels) @@ -1256,7 +1256,7 @@ class BackToEulerSumScene(PiCreatureScene): bubble = ThoughtBubble(direction = RIGHT, width = 4, height = 3, file_name = "Bubbles_thought.svg") - bubble.next_to(randy,LEFT) + bubble.next_to(randy,LEFT+UP) bubble.set_fill(color = BLACK, opacity = 1) self.play( @@ -1295,58 +1295,57 @@ class BackToEulerSumScene(PiCreatureScene): self.play(SwitchOn(light_source.ambient_light)) - # create an indicator and move a copy of it into the thought bubble + # create an indicator that will move along the number line indicator = LightIndicator(color = LIGHT_COLOR, radius = INDICATOR_RADIUS, opacity_for_unit_intensity = 0.2, #OPACITY_FOR_UNIT_INTENSITY, show_reading = False - ) + ) indicator_reading = euler_sum[0] indicator_reading.scale_to_fit_height(0.5 * indicator.get_height()) indicator_reading.move_to(indicator.get_center()) indicator.add(indicator_reading) + indicator.tex_reading = indicator_reading indicator.foreground.set_fill(None,opacities[0]) indicator.move_to(point) indicator.set_intensity(intensities[0]) - self.play(FadeIn(indicator)) - indicator_copy = indicator.deepcopy() - self.add_foreground_mobject(indicator_copy) - self.play(indicator_copy.move_to, bubble) - - moving_light_source = light_source.deepcopy() - - - ls = [] - ls.append(moving_light_source) + self.add_foreground_mobject(indicator) + collection_point = np.array([-5,2,0]) - for i in range(2,NUM_VISIBLE_CONES + 1): + + + for i in range(2, NUM_VISIBLE_CONES + 1): previous_point = self.number_line.number_to_point(i - 1) point = self.number_line.number_to_point(i) - - indicator_copy = indicator.deepcopy() - indicator_copy.move_to(previous_point) - self.add_foreground_mobject(indicator_copy) + # Create and position the target indicator (next on number line). indicator_target = indicator.deepcopy() indicator_target.move_to(point) - ls[-1].set_max_opacity_ambient(0.0001) - self.add(ls[-1].ambient_light) - ls.append(ls[-1].deepcopy()) - - ls[-1].move_source_to(point) - ls[-1].set_max_opacity_ambient(0.5) - - bubble_indicator = indicator_copy.deepcopy() + # Here we make a copy that will move into the thought bubble. + bubble_indicator = indicator.deepcopy() + # And its target bubble_indicator_target = bubble_indicator.deepcopy() - bubble_indicator_target.set_intensity(intensities[i-1]) - bubble_indicator_target.reading = euler_sum[-2+2*i] + bubble_indicator_target.set_intensity(intensities[i - 2]) + + # give the target the appropriate reading + bubble_indicator_target.remove(bubble_indicator_target.tex_reading) + bubble_indicator_target.tex_reading = euler_sum[2*i-4] + bubble_indicator_target.add(bubble_indicator_target.tex_reading) + # center it in the indicator + bubble_indicator_target.tex_reading.move_to( + bubble_indicator_target.get_center()) + bubble_indicator_target.reading.scale_to_fit_height(0.8*indicator.get_height()) - bubble_indicator_target.move_to(bubble) + + # position the target in the thought bubble + print "center:", bubble_indicator_target.get_center() + bubble_indicator_target.move_to(collection_point) + print "-center:", bubble_indicator_target.get_center() self.add_foreground_mobject(bubble_indicator) @@ -1355,11 +1354,13 @@ class BackToEulerSumScene(PiCreatureScene): ) self.play( - Transform(ls[-2], ls[-1]), - Transform(indicator_copy,indicator_target), - + Transform(indicator,indicator_target), ) + new_light = light_source.deepcopy() + new_light.move_source_to(point) + self.play(SwitchOn(new_light.ambient_light)) + From 968b0dd811b79879b172b2fbf06a8a7a93a248ee Mon Sep 17 00:00:00 2001 From: Ben Hambrecht Date: Wed, 7 Feb 2018 21:28:58 +0100 Subject: [PATCH 05/25] fixed spacing issue in BackToEulerSumScene --- .gitignore | 4 ++++ active_projects/basel.py | 22 +++++++++++++--------- 2 files changed, 17 insertions(+), 9 deletions(-) diff --git a/.gitignore b/.gitignore index f0f3ca3f..743924f1 100644 --- a/.gitignore +++ b/.gitignore @@ -9,3 +9,7 @@ files/ ben_playground.py ben_cairo_test.py + +.idea/manim.iml + +*.xml diff --git a/active_projects/basel.py b/active_projects/basel.py index 473fabd0..52763903 100644 --- a/active_projects/basel.py +++ b/active_projects/basel.py @@ -1288,8 +1288,8 @@ class BackToEulerSumScene(PiCreatureScene): v = point - self.number_line.number_to_point(0) light_source = LightSource() light_source.move_source_to(point) - light_source.ambient_light.move_source_to(point) - light_source.lighthouse.move_to(point) + #light_source.ambient_light.move_source_to(point) + #light_source.lighthouse.move_to(point) self.play(FadeIn(light_source.lighthouse)) self.play(SwitchOn(light_source.ambient_light)) @@ -1316,15 +1316,17 @@ class BackToEulerSumScene(PiCreatureScene): collection_point = np.array([-5,2,0]) - for i in range(2, NUM_VISIBLE_CONES + 1): previous_point = self.number_line.number_to_point(i - 1) point = self.number_line.number_to_point(i) + + v = point - previous_point + #print v # Create and position the target indicator (next on number line). indicator_target = indicator.deepcopy() - indicator_target.move_to(point) + indicator_target.shift(v) # Here we make a copy that will move into the thought bubble. bubble_indicator = indicator.deepcopy() @@ -1333,19 +1335,16 @@ class BackToEulerSumScene(PiCreatureScene): bubble_indicator_target.set_intensity(intensities[i - 2]) # give the target the appropriate reading + euler_sum[2*i-4].move_to(bubble_indicator_target) bubble_indicator_target.remove(bubble_indicator_target.tex_reading) bubble_indicator_target.tex_reading = euler_sum[2*i-4] bubble_indicator_target.add(bubble_indicator_target.tex_reading) # center it in the indicator - bubble_indicator_target.tex_reading.move_to( - bubble_indicator_target.get_center()) bubble_indicator_target.reading.scale_to_fit_height(0.8*indicator.get_height()) # position the target in the thought bubble - print "center:", bubble_indicator_target.get_center() bubble_indicator_target.move_to(collection_point) - print "-center:", bubble_indicator_target.get_center() self.add_foreground_mobject(bubble_indicator) @@ -1358,7 +1357,12 @@ class BackToEulerSumScene(PiCreatureScene): ) new_light = light_source.deepcopy() - new_light.move_source_to(point) + w = new_light.get_source_point() + print "old:", w + print "moving source to:", w + (i-1)*v + new_light.move_source_to(w + (i-1)*v) + w2 = new_light.get_source_point() + print "new:", w2 self.play(SwitchOn(new_light.ambient_light)) From 4a11d8f42df09ac2860fce90aa2d50aa5fc2b103 Mon Sep 17 00:00:00 2001 From: Ben Hambrecht Date: Thu, 8 Feb 2018 12:04:21 +0100 Subject: [PATCH 06/25] movement of indicators in BackToEulerSumScene --- active_projects/basel.py | 165 ++++++++++++++------------------------- 1 file changed, 60 insertions(+), 105 deletions(-) diff --git a/active_projects/basel.py b/active_projects/basel.py index 52763903..1c82c778 100644 --- a/active_projects/basel.py +++ b/active_projects/basel.py @@ -971,7 +971,6 @@ class ScreenShapingScene(ThreeDScene): dimmed_ambient_light = self.ambient_light.copy() dimmed_ambient_light.dimming(AMBIENT_DIMMED) #self.light_source.set_max_opacity_spotlight(0.001) - print self.light_source.shadow.get_center() self.play( self.light_source.set_max_opacity_spotlight,1.0, # this hides Morty for a moment, why? Transform(self.ambient_light,dimmed_ambient_light), @@ -1179,9 +1178,6 @@ class ScreenShapingScene(ThreeDScene): dphi = phi1 - phi0 dtheta = theta1 - theta0 - print "moving camera from (", phi0/DEGREES, ", ", theta0/DEGREES, ") to (", phi1/DEGREES, ", ", theta1/DEGREES, ")" - - camera_target_point = target_point # self.camera.get_spherical_coords(45 * DEGREES, -60 * DEGREES) projection_direction = self.camera.spherical_coords_to_point(phi1,theta1, 1) @@ -1298,7 +1294,7 @@ class BackToEulerSumScene(PiCreatureScene): # create an indicator that will move along the number line indicator = LightIndicator(color = LIGHT_COLOR, radius = INDICATOR_RADIUS, - opacity_for_unit_intensity = 0.2, #OPACITY_FOR_UNIT_INTENSITY, + opacity_for_unit_intensity = OPACITY_FOR_UNIT_INTENSITY, show_reading = False ) indicator_reading = euler_sum[0] @@ -1306,14 +1302,20 @@ class BackToEulerSumScene(PiCreatureScene): indicator_reading.move_to(indicator.get_center()) indicator.add(indicator_reading) indicator.tex_reading = indicator_reading + # the TeX reading is too bright at full intensity + indicator.tex_reading.set_fill(color = BLACK) indicator.foreground.set_fill(None,opacities[0]) + indicator.move_to(point) indicator.set_intensity(intensities[0]) + self.add_foreground_mobject(indicator) - collection_point = np.array([-5,2,0]) + collection_point = np.array([-6.,2.,0.]) + left_shift = 0.2*LEFT + collected_indicators = Mobject() for i in range(2, NUM_VISIBLE_CONES + 1): @@ -1328,6 +1330,7 @@ class BackToEulerSumScene(PiCreatureScene): indicator_target = indicator.deepcopy() indicator_target.shift(v) + # Here we make a copy that will move into the thought bubble. bubble_indicator = indicator.deepcopy() # And its target @@ -1337,127 +1340,79 @@ class BackToEulerSumScene(PiCreatureScene): # give the target the appropriate reading euler_sum[2*i-4].move_to(bubble_indicator_target) bubble_indicator_target.remove(bubble_indicator_target.tex_reading) - bubble_indicator_target.tex_reading = euler_sum[2*i-4] + bubble_indicator_target.tex_reading = euler_sum[2*i-4].copy() bubble_indicator_target.add(bubble_indicator_target.tex_reading) # center it in the indicator - bubble_indicator_target.reading.scale_to_fit_height(0.8*indicator.get_height()) - + if bubble_indicator_target.tex_reading.get_tex_string() != "1": + bubble_indicator_target.tex_reading.scale_to_fit_height(0.8*indicator.get_height()) + # the target is less bright, possibly switch to a white text color + if bubble_indicator_target.intensity < 0.7: + bubble_indicator.tex_reading.set_fill(color = WHITE) + # position the target in the thought bubble + collection_point bubble_indicator_target.move_to(collection_point) + self.add_foreground_mobject(bubble_indicator) - self.play( - Transform(bubble_indicator,bubble_indicator_target) - ) + + self.wait() self.play( - Transform(indicator,indicator_target), + Transform(bubble_indicator,bubble_indicator_target), + collected_indicators.shift,left_shift, ) + collected_indicators.add(bubble_indicator) + new_light = light_source.deepcopy() w = new_light.get_source_point() - print "old:", w - print "moving source to:", w + (i-1)*v - new_light.move_source_to(w + (i-1)*v) + new_light.move_source_to(w + (i-2)*v) w2 = new_light.get_source_point() - print "new:", w2 - self.play(SwitchOn(new_light.ambient_light)) + + self.add(new_light.lighthouse) + self.play( + Transform(indicator,indicator_target), + new_light.shift,v, + ) + + self.play(SwitchOn(new_light.ambient_light), + ) + # quickly switch on off-screen light cones + for i in range(NUM_VISIBLE_CONES,NUM_CONES): + indicator_start_time = 0.5 * (i+1) * FAST_SWITCH_ON_RUN_TIME/light_source.ambient_light.radius * self.number_line.unit_size + indicator_stop_time = indicator_start_time + FAST_INDICATOR_UPDATE_TIME + indicator_rate_func = squish_rate_func(#smooth, 0.8, 0.9) + smooth,indicator_start_time,indicator_stop_time) + ls = LightSource() + point = point = self.number_line.number_to_point(i) + ls.move_source_to(point) + self.play( + SwitchOn(ls.ambient_light, run_time = FAST_SWITCH_ON_RUN_TIME), + ) - # switch on lights off-screen - # while writing an ellipsis in the series - # and fading out the stack of indicators - # and fading in pi^2/6 instead - # move a copy of pi^2/6 down to the series - # ans fade in an equals sign + # and morph indicator stack into limit value + sum_indicator = LightIndicator(color = LIGHT_COLOR, + radius = INDICATOR_RADIUS, + opacity_for_unit_intensity = OPACITY_FOR_UNIT_INTENSITY, + show_reading = False + ) + sum_indicator.set_intensity(intensities[0] * np.pi**2/6) + sum_indicator_reading = TexMobject("{\pi^2 \over 6}") + sum_indicator_reading.scale_to_fit_height(0.8 * sum_indicator.get_height()) + sum_indicator.add(sum_indicator_reading) + sum_indicator.move_to(collection_point) - - - - # for i in range(1,NUM_VISIBLE_CONES+1): - - # # create light indicators - # # but they contain fractions! - # indicator = LightIndicator(color = LIGHT_COLOR, - # radius = INDICATOR_RADIUS, - # opacity_for_unit_intensity = OPACITY_FOR_UNIT_INTENSITY, - # show_reading = False - # ) - # indicator.set_intensity(intensities[i-1]) - # indicator_reading = euler_sum[-2+2*i] - # indicator_reading.scale_to_fit_height(0.8*indicator.get_height()) - # indicator_reading.move_to(indicator.get_center()) - # indicator.add(indicator_reading) - # indicator.foreground.set_fill(None,opacities[i-1]) - - - # if i == 1: - # indicator.next_to(randy,DOWN,buff = 5) - # indicator_reading.scale_to_fit_height(0.4*indicator.get_height()) - # # otherwise we get a huge 1 - # else: - # indicator.next_to(light_indicators[i-2],DOWN, buff = 0.2) - - # light_indicators.append(indicator) - # indicators_as_mob.add(indicator) - - - # bubble.add_content(indicators_as_mob) - # indicators_as_mob.shift(DOWN+0.5*LEFT) - - - # for lh in lighthouses: - # self.add_foreground_mobject(lh) - - - # # slowly switch on visible light cones and increment indicator - # for (i,ambient_light) in zip(range(NUM_VISIBLE_CONES),ambient_lights[:NUM_VISIBLE_CONES]): - # indicator_start_time = 0.4 * (i+1) * SWITCH_ON_RUN_TIME/ambient_light.radius * self.number_line.unit_size - # indicator_stop_time = indicator_start_time + INDICATOR_UPDATE_TIME - # indicator_rate_func = squish_rate_func( - # smooth,indicator_start_time,indicator_stop_time) - # self.play( - # SwitchOn(ambient_light), - # FadeIn(light_indicators[i]) - # ) - - # # quickly switch on off-screen light cones and increment indicator - # for (i,ambient_light) in zip(range(NUM_VISIBLE_CONES,NUM_CONES),ambient_lights[NUM_VISIBLE_CONES:NUM_CONES]): - # indicator_start_time = 0.5 * (i+1) * FAST_SWITCH_ON_RUN_TIME/ambient_light.radius * self.number_line.unit_size - # indicator_stop_time = indicator_start_time + FAST_INDICATOR_UPDATE_TIME - # indicator_rate_func = squish_rate_func(#smooth, 0.8, 0.9) - # smooth,indicator_start_time,indicator_stop_time) - # self.play( - # SwitchOn(ambient_light, run_time = FAST_SWITCH_ON_RUN_TIME), - # ) - - - # # show limit value in light indicator and an equals sign - # sum_indicator = LightIndicator(color = LIGHT_COLOR, - # radius = INDICATOR_RADIUS, - # opacity_for_unit_intensity = OPACITY_FOR_UNIT_INTENSITY, - # show_reading = False - # ) - # sum_indicator.set_intensity(intensities[0] * np.pi**2/6) - # sum_indicator_reading = TexMobject("{\pi^2 \over 6}") - # sum_indicator_reading.scale_to_fit_height(0.8 * sum_indicator.get_height()) - # sum_indicator.add(sum_indicator_reading) - - # brace = Brace(indicators_as_mob, direction = RIGHT, buff = 0.5) - # brace.shift(2*RIGHT) - # sum_indicator.next_to(brace,RIGHT) - - - # self.play( - # ShowCreation(brace), - # ShowCreation(sum_indicator), # DrawBorderThenFill - # ) + self.play( + Transform(collected_indicators,sum_indicator) + ) From 7871e557d2d5802806c72ab9b395e197e64ede8f Mon Sep 17 00:00:00 2001 From: Ben Hambrecht Date: Thu, 8 Feb 2018 12:04:53 +0100 Subject: [PATCH 07/25] bug fixes --- topics/light.py | 46 ++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 38 insertions(+), 8 deletions(-) diff --git a/topics/light.py b/topics/light.py index 43182761..35c2fb20 100644 --- a/topics/light.py +++ b/topics/light.py @@ -32,6 +32,7 @@ AMBIENT_FULL = 0.5 AMBIENT_DIMMED = 0.2 SPOTLIGHT_FULL = 0.9 SPOTLIGHT_DIMMED = 0.2 +LIGHTHOUSE_HEIGHT = 0.8 LIGHT_COLOR = YELLOW DEGREES = TAU/360 @@ -60,7 +61,8 @@ class LightSource(VMobject): "screen": None, "opacity_function": inverse_quadratic(1,2,1), "max_opacity_ambient": AMBIENT_FULL, - "max_opacity_spotlight": SPOTLIGHT_FULL + "max_opacity_spotlight": SPOTLIGHT_FULL, + "camera": None } def generate_points(self): @@ -84,7 +86,8 @@ class LightSource(VMobject): radius = self.radius, screen = self.screen, opacity_function = self.opacity_function, - max_opacity = self.max_opacity_spotlight + max_opacity = self.max_opacity_spotlight, + camera = self.camera ) else: self.spotlight = Spotlight() @@ -116,19 +119,26 @@ class LightSource(VMobject): self.max_opacity_spotlight = new_opacity self.spotlight.dimming(new_opacity) + def set_camera(self,new_cam): + self.camera = new_cam + self.spotlight.camera = new_cam + + def set_screen(self, new_screen): if self.has_screen(): self.spotlight.screen = new_screen else: # Note: See below index = self.submobjects.index(self.spotlight) + camera = self.spotlight.camera self.remove(self.spotlight) self.spotlight = Spotlight( source_point = VectorizedPoint(location = self.get_source_point()), color = self.color, num_levels = self.num_levels, radius = self.radius, - screen = new_screen + screen = new_screen, + camera = self.camera ) self.spotlight.move_source_to(self.get_source_point()) @@ -154,7 +164,10 @@ class LightSource(VMobject): # during an animation, and other updates can be defined wrt # that source point's location self.source_point.set_location(apoint) - self.lighthouse.next_to(apoint,DOWN,buff = 0) + #self.lighthouse.next_to(apoint,DOWN,buff = 0) + #self.ambient_light.move_source_to(apoint) + self.lighthouse.shift(v) + #self.ambient_light.shift(v) self.ambient_light.move_source_to(apoint) if self.has_screen(): self.spotlight.move_source_to(apoint) @@ -278,7 +291,7 @@ class SwitchOff(LaggedStart): class Lighthouse(SVGMobject): CONFIG = { "file_name" : "lighthouse", - "height" : 0.5 + "height" : LIGHTHOUSE_HEIGHT } def move_to(self,point): @@ -327,15 +340,18 @@ class AmbientLight(VMobject): color = self.color, fill_opacity = alpha ) - annulus.move_arc_center_to(self.get_source_point()) + annulus.move_to(self.get_source_point()) self.add(annulus) def move_source_to(self,point): # Note: Best to rewrite in terms of VectorizedPoint source_point - self.source_point.set_location(np.array(point)) - self.move_to(point) + old_source_point = self.get_source_point() + #self.source_point.set_location(np.array(point)) + + self.shift(point - old_source_point) + return self @@ -360,6 +376,19 @@ class AmbientLight(VMobject): submob.set_fill(opacity = new_submob_alpha) + + + + + + + + + + + + + class Spotlight(VMobject): CONFIG = { @@ -447,6 +476,7 @@ class Spotlight(VMobject): # determine the angle's sign depending on their plane's # choice of orientation. That choice is set by the camera # position, i. e. projection direction + if np.dot(self.projection_direction(),np.cross(v1, v2)) > 0: return absolute_angle else: From 3ef4b8a32ac198851162d3cd1dd3185b99b63ed8 Mon Sep 17 00:00:00 2001 From: Ben Hambrecht Date: Thu, 8 Feb 2018 12:32:08 +0100 Subject: [PATCH 08/25] Finished unpolished BackToEulerSumScene --- active_projects/basel.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/active_projects/basel.py b/active_projects/basel.py index 1c82c778..5afb71f2 100644 --- a/active_projects/basel.py +++ b/active_projects/basel.py @@ -1310,7 +1310,7 @@ class BackToEulerSumScene(PiCreatureScene): indicator.move_to(point) indicator.set_intensity(intensities[0]) - + self.play(FadeIn(indicator)) self.add_foreground_mobject(indicator) collection_point = np.array([-6.,2.,0.]) @@ -1351,7 +1351,6 @@ class BackToEulerSumScene(PiCreatureScene): bubble_indicator.tex_reading.set_fill(color = WHITE) # position the target in the thought bubble - collection_point bubble_indicator_target.move_to(collection_point) @@ -1375,8 +1374,10 @@ class BackToEulerSumScene(PiCreatureScene): self.add(new_light.lighthouse) self.play( Transform(indicator,indicator_target), - new_light.shift,v, + new_light.lighthouse.shift,v, ) + new_light.move_source_to(w + (i-1)*v) + new_light.lighthouse.move_to(w + (i-1)*v) self.play(SwitchOn(new_light.ambient_light), ) @@ -1406,6 +1407,7 @@ class BackToEulerSumScene(PiCreatureScene): ) sum_indicator.set_intensity(intensities[0] * np.pi**2/6) sum_indicator_reading = TexMobject("{\pi^2 \over 6}") + sum_indicator_reading.set_fill(color = BLACK) sum_indicator_reading.scale_to_fit_height(0.8 * sum_indicator.get_height()) sum_indicator.add(sum_indicator_reading) sum_indicator.move_to(collection_point) From 0e85da2939d2ba3a2a7284fb0e83fd8995da7d54 Mon Sep 17 00:00:00 2001 From: Ben Hambrecht Date: Thu, 8 Feb 2018 13:27:23 +0100 Subject: [PATCH 09/25] TwoLightSourcesScene: Hooked up indicator so it updates as replacement light moves around --- active_projects/basel.py | 79 ++++++++++++++++++++++++++-------------- 1 file changed, 51 insertions(+), 28 deletions(-) diff --git a/active_projects/basel.py b/active_projects/basel.py index 5afb71f2..0f92e125 100644 --- a/active_projects/basel.py +++ b/active_projects/basel.py @@ -1432,9 +1432,9 @@ class TwoLightSourcesScene(PiCreatureScene): INDICATOR_RADIUS = 0.6 OPACITY_FOR_UNIT_INTENSITY = 0.5 - A = np.array([5,-3,0]) - B = np.array([-5,3,0]) - C = np.array([-5,-3,0]) + A = np.array([5.,-3.,0.]) + B = np.array([-5.,3.,0.]) + C = np.array([-5.,-3.,0.]) morty = self.get_primary_pi_creature() morty.scale(0.3).flip().move_to(C) @@ -1462,24 +1462,42 @@ class TwoLightSourcesScene(PiCreatureScene): Write(indicator) ) - ambient_light1 = AmbientLight(max_opacity = MAX_OPACITY) - ambient_light1.move_source_to(A) - ambient_light2 = AmbientLight(max_opacity = MAX_OPACITY) - ambient_light2.move_source_to(B) - lighthouse1 = Lighthouse() - lighthouse1.next_to(A,DOWN,buff = 0) - lighthouse2 = Lighthouse() - lighthouse2.next_to(B,DOWN,buff = 0) + def intensity_for_location(loc,light_source = None): + intensity = 0.0 + distance = np.linalg.norm(C - loc) + print "source:", light_source.get_source_point(), "loc:", loc + print "distance:", distance + intensity = light_source.opacity_function(distance) / OPACITY_FOR_UNIT_INTENSITY + print "intensity:", intensity + return intensity + + def intensity_for_light_source(ls): + return intensity_for_location(ls.get_source_point(), light_source = ls) + + + ls1 = LightSource() + ls2 = LightSource().deepcopy() + #print "===" + #print ls1.get_source_point() + ls1.move_source_to(A) + #print ls1.get_source_point() + #print "===" + #print ls2.get_source_point() + ls2.move_source_to(B) + #print ls2.get_source_point() self.play( - FadeIn(lighthouse1), - FadeIn(lighthouse2), - SwitchOn(ambient_light1), - SwitchOn(ambient_light2) + FadeIn(ls1.lighthouse), + FadeIn(ls2.lighthouse), + SwitchOn(ls1.ambient_light), + SwitchOn(ls2.ambient_light) ) + intensity = intensity_for_light_source(ls1) + intensity += intensity_for_light_source(ls2) + self.play( - UpdateLightIndicator(indicator,1.5) + UpdateLightIndicator(indicator,intensity) ) self.wait() @@ -1490,25 +1508,30 @@ class TwoLightSourcesScene(PiCreatureScene): indicator.shift, 2 * UP ) - ambient_light3 = AmbientLight(max_opacity = MAX_OPACITY) - lighthouse3 = Lighthouse() - lighthouse3.next_to(ambient_light3,DOWN,buff = 0) - ambient_light3.add(lighthouse3) - #moving_light.move_to(np.array([6,3.5,0])) + ls3 = LightSource().deepcopy() + ls3.move_to(np.array([6,3.5,0])) + + intensity = intensity_for_light_source(ls3) + self.play( - FadeOut(ambient_light1), - FadeOut(lighthouse1), - FadeOut(ambient_light2), - FadeOut(lighthouse2), + SwitchOff(ls1.ambient_light), + FadeOut(ls1.lighthouse), + SwitchOff(ls2.ambient_light), + FadeOut(ls2.lighthouse), - FadeIn(ambient_light3), - UpdateLightIndicator(new_indicator,0.0) + SwitchOn(ls3.ambient_light), + FadeIn(ls3.lighthouse), + UpdateLightIndicator(new_indicator,intensity) ) self.wait() + + new_location = np.array([-3,-2.,0.]) + intensity = intensity_for_location(new_location, light_source = ls3) self.play( - ambient_light3.shift,UP+RIGHT + ls3.move_source_to,new_location, + UpdateLightIndicator(new_indicator,intensity) ) From 9fa18d54e5a36980ec9a9b5d5b7391f453cbe4de Mon Sep 17 00:00:00 2001 From: Ben Hambrecht Date: Thu, 8 Feb 2018 17:04:04 +0100 Subject: [PATCH 10/25] Finished unpolished TwoLightSourcesScene --- active_projects/basel.py | 146 ++++++++++++++++++++++++++------------- 1 file changed, 99 insertions(+), 47 deletions(-) diff --git a/active_projects/basel.py b/active_projects/basel.py index 0f92e125..5b20dfc7 100644 --- a/active_projects/basel.py +++ b/active_projects/basel.py @@ -58,7 +58,7 @@ inverse_quadratic = lambda maxint,scale,cutoff: inverse_power_law(maxint,scale,c class AngleUpdater(ContinualAnimation): def __init__(self, angle_arc, spotlight, **kwargs): self.angle_arc = angle_arc - self.source_point = angle_arc.get_arc_center() + self.spotlight = spotlight ContinualAnimation.__init__(self, self.angle_arc, **kwargs) @@ -66,9 +66,9 @@ class AngleUpdater(ContinualAnimation): new_arc = self.angle_arc.copy().set_bound_angles( start = self.spotlight.start_angle(), stop = self.spotlight.stop_angle() - ) + ) new_arc.generate_points() - new_arc.move_arc_center_to(self.source_point) + new_arc.move_arc_center_to(self.spotlight.get_source_point()) self.angle_arc.points = new_arc.points self.angle_arc.add_tip(tip_length = ARC_TIP_LENGTH, at_start = True, at_end = True) @@ -83,7 +83,9 @@ class LightIndicator(Mobject): "intensity": 0, "opacity_for_unit_intensity": 1, "precision": 3, - "show_reading": True + "show_reading": True, + "measurement_point": ORIGIN, + "light_source": None } def generate_points(self): @@ -105,6 +107,25 @@ class LightIndicator(Mobject): self.foreground.set_fill(opacity=new_opacity) ChangeDecimalToValue(self.reading, new_int).update(1) return self + + def get_measurement_point(self): + if self.measurement_point != None: + return self.measurement_point + else: + return self.get_center() + + + def measured_intensity(self): + distance = np.linalg.norm(self.get_measurement_point() - + self.light_source.get_source_point()) + intensity = self.light_source.opacity_function(distance) / self.opacity_for_unit_intensity + return intensity + + def continual_update(self): + if self.light_source == None: + print "Indicator cannot update, reason: no light source found" + self.set_intensity(self.measured_intensity()) + @@ -123,6 +144,12 @@ class UpdateLightIndicator(AnimationGroup): self.mobject = indicator +class ContinualLightIndicatorUpdate(ContinualAnimation): + + def update_mobject(self,dt): + self.mobject.continual_update() + + @@ -581,7 +608,7 @@ class SingleLighthouseScene(PiCreatureScene): self.setup_elements() self.setup_angle() # spotlight and angle msmt change when screen rotates - #self.rotate_screen() + self.rotate_screen() self.morph_lighthouse_into_sun() @@ -679,7 +706,7 @@ class SingleLighthouseScene(PiCreatureScene): self.angle_arc = Arc(radius = 5, start_angle = self.light_source.spotlight.start_angle(), angle = self.light_source.spotlight.opening_angle(), tip_length = ARC_TIP_LENGTH) #angle_arc.add_tip(at_start = True, at_end = True) - self.angle_arc.move_arc_center_to(self.light_source.source_point) + self.angle_arc.move_arc_center_to(self.light_source.get_source_point()) # angle msmt (decimal number) @@ -727,37 +754,37 @@ class SingleLighthouseScene(PiCreatureScene): - sun_position = ORIGIN #[-100,0,0] + sun_position = [-100,0,0] - # self.play( - # FadeOut(self.angle_arc), - # FadeOut(self.angle_indicator) - # ) + self.play( + FadeOut(self.angle_arc), + FadeOut(self.angle_indicator) + ) - #self.sun = self.light_source.deepcopy() + self.sun = self.light_source.deepcopy() - #self.sun.ambient_light.opacity_function = inverse_quadratic(1,2,1) #self.sun.num_levels = NUM_LEVELS, #self.sun.set_radius(150) #self.sun.set_max_opacity_ambient(AMBIENT_FULL) -# self.sun.spotlight.change_opacity_function(lambda r: 0.5) - # self.sun.set_radius(150) - #self.sun.move_source_to(sun_position) + self.sun.spotlight.change_opacity_function(lambda r: 0.5) + self.sun.set_radius(150) + self.sun.move_source_to(sun_position) # self.sun.update() # self.add(self.sun) # temporarily remove the screen tracker while we move the source - self.remove(self.screen_tracker) + #self.remove(self.screen_tracker) #print self.sun.spotlight.source_point self.play( - self.light_source.spotlight.move_source_to,sun_position, + #self.light_source.spotlight.move_source_to,sun_position, + Transform(self.light_source,self.sun) ) #self.add(ScreenTracker(self.sun)) @@ -1437,7 +1464,9 @@ class TwoLightSourcesScene(PiCreatureScene): C = np.array([-5.,-3.,0.]) morty = self.get_primary_pi_creature() - morty.scale(0.3).flip().move_to(C) + morty.scale(0.3).flip() + right_pupil = morty.pupils[1] + morty.next_to(C, LEFT, buff = 0, submobject_to_align = right_pupil) horizontal = VMobject(stroke_width = 1) horizontal.set_points_as_corners([C,A]) @@ -1462,20 +1491,8 @@ class TwoLightSourcesScene(PiCreatureScene): Write(indicator) ) - def intensity_for_location(loc,light_source = None): - intensity = 0.0 - distance = np.linalg.norm(C - loc) - print "source:", light_source.get_source_point(), "loc:", loc - print "distance:", distance - intensity = light_source.opacity_function(distance) / OPACITY_FOR_UNIT_INTENSITY - print "intensity:", intensity - return intensity - def intensity_for_light_source(ls): - return intensity_for_location(ls.get_source_point(), light_source = ls) - - - ls1 = LightSource() + ls1 = LightSource(radius = 20) ls2 = LightSource().deepcopy() #print "===" #print ls1.get_source_point() @@ -1493,8 +1510,10 @@ class TwoLightSourcesScene(PiCreatureScene): SwitchOn(ls2.ambient_light) ) - intensity = intensity_for_light_source(ls1) - intensity += intensity_for_light_source(ls2) + distance1 = np.linalg.norm(C - ls1.get_source_point()) + intensity = ls1.ambient_light.opacity_function(distance1) / indicator.opacity_for_unit_intensity + distance2 = np.linalg.norm(C - ls2.get_source_point()) + intensity += ls2.ambient_light.opacity_function(distance2) / indicator.opacity_for_unit_intensity self.play( UpdateLightIndicator(indicator,intensity) @@ -1502,38 +1521,71 @@ class TwoLightSourcesScene(PiCreatureScene): self.wait() + ls3 = LightSource().deepcopy() + ls3.move_to(np.array([6,3.5,0])) + new_indicator = indicator.copy() + new_indicator.light_source = ls3 + new_indicator.measurement_point = C self.add(new_indicator) self.play( indicator.shift, 2 * UP ) - ls3 = LightSource().deepcopy() - ls3.move_to(np.array([6,3.5,0])) - intensity = intensity_for_light_source(ls3) + + #intensity = intensity_for_light_source(ls3) self.play( SwitchOff(ls1.ambient_light), - FadeOut(ls1.lighthouse), + #FadeOut(ls1.lighthouse), SwitchOff(ls2.ambient_light), - FadeOut(ls2.lighthouse), + #FadeOut(ls2.lighthouse), + UpdateLightIndicator(new_indicator,0.0) + ) + # create a *continual* animation for the replacement source + updater = ContinualLightIndicatorUpdate(new_indicator) + self.add(updater) + + self.play( SwitchOn(ls3.ambient_light), FadeIn(ls3.lighthouse), - UpdateLightIndicator(new_indicator,intensity) + ) self.wait() - new_location = np.array([-3,-2.,0.]) - intensity = intensity_for_location(new_location, light_source = ls3) - self.play( - ls3.move_source_to,new_location, - UpdateLightIndicator(new_indicator,intensity) - ) - + # move the light source around + # TODO: moving along a path arc + + location = np.array([-3,-2.,0.]) + self.play(ls3.move_source_to,location) + location = np.array([6.,1.,0.]) + self.play(ls3.move_source_to,location) + location = np.array([5.,2.,0.]) + self.play(ls3.move_source_to,location) + closer_location = interpolate(location, C, 0.5) + self.play(ls3.move_source_to,closer_location) + self.play(ls3.move_source_to,location) + + # maybe move in a circle around C using a loop? + + # find the coords of the altitude point H + # as the solution of a certain LSE + xA = A[0] + yA = A[1] + xB = B[0] + yB = B[1] + xC = C[0] + yC = C[1] + matrix = np.array([[yA - yB, xB - xA], [xA - xB, yA - yB]]) # sic + vector = np.array([xB * yA - xA * yB, xC * (xA - xB) + yC * (yA - yB)]) + H2 = np.linalg.solve(matrix,vector) + H = np.append(H2, 0.) + + self.play(ls3.move_source_to,H) From 5b395853b719f7a0c8373e5c7ff237c32aea7a20 Mon Sep 17 00:00:00 2001 From: Ben Hambrecht Date: Fri, 9 Feb 2018 07:56:49 +0100 Subject: [PATCH 11/25] changes to how the sectors are redrawn --- active_projects/basel.py | 70 ++++++++++++++++++++++++++++++++++++++-- topics/light.py | 15 +++++---- 2 files changed, 76 insertions(+), 9 deletions(-) diff --git a/active_projects/basel.py b/active_projects/basel.py index 5b20dfc7..d08b7be3 100644 --- a/active_projects/basel.py +++ b/active_projects/basel.py @@ -1492,8 +1492,8 @@ class TwoLightSourcesScene(PiCreatureScene): ) - ls1 = LightSource(radius = 20) - ls2 = LightSource().deepcopy() + ls1 = LightSource(radius = 20, num_levels = 50) + ls2 = ls1.deepcopy() #print "===" #print ls1.get_source_point() ls1.move_source_to(A) @@ -1521,7 +1521,7 @@ class TwoLightSourcesScene(PiCreatureScene): self.wait() - ls3 = LightSource().deepcopy() + ls3 = ls1.deepcopy() ls3.move_to(np.array([6,3.5,0])) new_indicator = indicator.copy() @@ -1589,6 +1589,70 @@ class TwoLightSourcesScene(PiCreatureScene): + # draw lines to complete the geometric picture + # and label the lengths + + line_a = VMobject() + line_a.set_points_as_corners([B,C]) + line_b = VMobject() + line_b.set_points_as_corners([A,C]) + line_c = VMobject() + line_c.set_points_as_corners([A,B]) + line_h = VMobject() + line_h.set_points_as_corners([H,C]) + + label_a = TexMobject("a") + label_a.next_to(line_a, LEFT, buff = 0.5) + label_b = TexMobject("b") + label_b.next_to(line_b, DOWN, buff = 0.5) + label_h = TexMobject("h") + label_h.next_to(line_h.get_center(), RIGHT, buff = 0.5) + + self.play( + ShowCreation(line_a), + Write(label_a) + ) + + self.play( + ShowCreation(line_b), + Write(label_b) + ) + + self.play( + ShowCreation(line_c), + ) + + self.play( + ShowCreation(line_h), + Write(label_h) + ) + + + # state the IPT + theorem_location = np.array([3.,2.,0.]) + theorem = TexMobject("{1\over a^2} + {1\over b^2} = {1\over h^2}") + theorem_name = TextMobject("Inverse Pythagorean Theorem") + buffer = 1.2 + theorem_box = Rectangle(width = buffer*theorem.get_width(), + height = buffer*theorem.get_height()) + + theorem.move_to(theorem_location) + theorem_box.move_to(theorem_location) + theorem_name.next_to(theorem_box,UP) + + self.play( + Write(theorem), + ) + + self.play( + ShowCreation(theorem_box), + Write(theorem_name), + ) + + + + + diff --git a/topics/light.py b/topics/light.py index 35c2fb20..67f1458c 100644 --- a/topics/light.py +++ b/topics/light.py @@ -346,11 +346,9 @@ class AmbientLight(VMobject): def move_source_to(self,point): - # Note: Best to rewrite in terms of VectorizedPoint source_point - old_source_point = self.get_source_point() - #self.source_point.set_location(np.array(point)) - - self.shift(point - old_source_point) + #old_source_point = self.get_source_point() + #self.shift(point - old_source_point) + self.move_to(point) return self @@ -522,6 +520,8 @@ class Spotlight(VMobject): def move_source_to(self,point): self.source_point.set_location(np.array(point)) + #self.source_point.move_to(np.array(point)) + #self.move_to(point) self.update_sectors() return self @@ -532,9 +532,12 @@ class Spotlight(VMobject): for submob in self.submobject_family(): if type(submob) == AnnularSector: lower_angle, upper_angle = self.viewing_angles(self.screen) - dr = submob.outer_radius - submob.inner_radius + #dr = submob.outer_radius - submob.inner_radius + dr = self.radius / self.num_levels new_submob = self.new_sector(submob.inner_radius,dr,lower_angle,upper_angle) submob.points = new_submob.points + submob.set_fill(opacity = 10 * self.opacity_function(submob.outer_radius)) + print "new opacity:", self.opacity_function(submob.outer_radius) From 404a8b1b3f02c9b8a9eedaffc6376e9d7b02e897 Mon Sep 17 00:00:00 2001 From: Grant Sanderson Date: Fri, 9 Feb 2018 12:55:24 -0800 Subject: [PATCH 12/25] Added floobits files to gitignore --- .gitignore | 2 + old_projects/nn/mnist_loader.py | 170 ++++++++++++++++---------------- 2 files changed, 87 insertions(+), 85 deletions(-) diff --git a/.gitignore b/.gitignore index 961fd7cb..fda3b94b 100644 --- a/.gitignore +++ b/.gitignore @@ -8,6 +8,8 @@ prettiness_hall_of_fame.py files/ ben_playground.py ben_cairo_test.py +.floo +.flooignore *.xml *.iml diff --git a/old_projects/nn/mnist_loader.py b/old_projects/nn/mnist_loader.py index 611bbf70..7687f413 100644 --- a/old_projects/nn/mnist_loader.py +++ b/old_projects/nn/mnist_loader.py @@ -1,85 +1,85 @@ -""" -mnist_loader -~~~~~~~~~~~~ - -A library to load the MNIST image data. For details of the data -structures that are returned, see the doc strings for ``load_data`` -and ``load_data_wrapper``. In practice, ``load_data_wrapper`` is the -function usually called by our neural network code. -""" - -#### Libraries -# Standard library -import cPickle -import gzip - -# Third-party libraries -import numpy as np - -def load_data(): - """Return the MNIST data as a tuple containing the training data, - the validation data, and the test data. - - The ``training_data`` is returned as a tuple with two entries. - The first entry contains the actual training images. This is a - numpy ndarray with 50,000 entries. Each entry is, in turn, a - numpy ndarray with 784 values, representing the 28 * 28 = 784 - pixels in a single MNIST image. - - The second entry in the ``training_data`` tuple is a numpy ndarray - containing 50,000 entries. Those entries are just the digit - values (0...9) for the corresponding images contained in the first - entry of the tuple. - - The ``validation_data`` and ``test_data`` are similar, except - each contains only 10,000 images. - - This is a nice data format, but for use in neural networks it's - helpful to modify the format of the ``training_data`` a little. - That's done in the wrapper function ``load_data_wrapper()``, see - below. - """ - f = gzip.open('/Users/grant/cs/neural-networks-and-deep-learning/data/mnist.pkl.gz', 'rb') - training_data, validation_data, test_data = cPickle.load(f) - f.close() - return (training_data, validation_data, test_data) - -def load_data_wrapper(): - """Return a tuple containing ``(training_data, validation_data, - test_data)``. Based on ``load_data``, but the format is more - convenient for use in our implementation of neural networks. - - In particular, ``training_data`` is a list containing 50,000 - 2-tuples ``(x, y)``. ``x`` is a 784-dimensional numpy.ndarray - containing the input image. ``y`` is a 10-dimensional - numpy.ndarray representing the unit vector corresponding to the - correct digit for ``x``. - - ``validation_data`` and ``test_data`` are lists containing 10,000 - 2-tuples ``(x, y)``. In each case, ``x`` is a 784-dimensional - numpy.ndarry containing the input image, and ``y`` is the - corresponding classification, i.e., the digit values (integers) - corresponding to ``x``. - - Obviously, this means we're using slightly different formats for - the training data and the validation / test data. These formats - turn out to be the most convenient for use in our neural network - code.""" - tr_d, va_d, te_d = load_data() - training_inputs = [np.reshape(x, (784, 1)) for x in tr_d[0]] - training_results = [vectorized_result(y) for y in tr_d[1]] - training_data = zip(training_inputs, training_results) - validation_inputs = [np.reshape(x, (784, 1)) for x in va_d[0]] - validation_data = zip(validation_inputs, va_d[1]) - test_inputs = [np.reshape(x, (784, 1)) for x in te_d[0]] - test_data = zip(test_inputs, te_d[1]) - return (training_data, validation_data, test_data) - -def vectorized_result(j): - """Return a 10-dimensional unit vector with a 1.0 in the jth - position and zeroes elsewhere. This is used to convert a digit - (0...9) into a corresponding desired output from the neural - network.""" - e = np.zeros((10, 1)) - e[j] = 1.0 - return e +""" +mnist_loader +~~~~~~~~~~~~ + +A library to load the MNIST image data. For details of the data +structures that are returned, see the doc strings for ``load_data`` +and ``load_data_wrapper``. In practice, ``load_data_wrapper`` is the +function usually called by our neural network code. +""" + +#### Libraries +# Standard library +import cPickle +import gzip + +# Third-party libraries +import numpy as np + +def load_data(): + """Return the MNIST data as a tuple containing the training data, + the validation data, and the test data. + + The ``training_data`` is returned as a tuple with two entries. + The first entry contains the actual training images. This is a + numpy ndarray with 50,000 entries. Each entry is, in turn, a + numpy ndarray with 784 values, representing the 28 * 28 = 784 + pixels in a single MNIST image. + + The second entry in the ``training_data`` tuple is a numpy ndarray + containing 50,000 entries. Those entries are just the digit + values (0...9) for the corresponding images contained in the first + entry of the tuple. + + The ``validation_data`` and ``test_data`` are similar, except + each contains only 10,000 images. + + This is a nice data format, but for use in neural networks it's + helpful to modify the format of the ``training_data`` a little. + That's done in the wrapper function ``load_data_wrapper()``, see + below. + """ + f = gzip.open('/Users/grant/cs/neural-networks-and-deep-learning/data/mnist.pkl.gz', 'rb') + training_data, validation_data, test_data = cPickle.load(f) + f.close() + return (training_data, validation_data, test_data) + +def load_data_wrapper(): + """Return a tuple containing ``(training_data, validation_data, + test_data)``. Based on ``load_data``, but the format is more + convenient for use in our implementation of neural networks. + + In particular, ``training_data`` is a list containing 50,000 + 2-tuples ``(x, y)``. ``x`` is a 784-dimensional numpy.ndarray + containing the input image. ``y`` is a 10-dimensional + numpy.ndarray representing the unit vector corresponding to the + correct digit for ``x``. + + ``validation_data`` and ``test_data`` are lists containing 10,000 + 2-tuples ``(x, y)``. In each case, ``x`` is a 784-dimensional + numpy.ndarry containing the input image, and ``y`` is the + corresponding classification, i.e., the digit values (integers) + corresponding to ``x``. + + Obviously, this means we're using slightly different formats for + the training data and the validation / test data. These formats + turn out to be the most convenient for use in our neural network + code.""" + tr_d, va_d, te_d = load_data() + training_inputs = [np.reshape(x, (784, 1)) for x in tr_d[0]] + training_results = [vectorized_result(y) for y in tr_d[1]] + training_data = zip(training_inputs, training_results) + validation_inputs = [np.reshape(x, (784, 1)) for x in va_d[0]] + validation_data = zip(validation_inputs, va_d[1]) + test_inputs = [np.reshape(x, (784, 1)) for x in te_d[0]] + test_data = zip(test_inputs, te_d[1]) + return (training_data, validation_data, test_data) + +def vectorized_result(j): + """Return a 10-dimensional unit vector with a 1.0 in the jth + position and zeroes elsewhere. This is used to convert a digit + (0...9) into a corresponding desired output from the neural + network.""" + e = np.zeros((10, 1)) + e[j] = 1.0 + return e From f8653f2946af3370f38af2f3b87bb7abdb3593e3 Mon Sep 17 00:00:00 2001 From: Grant Sanderson Date: Fri, 9 Feb 2018 15:26:12 -0800 Subject: [PATCH 13/25] Opening scene of Uncertainty project --- active_projects/uncertainty.py | 239 +++++++++++++++++++++++++++++++++ 1 file changed, 239 insertions(+) create mode 100644 active_projects/uncertainty.py diff --git a/active_projects/uncertainty.py b/active_projects/uncertainty.py new file mode 100644 index 00000000..83109534 --- /dev/null +++ b/active_projects/uncertainty.py @@ -0,0 +1,239 @@ +from helpers import * +import scipy + +from animation.animation import Animation +from animation.transform import * +from animation.simple_animations import * +from animation.playground import * +from animation.continual_animation import * +from topics.geometry import * +from topics.characters import * +from topics.functions import * +from topics.fractals import * +from topics.number_line import * +from topics.combinatorics import * +from topics.numerals import * +from topics.three_dimensions import * +from topics.objects import * +from topics.probability import * +from topics.complex_numbers import * +from topics.common_scenes import * +from scene import Scene +from scene.reconfigurable_scene import ReconfigurableScene +from scene.zoomed_scene import * +from camera import Camera +from mobject import * +from mobject.image_mobject import * +from mobject.vectorized_mobject import * +from mobject.svg_mobject import * +from mobject.tex_mobject import * +from topics.graph_scene import * + +from active_projects.fourier import * + + +class GaussianDistributionWrapper(Line): + """ + This is meant to encode a 2d normal distribution as + a mobject (so as to be able to have it be interpolated + during animations). It is a line whose start_point coordinates + encode the coordinates of mu, and whose end_point - start_point + encodes the coordinates of sigma. + """ + CONFIG = { + "stroke_width" : 0, + "mu_x" : 0, + "sigma_x" : 1, + "mu_y" : 0, + "sigma_y" : 0, + } + def __init__(self, **kwargs): + Line.__init__(self, ORIGIN, RIGHT, **kwargs) + self.change_parameters(self.mu_x, self.mu_y, self.sigma_x, self.sigma_y) + + def change_parameters(self, mu_x = None, mu_y = None, sigma_x = None, sigma_y = None): + curr_parameters = self.get_parameteters() + args = [mu_x, mu_y, sigma_x, sigma_y] + new_parameters = [ + arg or curr + for curr, arg in zip(curr_parameters, args) + ] + mu_x, mu_y, sigma_x, sigma_y = new_parameters + mu_point = mu_x*RIGHT + mu_y*UP + sigma_vect = sigma_x*RIGHT + sigma_y*UP + self.put_start_and_end_on(mu_point, mu_point + sigma_vect) + return self + + def get_parameteters(self): + """ Return mu_x, mu_y, sigma_x, sigma_y""" + start, end = self.get_start_and_end() + return tuple(it.chain(start[:2], (end - start)[:2])) + + def get_random_points(self, size = 1): + mu_x, mu_y, sigma_x, sigma_y = self.get_parameteters() + x_vals = np.random.normal(mu_x, sigma_x, size) + y_vals = np.random.normal(mu_y, sigma_y, size) + return np.array([ + x*RIGHT + y*UP + for x, y in zip(x_vals, y_vals) + ]) + +class ProbabalisticMobjectCloud(ContinualAnimation): + CONFIG = { + "fill_opacity" : 0.25, + "n_copies" : 100, + "gaussian_distribution_wrapper_config" : { + "sigma_x" : 1, + } + } + def __init__(self, prototype, **kwargs): + digest_config(self, kwargs) + fill_opacity = self.fill_opacity or prototype.get_fill_opacity() + self.gaussian_distribution_wrapper = GaussianDistributionWrapper( + **self.gaussian_distribution_wrapper_config + ) + group = VGroup(*[ + prototype.copy().set_fill(opacity = fill_opacity) + for x in range(self.n_copies) + ]) + ContinualAnimation.__init__(self, group, **kwargs) + + def update_mobject(self, dt): + group = self.mobject + points = self.gaussian_distribution_wrapper.get_random_points(len(group)) + for mob, point in zip(group, points): + self.update_mobject_by_point(mob, point) + return self + + def update_mobject_by_point(self, mobject, point): + mobject.move_to(point) + return self + +class ProbabalisticDotCloud(ProbabalisticMobjectCloud): + CONFIG = { + "color" : BLUE, + } + def __init__(self, **kwargs): + digest_config(self, kwargs) + dot = Dot(color = self.color) + ProbabalisticMobjectCloud.__init__(self, dot) + +class ProbabalisticVectorCloud(ProbabalisticMobjectCloud): + CONFIG = { + "color" : RED, + "n_copies" : 20, + "fill_opacity" : 0.5, + "center_func" : lambda : ORIGIN, + } + def __init__(self, **kwargs): + digest_config(self, kwargs) + vector = Vector( + RIGHT, color = self.color, + max_tip_length_to_length_ratio = 1, + ) + ProbabalisticMobjectCloud.__init__(self, vector) + + def update_mobject_by_point(self, vector, point): + vector.put_start_and_end_on( + self.center_func(), + point + ) + +################### + +class MentionUncertaintyPrinciple(TeacherStudentsScene): + def construct(self): + title = TextMobject("Heisenberg Uncertainty Principle") + title.to_edge(UP) + + dot_cloud = ProbabalisticDotCloud() + vector_cloud = ProbabalisticVectorCloud( + gaussian_distribution_wrapper_config = {"sigma_x" : 0.2}, + center_func = dot_cloud.gaussian_distribution_wrapper.get_start, + ) + for cloud in dot_cloud, vector_cloud: + gdw = cloud.gaussian_distribution_wrapper + gdw.move_to(title.get_center(), LEFT) + gdw.shift(2*DOWN) + vector_cloud.gaussian_distribution_wrapper.shift(3*RIGHT) + + def get_brace_text_group_update(gdw, vect, text): + brace = Brace(gdw, vect) + text = brace.get_tex("\\sigma_{\\text{%s}}"%text, buff = SMALL_BUFF) + group = VGroup(brace, text) + def update_group(group): + brace, text = group + brace.match_width(gdw, stretch = True) + brace.next_to(gdw, vect) + text.next_to(brace, vect, buff = SMALL_BUFF) + return ContinualUpdateFromFunc(group, update_group) + + dot_brace_anim = get_brace_text_group_update( + dot_cloud.gaussian_distribution_wrapper, + DOWN, "position", + ) + vector_brace_anim = get_brace_text_group_update( + vector_cloud.gaussian_distribution_wrapper, + UP, "momentum", + ) + + self.add(title) + self.add(dot_cloud) + self.play( + Write(title), + self.teacher.change, "raise_right_hand", + self.get_student_changes(*["pondering"]*3) + ) + self.play( + Write(dot_brace_anim.mobject, run_time = 1) + ) + self.add(dot_brace_anim) + self.wait() + # self.wait(2) + self.play( + dot_cloud.gaussian_distribution_wrapper.change_parameters, + {"sigma_x" : 0.1}, + run_time = 2, + ) + self.wait() + self.add(vector_cloud) + self.play( + FadeIn(vector_brace_anim.mobject) + ) + self.add(vector_brace_anim) + self.play( + vector_cloud.gaussian_distribution_wrapper.change_parameters, + {"sigma_x" : 1}, + self.get_student_changes(*3*["confused"]), + run_time = 3, + ) + self.wait() + #Back and forth + self.play( + dot_cloud.gaussian_distribution_wrapper.change_parameters, + {"sigma_x" : 2}, + vector_cloud.gaussian_distribution_wrapper.change_parameters, + {"sigma_x" : 0.1}, + run_time = 3, + ) + self.change_student_modes("thinking", "erm", "sassy") + self.play( + dot_cloud.gaussian_distribution_wrapper.change_parameters, + {"sigma_x" : 0.1}, + vector_cloud.gaussian_distribution_wrapper.change_parameters, + {"sigma_x" : 1}, + run_time = 3, + ) + self.wait(2) + + + + + + + + + + + + From 90e4951ef5bba0d1a2698bd9c4165adae6293840 Mon Sep 17 00:00:00 2001 From: Grant Sanderson Date: Sat, 10 Feb 2018 18:32:17 -0800 Subject: [PATCH 14/25] Implemented FourierTradeoff in uncertainty.py --- active_projects/fourier.py | 40 +++++++- active_projects/uncertainty.py | 182 ++++++++++++++++++++++++++++++--- 2 files changed, 202 insertions(+), 20 deletions(-) diff --git a/active_projects/fourier.py b/active_projects/fourier.py index 429b5041..83857a08 100644 --- a/active_projects/fourier.py +++ b/active_projects/fourier.py @@ -30,18 +30,48 @@ from mobject.svg_mobject import * from mobject.tex_mobject import * from topics.graph_scene import * +USE_ALMOST_FOURIER_BY_DEFAULT = True +NUM_SAMPLES_FOR_FFT = 1000 + + +def get_fourier_graph( + axes, time_func, t_min, t_max, + n_samples = NUM_SAMPLES_FOR_FFT, + complex_to_real_func = lambda z : z.real, + color = RED, + ): + # N = n_samples + # T = time_range/n_samples + time_range = float(t_max - t_min) + time_step_size = time_range/n_samples + time_samples = time_func(np.linspace(t_min, t_max, n_samples)) + fft_output = np.fft.fft(time_samples) + frequencies = np.linspace(0.0, n_samples/(2.0*time_range), n_samples//2) + # #Cycles per second of fouier_samples[1] + # (1/time_range)*n_samples + # freq_step_size = 1./time_range + graph = VMobject() + graph.set_points_smoothly([ + axes.coords_to_point( + x, 200.0*complex_to_real_func(y)/n_samples, + ) + for x, y in zip(frequencies, fft_output[:n_samples//2]) + ]) + graph.highlight(color) + return graph def get_fourier_transform( func, t_min, t_max, - real_part = True, - use_almost_fourier = True, + complex_to_real_func = lambda z : z.real, + use_almost_fourier = USE_ALMOST_FOURIER_BY_DEFAULT, ): - # part = "real" if real_part else "imag" - trig = np.cos if real_part else np.sin scalar = 1./(t_max - t_min) if use_almost_fourier else 1.0 def fourier_transform(f): return scalar*scipy.integrate.quad( - lambda t : func(t)*trig(-TAU*f*t), + lambda t : complex_to_real_func( + # f(t) e^{-TAU*i*f*t} + func(t)*np.exp(complex(0, -TAU*f*t)) + ), t_min, t_max )[0] return fourier_transform diff --git a/active_projects/uncertainty.py b/active_projects/uncertainty.py index 83109534..31da08af 100644 --- a/active_projects/uncertainty.py +++ b/active_projects/uncertainty.py @@ -32,6 +32,9 @@ from topics.graph_scene import * from active_projects.fourier import * +FREQUENCY_COLOR = RED +USE_ALMOST_FOURIER_BY_DEFAULT = False + class GaussianDistributionWrapper(Line): """ This is meant to encode a 2d normal distribution as @@ -207,24 +210,173 @@ class MentionUncertaintyPrinciple(TeacherStudentsScene): self.get_student_changes(*3*["confused"]), run_time = 3, ) - self.wait() #Back and forth - self.play( - dot_cloud.gaussian_distribution_wrapper.change_parameters, - {"sigma_x" : 2}, - vector_cloud.gaussian_distribution_wrapper.change_parameters, - {"sigma_x" : 0.1}, - run_time = 3, + for x in range(2): + self.play( + dot_cloud.gaussian_distribution_wrapper.change_parameters, + {"sigma_x" : 2}, + vector_cloud.gaussian_distribution_wrapper.change_parameters, + {"sigma_x" : 0.1}, + run_time = 3, + ) + self.change_student_modes("thinking", "erm", "sassy") + self.play( + dot_cloud.gaussian_distribution_wrapper.change_parameters, + {"sigma_x" : 0.1}, + vector_cloud.gaussian_distribution_wrapper.change_parameters, + {"sigma_x" : 1}, + run_time = 3, + ) + self.wait() + +class FourierTradeoff(Scene): + def construct(self): + #Setup axes + time_mean = 4 + time_axes = Axes( + x_min = 0, + x_max = 2*time_mean, + x_axis_config = {"unit_size" : 1.5}, + y_min = -2, + y_max = 2, + y_axis_config = {"unit_size" : 0.5} ) - self.change_student_modes("thinking", "erm", "sassy") - self.play( - dot_cloud.gaussian_distribution_wrapper.change_parameters, - {"sigma_x" : 0.1}, - vector_cloud.gaussian_distribution_wrapper.change_parameters, - {"sigma_x" : 1}, - run_time = 3, + time_label = TextMobject("Time") + time_label.next_to( + time_axes.x_axis.get_right(), UP, + buff = MED_SMALL_BUFF, ) - self.wait(2) + time_axes.add(time_label) + time_axes.center().to_edge(UP) + time_axes.x_axis.add_numbers(*range(1, 2*time_mean)) + + frequency_axes = Axes( + x_min = 0, + x_max = 8, + x_axis_config = {"unit_size" : 1.5}, + y_min = 0, + y_max = 15, + y_axis_config = { + "unit_size" : 0.15, + "tick_frequency" : 5, + }, + color = TEAL, + ) + frequency_label = TextMobject("Frequency") + frequency_label.next_to( + frequency_axes.x_axis.get_right(), UP, + buff = MED_SMALL_BUFF, + ) + frequency_label.highlight(FREQUENCY_COLOR) + frequency_axes.add(frequency_label) + frequency_axes.move_to(time_axes, LEFT) + frequency_axes.to_edge(DOWN, buff = LARGE_BUFF) + frequency_axes.x_axis.add_numbers() + + # Graph information + + #x-coordinate of this point determines width of wave_packet graph + width_tracker = VectorizedPoint(0.5*RIGHT) + def get_width(): + return width_tracker.get_center()[0] + + def get_wave_packet_function(): + factor = 1./get_width() + return lambda t : np.sqrt(factor)*np.cos(4*TAU*t)*np.exp(-factor*(t-time_mean)**2) + + def get_wave_packet(): + graph = time_axes.get_graph( + get_wave_packet_function(), + num_graph_points = 200, + ) + graph.highlight(YELLOW) + return graph + + time_radius = 10 + def get_wave_packet_fourier_transform(): + return get_fourier_graph( + frequency_axes, get_wave_packet_function(), + t_min = time_mean - time_radius, + t_max = time_mean + time_radius, + n_samples = 2*time_radius*17, + complex_to_real_func = abs, + color = FREQUENCY_COLOR, + ) + + wave_packet = get_wave_packet() + wave_packet_update = UpdateFromFunc( + wave_packet, + lambda g : Transform(g, get_wave_packet()).update(1) + ) + fourier_graph = get_wave_packet_fourier_transform() + fourier_graph_update = UpdateFromFunc( + fourier_graph, + lambda g : Transform(g, get_wave_packet_fourier_transform()).update(1) + ) + + arrow = Arrow( + wave_packet, frequency_axes.coords_to_point(4, 10), + color = FREQUENCY_COLOR, + ) + fourier_words = TextMobject("Fourier Transform") + fourier_words.next_to(arrow, RIGHT, buff = MED_LARGE_BUFF) + sub_words = TextMobject("(To be explained shortly)") + sub_words.highlight(BLUE) + sub_words.scale(0.75) + sub_words.next_to(fourier_words, DOWN) + + #Draw items + self.add(time_axes, frequency_axes) + self.play(ShowCreation(wave_packet)) + self.play( + ReplacementTransform( + wave_packet.copy(), + fourier_graph, + ), + GrowArrow(arrow), + Write(fourier_words, run_time = 1) + ) + # self.play(FadeOut(arrow)) + self.wait() + for width in 6, 0.1, 1: + self.play( + width_tracker.move_to, width*RIGHT, + wave_packet_update, + fourier_graph_update, + run_time = 3 + ) + if sub_words not in self.mobjects: + self.play(FadeIn(sub_words)) + else: + self.wait() + self.wait() + + + + + + + + + + + + + + + + + + + + + + + + + + + From 5af31c1b8bedbb3bb62b140990efef52c78cd08d Mon Sep 17 00:00:00 2001 From: Grant Sanderson Date: Sat, 10 Feb 2018 18:37:34 -0800 Subject: [PATCH 15/25] Minor refactoring to the camera class, with an additional change to z-buffering default behavior --- camera/camera.py | 54 +++++++++++++++++++++++++++++++++++------------- 1 file changed, 40 insertions(+), 14 deletions(-) diff --git a/camera/camera.py b/camera/camera.py index 75a71089..6c3c5a99 100644 --- a/camera/camera.py +++ b/camera/camera.py @@ -10,6 +10,10 @@ from helpers import * from mobject import Mobject, PMobject, VMobject, \ ImageMobject, Group, BackgroundColoredVMobject +# Set a @profile decorator over any method whose +# performance you'd like to analyze +from profilehooks import profile + class Camera(object): CONFIG = { "background_image" : None, @@ -31,7 +35,11 @@ class Camera(object): "image_mode" : "RGBA", "n_rgb_coords" : 4, "background_alpha" : 0, #Out of color_max_val - "pixel_array_dtype" : 'uint8' + "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. + # round z coordinate to nearest hundredth when comparring + "z_buff_func" : lambda m : np.round(m.get_center()[2], 2), } def __init__(self, background = None, **kwargs): @@ -94,7 +102,12 @@ class Camera(object): return retval def set_pixel_array(self, pixel_array, convert_from_floats = False): - self.pixel_array = self.convert_pixel_array(pixel_array, convert_from_floats) + converted_array = self.convert_pixel_array(pixel_array, convert_from_floats) + if not hasattr(self, "pixel_array"): #TODO: And the shapes match? + self.pixel_array = converted_array + else: + #Set in place + self.pixel_array[:,:,:] = converted_array[:,:,:] def set_background(self, pixel_array, convert_from_floats = False): self.background = self.convert_pixel_array(pixel_array, convert_from_floats) @@ -141,8 +154,6 @@ class Camera(object): self, mobjects, include_submobjects = True, excluded_mobjects = None, - #Round z coordinate to nearest hundredth when comparring - z_buff_func = lambda m : np.round(m.get_center()[2], 2) ): if include_submobjects: mobjects = self.extract_mobject_family_members( @@ -154,15 +165,22 @@ class Camera(object): ) mobjects = list_difference_update(mobjects, all_excluded) - # Should perhaps think about what happens here when include_submobjects is False, - # (for now, the onus is then on the caller to ensure this is handled correctly by - # passing us an appropriately pre-flattened list of mobjects if need be) - return sorted(mobjects, lambda a, b: cmp(z_buff_func(a), z_buff_func(b))) + if self.use_z_coordinate_for_display_order: + # Should perhaps think about what happens here when include_submobjects is False, + # (for now, the onus is then on the caller to ensure this is handled correctly by + # passing us an appropriately pre-flattened list of mobjects if need be) + return sorted( + mobjects, + lambda a, b: cmp(self.z_buff_func(a), self.z_buff_func(b)) + ) + else: + return mobjects def capture_mobject(self, mobject, **kwargs): return self.capture_mobjects([mobject], **kwargs) def capture_mobjects(self, mobjects, **kwargs): + self.reset_aggdraw_canvas() mobjects = self.get_mobjects_to_display(mobjects, **kwargs) vmobjects = [] for mobject in mobjects: @@ -190,23 +208,32 @@ class Camera(object): #TODO, more? Call out if it's unknown? self.display_multiple_vectorized_mobjects(vmobjects) + ## Methods associated with svg rendering + + def get_aggdraw_canvas(self): + if not hasattr(self, "canvas"): + self.reset_aggdraw_canvas() + return self.canvas + + def reset_aggdraw_canvas(self): + image = Image.fromarray(self.pixel_array, mode = self.image_mode) + self.canvas = aggdraw.Draw(image) + def display_multiple_vectorized_mobjects(self, vmobjects): if len(vmobjects) == 0: return #More efficient to bundle together in one "canvas" - image = Image.fromarray(self.pixel_array, mode = self.image_mode) - canvas = aggdraw.Draw(image) + canvas = self.get_aggdraw_canvas() for vmobject in vmobjects: self.display_vectorized(vmobject, canvas) canvas.flush() - self.pixel_array[:,:] = image - - def display_vectorized(self, vmobject, canvas): + def display_vectorized(self, vmobject, canvas = None): if vmobject.is_subpath: #Subpath vectorized mobjects are taken care #of by their parent return + canvas = canvas or self.get_aggdraw_canvas() pen, fill = self.get_pen_and_fill(vmobject) pathstring = self.get_pathstring(vmobject) symbol = aggdraw.Symbol(pathstring) @@ -280,7 +307,6 @@ class Camera(object): self.pixel_array, array ) - def display_point_cloud(self, points, rgbas, thickness): if len(points) == 0: return From d35ba7b196fa5210e5e091d7b86ef9dd84740a3e Mon Sep 17 00:00:00 2001 From: Grant Sanderson Date: Sat, 10 Feb 2018 21:19:26 -0800 Subject: [PATCH 16/25] Sped up get_pathstring of camera --- camera/camera.py | 29 +++++++++++++++-------------- 1 file changed, 15 insertions(+), 14 deletions(-) diff --git a/camera/camera.py b/camera/camera.py index 6c3c5a99..111eae03 100644 --- a/camera/camera.py +++ b/camera/camera.py @@ -179,6 +179,7 @@ class Camera(object): def capture_mobject(self, mobject, **kwargs): return self.capture_mobjects([mobject], **kwargs) + @profile def capture_mobjects(self, mobjects, **kwargs): self.reset_aggdraw_canvas() mobjects = self.get_mobjects_to_display(mobjects, **kwargs) @@ -269,20 +270,18 @@ class Camera(object): # points = self.adjust_out_of_range_points(points) if len(points) == 0: continue - points = self.align_points_to_camera(points) - coords = self.points_to_pixel_coords(points) - start = "M%d %d"%tuple(coords[0]) - #(handle1, handle2, anchor) tripletes - triplets = zip(*[ - coords[i+1::3] - for i in range(3) - ]) - cubics = [ - "C" + " ".join(map(str, it.chain(*triplet))) - for triplet in triplets - ] - end = "Z" if vmobject.mark_paths_closed else "" - result += " ".join([start] + cubics + [end]) + aligned_points = self.align_points_to_camera(points) + coords = self.points_to_pixel_coords(aligned_points) + coord_strings = coords.flatten().astype("string") + #Start new path string with M + coord_strings[0] = "M" + coord_strings[0] + #The C at the start of every 6th number communicates + #that the following 6 define a cubic Bezier + coord_strings[2::6] = map(lambda s : "C" + str(s), coord_strings[2::6]) + #Possibly finish with "Z" + if vmobject.mark_paths_closed: + coord_strings[-1] = coord_strings[-1] + " Z" + result += " ".join(coord_strings) return result def display_background_colored_vmobject(self, cvmobject): @@ -307,6 +306,8 @@ class Camera(object): self.pixel_array, array ) + ## Methods for other rendering + def display_point_cloud(self, points, rgbas, thickness): if len(points) == 0: return From 983a3e0357d82818795baf44a6c1fb4e2571c65e Mon Sep 17 00:00:00 2001 From: Grant Sanderson Date: Sat, 10 Feb 2018 21:38:12 -0800 Subject: [PATCH 17/25] Direct computation of color hex to speed things up --- camera/camera.py | 34 +++++++++++++++++++++------------- helpers.py | 2 +- mobject/vectorized_mobject.py | 6 ++++++ 3 files changed, 28 insertions(+), 14 deletions(-) diff --git a/camera/camera.py b/camera/camera.py index 111eae03..6c7c369f 100644 --- a/camera/camera.py +++ b/camera/camera.py @@ -241,14 +241,22 @@ class Camera(object): canvas.symbol((0, 0), symbol, pen, fill) def get_pen_and_fill(self, vmobject): - pen = aggdraw.Pen( - self.color_to_hex_l(self.get_stroke_color(vmobject)), - max(vmobject.stroke_width, 0) - ) - fill = aggdraw.Brush( - self.color_to_hex_l(self.get_fill_color(vmobject)), - opacity = int(self.color_max_val*vmobject.get_fill_opacity()) - ) + stroke_width = max(vmobject.get_stroke_width(), 0) + if stroke_width == 0: + pen = None + else: + stroke_rgb = self.get_stroke_rgb(vmobject) + stroke_hex = rgb_to_hex(stroke_rgb) + pen = aggdraw.Pen(stroke_hex, stroke_width) + + fill_opacity = int(self.color_max_val*vmobject.get_fill_opacity()) + if fill_opacity == 0: + fill = None + else: + fill_rgb = self.get_fill_rgb(vmobject) + fill_hex = rgb_to_hex(fill_rgb) + fill = aggdraw.Brush(fill_hex, fill_opacity) + return (pen, fill) def color_to_hex_l(self, color): @@ -257,14 +265,14 @@ class Camera(object): except: return Color(BLACK).get_hex_l() - def get_stroke_color(self, vmobject): - return vmobject.get_stroke_color() + def get_stroke_rgb(self, vmobject): + return vmobject.get_stroke_rgb() - def get_fill_color(self, vmobject): - return vmobject.get_fill_color() + def get_fill_rgb(self, vmobject): + return vmobject.get_fill_rgb() def get_pathstring(self, vmobject): - result = "" + result = "" for mob in [vmobject]+vmobject.get_subpath_mobjects(): points = mob.points # points = self.adjust_out_of_range_points(points) diff --git a/helpers.py b/helpers.py index 8e1af238..d0e3c962 100644 --- a/helpers.py +++ b/helpers.py @@ -126,7 +126,7 @@ def rgba_to_color(rgba): return rgb_to_color(rgba[:3]) def rgb_to_hex(rgb): - return Color(rgb = rgb).get_hex_l() + return "#" + "".join('%02x'%int(255*x) for x in rgb) def invert_color(color): return rgb_to_color(1.0 - color_to_rgb(color)) diff --git a/mobject/vectorized_mobject.py b/mobject/vectorized_mobject.py index fbf055b0..7bb8bdf3 100644 --- a/mobject/vectorized_mobject.py +++ b/mobject/vectorized_mobject.py @@ -120,6 +120,9 @@ class VMobject(Mobject): ) return self + def get_fill_rgb(self): + return self.fill_rgb + def get_fill_color(self): try: self.fill_rgb = np.clip(self.fill_rgb, 0.0, 1.0) @@ -130,6 +133,9 @@ class VMobject(Mobject): def get_fill_opacity(self): return np.clip(self.fill_opacity, 0, 1) + def get_stroke_rgb(self): + return self.stroke_rgb + def get_stroke_color(self): try: self.stroke_rgb = np.clip(self.stroke_rgb, 0, 1) From c9ff83920cfb4d2604b92ba23d2c71b4f07dc5e0 Mon Sep 17 00:00:00 2001 From: Grant Sanderson Date: Sat, 10 Feb 2018 21:43:55 -0800 Subject: [PATCH 18/25] Updated ThreeDCamera methods for new color scheme --- topics/three_dimensions.py | 17 ++++++----------- 1 file changed, 6 insertions(+), 11 deletions(-) diff --git a/topics/three_dimensions.py b/topics/three_dimensions.py index a7314d2e..55de2925 100644 --- a/topics/three_dimensions.py +++ b/topics/three_dimensions.py @@ -40,22 +40,17 @@ class ThreeDCamera(CameraWithPerspective): self.rotation_mobject = VectorizedPoint() self.set_position(self.phi, self.theta, self.distance) - def get_color(self, method): - color = method() - vmobject = method.im_self + def modified_rgb(self, vmobject, rgb): if should_shade_in_3d(vmobject): - return Color(rgb = self.get_shaded_rgb( - color_to_rgb(color), - normal_vect = self.get_unit_normal_vect(vmobject) - )) + return self.get_shaded_rgb(rgb, self.get_unit_normal_vect(vmobject)) else: return color - def get_stroke_color(self, vmobject): - return self.get_color(vmobject.get_stroke_color) + def get_stroke_rgb(self, vmobject): + return self.modified_rgb(vmobject, vmobject.get_stroke_rgb()) - def get_fill_color(self, vmobject): - return self.get_color(vmobject.get_fill_color) + def get_fill_rgb(self, vmobject): + return self.modified_rgb(vmobject, vmobject.get_fill_rgb()) def get_shaded_rgb(self, rgb, normal_vect): brightness = np.dot(normal_vect, self.unit_sun_vect)**2 From 5e25ecd33ca8651a9a142e9e04b44d70a947cd30 Mon Sep 17 00:00:00 2001 From: Grant Sanderson Date: Sat, 10 Feb 2018 22:19:00 -0800 Subject: [PATCH 19/25] Tiny changes --- animation/transform.py | 2 +- camera/camera.py | 9 ++------- topics/number_line.py | 2 -- 3 files changed, 3 insertions(+), 10 deletions(-) diff --git a/animation/transform.py b/animation/transform.py index 30d7c7d2..4ee132a5 100644 --- a/animation/transform.py +++ b/animation/transform.py @@ -48,7 +48,7 @@ class Transform(Animation): self.path_arc, self.path_arc_axis, ) - + def get_all_mobjects(self): return self.mobject, self.starting_mobject, self.target_mobject diff --git a/camera/camera.py b/camera/camera.py index 6c7c369f..958787f9 100644 --- a/camera/camera.py +++ b/camera/camera.py @@ -10,10 +10,6 @@ from helpers import * from mobject import Mobject, PMobject, VMobject, \ ImageMobject, Group, BackgroundColoredVMobject -# Set a @profile decorator over any method whose -# performance you'd like to analyze -from profilehooks import profile - class Camera(object): CONFIG = { "background_image" : None, @@ -179,7 +175,6 @@ class Camera(object): def capture_mobject(self, mobject, **kwargs): return self.capture_mobjects([mobject], **kwargs) - @profile def capture_mobjects(self, mobjects, **kwargs): self.reset_aggdraw_canvas() mobjects = self.get_mobjects_to_display(mobjects, **kwargs) @@ -256,7 +251,7 @@ class Camera(object): fill_rgb = self.get_fill_rgb(vmobject) fill_hex = rgb_to_hex(fill_rgb) fill = aggdraw.Brush(fill_hex, fill_opacity) - + return (pen, fill) def color_to_hex_l(self, color): @@ -280,7 +275,7 @@ class Camera(object): continue aligned_points = self.align_points_to_camera(points) coords = self.points_to_pixel_coords(aligned_points) - coord_strings = coords.flatten().astype("string") + coord_strings = coords.flatten().astype(str) #Start new path string with M coord_strings[0] = "M" + coord_strings[0] #The C at the start of every 6th number communicates diff --git a/topics/number_line.py b/topics/number_line.py index 4dab9f07..30ad693a 100644 --- a/topics/number_line.py +++ b/topics/number_line.py @@ -136,8 +136,6 @@ class NumberLine(VMobject): self.tip = tip self.add(tip) - - class UnitInterval(NumberLine): CONFIG = { "x_min" : 0, From b9ef9f6fc0128a1a4b38c456b2a5d5a712c73a72 Mon Sep 17 00:00:00 2001 From: Grant Sanderson Date: Sat, 10 Feb 2018 22:34:39 -0800 Subject: [PATCH 20/25] Enabled adding end animation number with -n flag --- extract_scene.py | 9 ++++++++- scene/scene.py | 5 +++++ 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/extract_scene.py b/extract_scene.py index 825f0ea1..ea34fa06 100644 --- a/extract_scene.py +++ b/extract_scene.py @@ -89,6 +89,7 @@ def get_configuration(): "write_all" : args.write_all, "output_name" : args.output_name, "skip_to_animation_number" : args.skip_to_animation_number, + "end_after_animation_number" : None, } if args.low_quality: config["camera_config"] = LOW_QUALITY_CAMERA_CONFIG @@ -102,7 +103,12 @@ def get_configuration(): stan = config["skip_to_animation_number"] if stan is not None: - config["skip_to_animation_number"] = int(stan) + if "," in stan: + start, end = stan.split(",") + config["skip_to_animation_number"] = int(start) + config["end_after_animation_number"] = int(end) + else: + config["skip_to_animation_number"] = int(stan) config["skip_animations"] = any([ config["show_last_frame"] and not config["write_to_movie"], @@ -221,6 +227,7 @@ def main(): "output_directory", "save_pngs", "skip_to_animation_number", + "end_after_animation_number", ] ]) diff --git a/scene/scene.py b/scene/scene.py index 4c72897c..625145f4 100644 --- a/scene/scene.py +++ b/scene/scene.py @@ -40,6 +40,7 @@ class Scene(Container): "always_continually_update" : False, "random_seed" : 0, "skip_to_animation_number" : None, + "end_after_animation_number" : None, } def __init__(self, **kwargs): Container.__init__(self, **kwargs) # Perhaps allow passing in a non-empty *mobjects parameter? @@ -409,6 +410,10 @@ class Scene(Container): if self.skip_to_animation_number: if self.num_plays + 1 == self.skip_to_animation_number: self.skip_animations = False + if self.end_after_animation_number: + if self.num_plays >= self.end_after_animation_number: + self.skip_animations = True + return self #Don't even both with the rest... if self.skip_animations: kwargs["run_time"] = 0 From 9cfab5c2ed3ca5370d221f5fffe561f54d6fc386 Mon Sep 17 00:00:00 2001 From: Grant Sanderson Date: Sat, 10 Feb 2018 22:45:46 -0800 Subject: [PATCH 21/25] Better convention for -n flag --- extract_scene.py | 20 ++++++++++---------- scene/scene.py | 14 +++++++------- 2 files changed, 17 insertions(+), 17 deletions(-) diff --git a/extract_scene.py b/extract_scene.py index ea34fa06..605b4513 100644 --- a/extract_scene.py +++ b/extract_scene.py @@ -68,7 +68,7 @@ def get_configuration(): for short_arg, long_arg in optional_args: parser.add_argument(short_arg, long_arg, action = "store_true") parser.add_argument("-o", "--output_name") - parser.add_argument("-n", "--skip_to_animation_number") + parser.add_argument("-n", "--start_at_animation_number") args = parser.parse_args() except argparse.ArgumentError as err: print(str(err)) @@ -88,8 +88,8 @@ def get_configuration(): "ignore_waits" : args.preview, "write_all" : args.write_all, "output_name" : args.output_name, - "skip_to_animation_number" : args.skip_to_animation_number, - "end_after_animation_number" : None, + "start_at_animation_number" : args.start_at_animation_number, + "end_at_animation_number" : None, } if args.low_quality: config["camera_config"] = LOW_QUALITY_CAMERA_CONFIG @@ -101,18 +101,18 @@ def get_configuration(): config["camera_config"] = PRODUCTION_QUALITY_CAMERA_CONFIG config["frame_duration"] = PRODUCTION_QUALITY_FRAME_DURATION - stan = config["skip_to_animation_number"] + stan = config["start_at_animation_number"] if stan is not None: if "," in stan: start, end = stan.split(",") - config["skip_to_animation_number"] = int(start) - config["end_after_animation_number"] = int(end) + config["start_at_animation_number"] = int(start) + config["end_at_animation_number"] = int(end) else: - config["skip_to_animation_number"] = int(stan) + config["start_at_animation_number"] = int(stan) config["skip_animations"] = any([ config["show_last_frame"] and not config["write_to_movie"], - config["skip_to_animation_number"], + config["start_at_animation_number"], ]) return config @@ -226,8 +226,8 @@ def main(): "write_to_movie", "output_directory", "save_pngs", - "skip_to_animation_number", - "end_after_animation_number", + "start_at_animation_number", + "end_at_animation_number", ] ]) diff --git a/scene/scene.py b/scene/scene.py index 625145f4..1cb81bff 100644 --- a/scene/scene.py +++ b/scene/scene.py @@ -39,8 +39,8 @@ class Scene(Container): "name" : None, "always_continually_update" : False, "random_seed" : 0, - "skip_to_animation_number" : None, - "end_after_animation_number" : None, + "start_at_animation_number" : None, + "end_at_animation_number" : None, } def __init__(self, **kwargs): Container.__init__(self, **kwargs) # Perhaps allow passing in a non-empty *mobjects parameter? @@ -407,18 +407,17 @@ class Scene(Container): if len(args) == 0: warnings.warn("Called Scene.play with no animations") return - if self.skip_to_animation_number: - if self.num_plays + 1 == self.skip_to_animation_number: + if self.start_at_animation_number: + if self.num_plays == self.start_at_animation_number: self.skip_animations = False - if self.end_after_animation_number: - if self.num_plays >= self.end_after_animation_number: + if self.end_at_animation_number: + if self.num_plays >= self.end_at_animation_number: self.skip_animations = True return self #Don't even both with the rest... if self.skip_animations: kwargs["run_time"] = 0 animations = self.compile_play_args_to_animation_list(*args) - self.num_plays += 1 sync_animation_run_times_and_rate_funcs(*animations, **kwargs) moving_mobjects = self.get_moving_mobjects(*animations) @@ -434,6 +433,7 @@ class Scene(Container): self.mobjects_from_last_animation = moving_mobjects self.clean_up_animations(*animations) self.continual_update(0) + self.num_plays += 1 return self def clean_up_animations(self, *animations): From 6e296ae6df2adba18df961e4aa406846185f53cf Mon Sep 17 00:00:00 2001 From: Grant Sanderson Date: Sun, 11 Feb 2018 18:21:31 -0800 Subject: [PATCH 22/25] Refactor of background coloring vmobjects, should be faster this way. --- camera/camera.py | 111 ++++++++++++++++++++++++++-------- mobject/__init__.py | 2 +- mobject/vectorized_mobject.py | 53 ++++------------ 3 files changed, 98 insertions(+), 68 deletions(-) diff --git a/camera/camera.py b/camera/camera.py index 958787f9..c1b96aed 100644 --- a/camera/camera.py +++ b/camera/camera.py @@ -8,7 +8,9 @@ import aggdraw from helpers import * from mobject import Mobject, PMobject, VMobject, \ - ImageMobject, Group, BackgroundColoredVMobject + ImageMobject, Group + +from profilehooks import profile class Camera(object): CONFIG = { @@ -180,15 +182,13 @@ class Camera(object): mobjects = self.get_mobjects_to_display(mobjects, **kwargs) vmobjects = [] for mobject in mobjects: - if isinstance(mobject, VMobject) and not isinstance(mobject, BackgroundColoredVMobject): - vmobjects.append(mobject) + if isinstance(mobject, VMobject): + vmobjects.append(mobject) elif len(vmobjects) > 0: self.display_multiple_vectorized_mobjects(vmobjects) vmobjects = [] - if isinstance(mobject, BackgroundColoredVMobject): - self.display_background_colored_vmobject(mobject) - elif isinstance(mobject, PMobject): + if isinstance(mobject, PMobject): self.display_point_cloud( mobject.points, mobject.rgbas, self.adjusted_thickness(mobject.stroke_width) @@ -221,7 +221,15 @@ class Camera(object): #More efficient to bundle together in one "canvas" canvas = self.get_aggdraw_canvas() for vmobject in vmobjects: - self.display_vectorized(vmobject, canvas) + if vmobject.get_background_image_file(): + canvas.flush() + self.display_background_colored_vmobject(vmobject) + #TODO: Resetting canvas every time here is inefficient + self.reset_aggdraw_canvas() + canvas = self.get_aggdraw_canvas() + else: + self.display_vectorized(vmobject, canvas) + # last_vmobject_had_background = False canvas.flush() def display_vectorized(self, vmobject, canvas = None): @@ -287,27 +295,21 @@ class Camera(object): result += " ".join(coord_strings) return result + def get_background_colored_vmobject_displayer(self): + #Quite wordy to type out a bunch + long_name = "background_colored_vmobject_displayer" + if not hasattr(self, long_name): + setattr(self, long_name, BackgroundColoredVMobjectDisplayer(self)) + return getattr(self, long_name) + + @profile def display_background_colored_vmobject(self, cvmobject): - mob_array = np.zeros( - self.pixel_array.shape, - dtype = self.pixel_array_dtype - ) - image = Image.fromarray(mob_array, mode = self.image_mode) - canvas = aggdraw.Draw(image) - self.display_vectorized(cvmobject, canvas) - canvas.flush() - cv_background = cvmobject.background_array - if not np.all(self.pixel_array.shape == cv_background): - cvmobject.resize_background_array_to_match(self.pixel_array) - cv_background = cvmobject.background_array - array = np.array( - (np.array(mob_array).astype('float')/255.)*\ - np.array(cv_background), - dtype = self.pixel_array_dtype - ) + displayer = self.get_background_colored_vmobject_displayer() + cvmobject_pixel_array = displayer.display(cvmobject) self.pixel_array[:,:] = np.maximum( - self.pixel_array, array + self.pixel_array, cvmobject_pixel_array ) + return self ## Methods for other rendering @@ -505,6 +507,65 @@ class Camera(object): return centered_space_coords +class BackgroundColoredVMobjectDisplayer(object): + def __init__(self, camera): + self.camera = camera + self.file_name_to_pixel_array_map = {} + self.init_canvas() + + def init_canvas(self): + self.pixel_array = np.zeros( + self.camera.pixel_array.shape, + dtype = self.camera.pixel_array_dtype, + ) + self.reset_canvas() + + def reset_canvas(self): + image = Image.fromarray(self.pixel_array, mode = self.camera.image_mode) + self.canvas = aggdraw.Draw(image) + + def resize_background_array( + self, background_array, + new_width, new_height, + mode = "RGBA" + ): + image = Image.fromarray(background_array, mode = mode) + resized_image = image.resize((new_width, new_height)) + return np.array(resized_image) + + def resize_background_array_to_match(self, background_array, pixel_array): + height, width = pixel_array.shape[:2] + mode = "RGBA" if pixel_array.shape[2] == 4 else "RGB" + return self.resize_background_array(background_array, width, height, mode) + + def get_background_array(self, cvmobject): + file_name = cvmobject.get_background_image_file() + if file_name in self.file_name_to_pixel_array_map: + return self.file_name_to_pixel_array_map[file_name] + full_path = get_full_raster_image_path(file_name) + image = Image.open(full_path) + array = np.array(image) + + camera = self.camera + if not np.all(camera.pixel_array.shape == array.shape): + array = self.resize_background_array_to_match(array, camera.pixel_array) + + self.file_name_to_pixel_array_map[file_name] = array + return array + + def display(self, cvmobject): + background_array = self.get_background_array(cvmobject) + self.camera.display_vectorized(cvmobject, self.canvas) + self.canvas.flush() + array = np.array( + (background_array*self.pixel_array.astype('float')/255), + dtype = self.camera.pixel_array_dtype + ) + self.pixel_array[:,:] = 0 + self.reset_canvas() + return array + + class MovingCamera(Camera): """ Stays in line with the height, width and position diff --git a/mobject/__init__.py b/mobject/__init__.py index 4dbeb0f9..e87c8285 100644 --- a/mobject/__init__.py +++ b/mobject/__init__.py @@ -6,5 +6,5 @@ __all__ = [ from mobject import Mobject, Group from point_cloud_mobject import Point, Mobject1D, Mobject2D, PMobject -from vectorized_mobject import VMobject, VGroup, BackgroundColoredVMobject +from vectorized_mobject import VMobject, VGroup from image_mobject import ImageMobject \ No newline at end of file diff --git a/mobject/vectorized_mobject.py b/mobject/vectorized_mobject.py index 7bb8bdf3..e732cc5b 100644 --- a/mobject/vectorized_mobject.py +++ b/mobject/vectorized_mobject.py @@ -17,6 +17,7 @@ class VMobject(Mobject): "propagate_style_to_family" : False, "pre_function_handle_to_anchor_scale_factor" : 0.01, "make_smooth_after_applying_functions" : False, + "background_image_file" : None, } def get_group_class(self): @@ -151,6 +152,16 @@ class VMobject(Mobject): return self.get_stroke_color() return self.get_fill_color() + def color_using_background_image(self, background_image_file): + self.background_image_file = background_image_file + self.highlight(WHITE) + for submob in self.submobjects: + submob.color_using_background_image(background_image_file) + return self + + def get_background_image_file(self): + return self.background_image_file + ## Drawing def start_at(self, point): if len(self.points) == 0: @@ -470,46 +481,4 @@ class VectorizedPoint(VMobject): def set_location(self,new_loc): self.set_points(np.array([new_loc])) -class BackgroundColoredVMobject(VMobject): - CONFIG = { - # Can be set to None, using set_background_array to initialize instead - "background_image_file" : "color_background", - "stroke_color" : WHITE, - "fill_color" : WHITE, - } - def __init__(self, vmobject, **kwargs): - # Note: At the moment, this does nothing to mimic - # the full family of the vmobject passed in. - VMobject.__init__(self, **kwargs) - - #Match properties of vmobject - self.points = np.array(vmobject.points) - self.set_stroke(WHITE, vmobject.get_stroke_width()) - self.set_fill(WHITE, vmobject.get_fill_opacity()) - for submob in vmobject.submobjects: - self.add(BackgroundColoredVMobject(submob, **kwargs)) - - if self.background_image_file != None: - #Initialize background array - path = get_full_raster_image_path(self.background_image_file) - image = Image.open(path) - self.set_background_array(np.array(image)) - - def set_background_array(self, background_array): - self.background_array = background_array - - def resize_background_array(self, new_width, new_height, mode = "RGBA"): - image = Image.fromarray(self.background_array, mode = mode) - resized_image = image.resize((new_width, new_height)) - self.background_array = np.array(resized_image) - - def resize_background_array_to_match(self, pixel_array): - height, width = pixel_array.shape[:2] - mode = "RGBA" if pixel_array.shape[2] == 4 else "RGB" - self.resize_background_array(width, height, mode) - - - - - From ae10d26696c0dbbd2d67d9065e3fa628f1e852b9 Mon Sep 17 00:00:00 2001 From: Grant Sanderson Date: Sun, 11 Feb 2018 18:59:54 -0800 Subject: [PATCH 23/25] Added batch_by_property --- helpers.py | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/helpers.py b/helpers.py index d0e3c962..80ee9e33 100644 --- a/helpers.py +++ b/helpers.py @@ -226,6 +226,24 @@ def all_elements_are_instances(iterable, Class): def adjacent_pairs(objects): return zip(objects, list(objects[1:])+[objects[0]]) +def batch_by_property(items, property_func): + batches = [] + def add_batch(batch): + if len(batch) > 0: + batches.append(batch) + curr_batch = [] + curr_prop = None + for item in items: + prop = property_func(item) + if prop != curr_prop: + add_batch(curr_batch) + curr_prop = prop + curr_batch = [item] + else: + curr_batch.append(item) + add_batch(curr_batch) + return batches + def complex_to_R3(complex_num): return np.array((complex_num.real, complex_num.imag, 0)) From 2ff4a7ba079632b989d5a1b3366425fdc135cb92 Mon Sep 17 00:00:00 2001 From: Grant Sanderson Date: Sun, 11 Feb 2018 19:00:09 -0800 Subject: [PATCH 24/25] Refactored for meaningful speedup in background colored vmobjects --- camera/camera.py | 59 +++++++++++++++++++++++++++++------------------- 1 file changed, 36 insertions(+), 23 deletions(-) diff --git a/camera/camera.py b/camera/camera.py index c1b96aed..8b08be11 100644 --- a/camera/camera.py +++ b/camera/camera.py @@ -178,7 +178,7 @@ class Camera(object): return self.capture_mobjects([mobject], **kwargs) def capture_mobjects(self, mobjects, **kwargs): - self.reset_aggdraw_canvas() + # self.reset_aggdraw_canvas() mobjects = self.get_mobjects_to_display(mobjects, **kwargs) vmobjects = [] for mobject in mobjects: @@ -218,18 +218,21 @@ class Camera(object): def display_multiple_vectorized_mobjects(self, vmobjects): if len(vmobjects) == 0: return - #More efficient to bundle together in one "canvas" + batches = batch_by_property( + vmobjects, + lambda vm : vm.get_background_image_file() + ) + for batch in batches: + if batch[0].get_background_image_file(): + self.display_multiple_background_colored_vmobject(batch) + else: + self.display_multiple_non_background_colored_vmobjects(batch) + + def display_multiple_non_background_colored_vmobjects(self, vmobjects): + self.reset_aggdraw_canvas() canvas = self.get_aggdraw_canvas() for vmobject in vmobjects: - if vmobject.get_background_image_file(): - canvas.flush() - self.display_background_colored_vmobject(vmobject) - #TODO: Resetting canvas every time here is inefficient - self.reset_aggdraw_canvas() - canvas = self.get_aggdraw_canvas() - else: - self.display_vectorized(vmobject, canvas) - # last_vmobject_had_background = False + self.display_vectorized(vmobject, canvas) canvas.flush() def display_vectorized(self, vmobject, canvas = None): @@ -303,9 +306,9 @@ class Camera(object): return getattr(self, long_name) @profile - def display_background_colored_vmobject(self, cvmobject): + def display_multiple_background_colored_vmobject(self, cvmobjects): displayer = self.get_background_colored_vmobject_displayer() - cvmobject_pixel_array = displayer.display(cvmobject) + cvmobject_pixel_array = displayer.display(*cvmobjects) self.pixel_array[:,:] = np.maximum( self.pixel_array, cvmobject_pixel_array ) @@ -553,17 +556,27 @@ class BackgroundColoredVMobjectDisplayer(object): self.file_name_to_pixel_array_map[file_name] = array return array - def display(self, cvmobject): - background_array = self.get_background_array(cvmobject) - self.camera.display_vectorized(cvmobject, self.canvas) - self.canvas.flush() - array = np.array( - (background_array*self.pixel_array.astype('float')/255), - dtype = self.camera.pixel_array_dtype + def display(self, *cvmobjects): + batches = batch_by_property( + cvmobjects, lambda cv : cv.get_background_image_file() ) - self.pixel_array[:,:] = 0 - self.reset_canvas() - return array + curr_array = None + for batch in batches: + background_array = self.get_background_array(batch[0]) + for cvmobject in batch: + self.camera.display_vectorized(cvmobject, self.canvas) + self.canvas.flush() + new_array = np.array( + (background_array*self.pixel_array.astype('float')/255), + dtype = self.camera.pixel_array_dtype + ) + if curr_array is None: + curr_array = new_array + else: + curr_array = np.maximum(curr_array, new_array) + self.pixel_array[:,:] = 0 + self.reset_canvas() + return curr_array class MovingCamera(Camera): From 0d05c5834237ecdf0bd6bf0deb0226b17cb24047 Mon Sep 17 00:00:00 2001 From: Grant Sanderson Date: Sun, 11 Feb 2018 19:05:24 -0800 Subject: [PATCH 25/25] Cleanup from last changes --- camera/camera.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/camera/camera.py b/camera/camera.py index 8b08be11..7c6d5477 100644 --- a/camera/camera.py +++ b/camera/camera.py @@ -10,8 +10,6 @@ from helpers import * from mobject import Mobject, PMobject, VMobject, \ ImageMobject, Group -from profilehooks import profile - class Camera(object): CONFIG = { "background_image" : None, @@ -178,7 +176,6 @@ class Camera(object): return self.capture_mobjects([mobject], **kwargs) def capture_mobjects(self, mobjects, **kwargs): - # self.reset_aggdraw_canvas() mobjects = self.get_mobjects_to_display(mobjects, **kwargs) vmobjects = [] for mobject in mobjects: @@ -305,7 +302,6 @@ class Camera(object): setattr(self, long_name, BackgroundColoredVMobjectDisplayer(self)) return getattr(self, long_name) - @profile def display_multiple_background_colored_vmobject(self, cvmobjects): displayer = self.get_background_colored_vmobject_displayer() cvmobject_pixel_array = displayer.display(*cvmobjects)