Проблема с анимацией отслеживания значений manimPython

Программы на Python
Anonymous
 Проблема с анимацией отслеживания значений manim

Сообщение Anonymous »

Недавно я хотел создать сцену анимации преобразования Фурье, которая будет использоваться для определения нового класса FourierCircle. Кажется, это эффект видео, подобного
этому в 0:51
но результат анимации, похоже, не совпадает с результатом, который показан как на рисунке ниже
Изображение
при рендеринге кода ниже:

Код: Выделить всё

from manim import *

from ObjectBase.FourierCircles.mobjects import PeriodicValueTracker, ArrayMobject, NestedPath

from ObjectBase.FourierCircles.utils import *

"""
fourier circles is a Mobject which animates fourier series
(complex fourier series, to be specific) representation
to be specific
of a given image's edge (represented in complex number ordered set)
to be specific how it shows:
[youtube]r6sGWTCMz2k[/youtube] 0:51

init: loads the image, extracts edges, performs fft,
and sets up the mobject with arrows, circles, and path
start_animate_in_steady_speed: starts the time value incrementing at a steady speed

input:n: the number of fourier components to use, default number is 100
time: the PeriodicValueTracker object to track time value
source_picture: the path to the source picture
source_array: alternatively, the source picture can be provided as a numpy array

--------------------------------------------------------------------------------
!!!!!!!!!!!!!!!!source_picture and source_array are mutually exclusive!!!!!!!!!!!
--------------------------------------------------------------------------------

methods:
start_animate_in_steady_speed: starts the time value incrementing at a steady speed
speed: the speed at which the time value increments (radians per second)

set_time_value: sets the time value to a specific value
value: the value to set the time to (radians)

set_time_value: sets the time value to a specific value
fade: the rate at which the path fades out

--------------------------------------------------------------------------------
!!!!!!!!!!!!!!the photo must be SVG format and contain path data!!!!!!!!!!!!!!!
------------------------------------------------------------------------------

"""
class FourierCircles(Group):
def __init__(
self,
*,
n: int = 100,

min_value=0,
max_value=2 * np.pi
,
source_picture: str = None,
source_array:  np.ndarray = None,
fade = 0.5
):
##
if source_array == None and source_picture is None:
raise ValueError("Either source_picture or source_array must be provided")
elif source_array is not None and source_picture is not None:
raise ValueError("Only one of source_picture or source_array is needed be provided, or they will be exclusive")

super().__init__()

edge_array = extract_svg_edges_vector(
f"{source_picture}"
)

# setup settings
points,_ = normalise(edge_array,return_factor=True)
self.N = min(n, len(points))
self.time = PeriodicValueTracker(0,min_value=min_value,max_value=max_value)
self.fade = fade

#extract discrete fourier components of the points
amplitudes, frequencies, phases = fft(points, self.N)

#add arrows and circles to the VGroup
arrows = [Arrow(ORIGIN, RIGHT) for _ in
range(self.N)]
circles = [
Circle(
radius=abs(amplitudes.tolist()[i]),
color=TEAL,
stroke_width=.2, stroke_opacity=.5
)
for i in range(self.N)

]

self.add(*arrows, *circles)

#create values and points array for cycles
values = ArrayMobject()
cumulative = ArrayMobject()

# set the value to e^i(a + wt)
# and accumulate their sums
values.add_updater(
lambda array, dt:
array.set_data(
np.array(
[0] + [a * np.exp(1j * (p + self.time.get_value() * f))
for a, f, p in
zip(amplitudes, frequencies, phases)])
),
call_updater=True
)
cumulative.add_updater(
lambda array, dt:
array.become(
values.sum()),
call_updater=True
)
values.update(0)
cumulative.update(0)

