Watts-College / cpp-527-fall-2021

A course shell for CPP 527 Foundations of Data Science II
https://watts-college.github.io/cpp-527-fall-2021/
2 stars 6 forks source link

Error in creating animation #10

Open ctmccull opened 2 years ago

ctmccull commented 2 years ago

# create a directory for your images

dir.create("gifs")
setwd("gifs")

library( animation )
library(magick)

saveGIF({

 for( i in 5:100 )
{

x <- cumsum( rnorm(100) )
y <- cumsum( rnorm(100) )

max.x <- max(x)
min.x <- min(x)
max.y <- max(y)
min.y <- min(y)

par( ask=T )
par( mar=c(2,2,3,1) )
    plot( x[i], y[i], pch=19, cex=2, xlim=c(min.x,max.x), ylim=c(min.y,max.y)  )
} 
# the loop that creates your image files here

}, 

movie.name = "movie_name.gif",   # name of your gif
interval = 0.3,                  # controls the animation speed
ani.width = 800,                 # size of the gif in pixels
ani.height = 800 )               # size of the git in pixels

Error in magick::image_animate(img, loop = loop, fps = 100/as.integer(interval * : argument 'fps' must be a factor of 100

I keep receiving this error when trying to animate the random walk from the lecture. Is there a way to edit the fps argument? Am I just completely missing something?

lecy commented 2 years ago

You have reorganized some of the content and placed things inside the loop that were outside.

As a result, every time through they loop they are being recreated. For example, your data consists of a bunch of random steps that move the location of the dot by small increments. If you recreate that data vector each time through the loop there will be no continuity - the dot will bounce around the graph without following any path.

You also added the parameter par( ask=T ) to the loop. This means to ask the user for the next plot in a series of plots. It allows you to click through the graphs in real-time like it is a slow animation. They might break the animation feature, though, because it will be waiting for you to proceed to the next graph manually.

The par() commands are setting global plot parameters, so they only need to be set once.

Does that make sense?

Make sure to split out the data creation from the plot creation steps:

# create a directory for your images

dir.create("gifs")
setwd("gifs")

library( animation )

x <- cumsum( rnorm(100) )
y <- cumsum( rnorm(100) )

max.x <- max(x)
min.x <- min(x)
max.y <- max(y)
min.y <- min(y)

par( ask=T )
par( mar=c(2,2,3,1) )

saveGIF({

  for( i in 5:100 )
  {
     plot( x[i], y[i], pch=19, cex=2, xlim=c(min.x,max.x), ylim=c(min.y,max.y)  )
  }

}, 

movie.name = "movie_name.gif",   # name of your gif
interval = 0.3,                  # controls the animation speed
ani.width = 800,                 # size of the gif in pixels
ani.height = 800 )               # size of the git in pixels

It might be that the arguments in the package changed since the example was created. Please report back on if this works.

ctmccull commented 2 years ago

That definitely makes sense! I took out the data creation and separated it from the plot creation, so that's all good now. However, the same error is still coming up regarding the 'fps' argument.

ctmccull commented 2 years ago

I think I got it! I finally googled what the 'fps' argument even was and it stands for "frames per second", which you probably knew. Anyway, that got me thinking that I needed to change "interval =" to a factor of 100. I tried interval = 0.10 and it worked!

lecy commented 2 years ago

So, here the function: https://github.com/yihui/animation/blob/master/R/im.convert.R

magick.convert = function(files, output, interval = 1, loop = 0, dispose = NULL){
  if (!length(dispose)) dispose = "background"
  dispose = tolower(dispose)
  img = magick::image_read(files, strip = TRUE)
  anim = magick::image_animate(img, loop = loop, fps = 100 / as.integer(interval * 100), dispose = dispose)
  magick::image_write(anim, path = output)
}

And the magick::image_animate() documentation states that it requires factors of 100.

If you look up the function there: https://github.com/ropensci/magick/blob/master/R/animation.R

You find:

    if(100 %% fps)
      stop("argument 'fps' must be a factor of 100")

Which is where the error is coming from. The modulus operator %% returns the remainder from division:

100 %% 10
[1] 0
100 %% 100
[1] 0
> 100 %% 30
[1] 10

I just now learned that you can use 0 or 1 as values in if() statements. 0=FALSE, 1=TRUE.

if( 0 ){ print("x") }

if( 1 ){ print("x") }
[1] "x"

So there you go. Interval would need to produce zero here:

interval <- 0.25
fps <- 100 / ( interval * 100 )
100 %% fps
[1] 0

How's that for a wormhole?

lecy commented 2 years ago

When you see the term "dependency" in documentation it means this - when the performance of your code depends upon code in another package, and thus changes to that package can impact your code!

ctmccull commented 2 years ago

Wow! That's both fascinating and anxiety-producing. I'm glad to know about dependency now, so I can keep my eye out for scenarios like this if a package were to ever change. Thank you for your help!

lecy commented 2 years ago

Yep, you caught the important part and were able to problem-solve:

argument 'fps' must be a factor of 100

But you can see how the animate package is a wrapper for image magick, which is called through the magick package.

The error occurs in the magick package, then is sent back through the function you called in the animate package.

The interesting thing to note is the code it returns is a combination of code from both functions:

Error in magick::image_animate(img, loop = loop, fps = 100/as.integer(interval * : argument 

But it tells you where the error occurs: magick::image_animate()

This is called error tracing and is really important for debugging because you know which chunk of code is actually triggering the error then. Sometimes you have no idea and need to trace it yourself with special debugging functions.

Kind of a miracle that any of it works as well as it does!