# draw mobjects in scene
for i, (arrow, ring) in enumerate(zip(arrows, circles)):
# give each object an id
# then put the circle at the center
# and the arrow from the last to next point
arrow.idx = i
ring.idx = i
ring.add_updater(
lambda self_ring:
self_ring.move_to(
complex_to_R3(cumulative[self_ring.idx])
)
)
arrow.add_updater(
lambda self_arrow:
self_arrow.become(
Arrow(
complex_to_R3(cumulative[self_arrow.idx]),
complex_to_R3(cumulative[self_arrow.idx + 1]),
buff=0,
max_tip_length_to_length_ratio=.2,
stroke_width=2,
stroke_opacity=.8
)
)
)
# add the last point to the path
# and get the path to fade out

path = NestedPath()
path.set_points_as_corners([complex_to_R3(cumulative[-1])] * 2)
path.add_updater(
lambda self_path: self_path.updater(
complex_to_R3(cumulative[-1]), fade)
)
self.add(*arrows, *circles,path,values,cumulative)

#this method makes the time value increment at a steady speed
def start_rotate_in_steady_speed(
self, *,
speed: float = np.pi,
):
self.time.add_updater(
lambda mob, dt: mob.increment_value(dt * speed)
)

#set the orientation of the time value between 0 to 2pi
def set_rotate_value(self, value:  float):
self.time.set_value(value)

# Example usage:
# Assuming you have an SVG file named 'example.svg'
# containing path data.

#this method loads an image and converts it to points

#test render code
if __name__ == "__main__":
class test(Scene):
def construct(self):
fc = FourierCircles(
n=1000,
source_picture="/Users/niuyingkai/PycharmProjects/ManimTestStation/FourierCircles/test_image_violin.svg",

)
self.add(fc)
with tempconfig({"quality": "high_quality","preview": True}):
scene = test()
scene.render()

А это код специализированных VMobjectов

Код: Выделить всё

from __future__ import annotations
from manim import *
import numpy as np

# updating object to contain arrays
class ArrayMobject(Mobject):
def __init__(self, array : np.ndarray = None, data: np.ndarray = np.array([])):
super().__init__()
# save the data
self.__data = data
self.set_data(array)

# data getter
@property
def get_data(self) -> np.ndarray:
return self.__data

# data setter
def set_data(self, data : np.ndarray):
self.__data = data

def sum(self) -> ArrayMobject:
# accumulate data and return new mobject
return ArrayMobject(np.add.accumulate(self.get_data))

# easy indexing
def __getitem__(self, idx: int) -> float:
date_list  = self.get_data.tolist()
return date_list[idx]

def become(self, new_obj: ArrayMobject, **kwargs):
# it doesn't create new mobject
self.set_data(new_obj.get_data)

# standard
return self

class NestedPath(VMobject):
"""
A VMobject that can update by adding new points
and fading out the previous path.
"""
def updater(self, point : np.ndarray, fade: float):
# save previous path
# as submobject
previous_path = NestedPath()
self.add(previous_path)

# add new point to corner
# and fade while visible
previous_path.set_points_as_corners(self.points.copy())
previous_path.add_updater(lambda path: path.fade(
fade) if path.get_stroke_opacity() > 2e-2 else path.clear_updaters())

# add to path
# and truncate points
self.add_points_as_corners([point])
self.set_points_as_corners(self.points[-4:])

# standard
return self

"""
PeriodicValueTracker is a class which inherits ValueTracker, behaving similarly,
but wraps around a specified range [min_value, max_value].
input: min_value: the minimum value of the range
max_value: the maximum value of the range
method:
set_value: sets the value, wrapping around the range if necessary
"""
class PeriodicValueTracker(ValueTracker):
def __init__(self,value=0,*, min_value: float=0, max_value: float=1, **kwargs):
self.value = value
self.min_value = min_value
self.max_value = max_value
self.range = max_value - min_value
super().__init__(min_value, **kwargs)

def set_value(self, value: float):
# Wrap the value around the specified range
wrapped_value = ((value - self.min_value) % self.range) + self.min_value
super().set_value(wrapped_value)
Изображение


Подробнее здесь: https://stackoverflow.com/questions/797 ... -animation

Вернуться в «Python»