[ad_1]
[Esteartigofoipublicadopelaprimeiravezem[Thisarticlewasfirstpublishedon Posts | Joshua Cook, e gentilmente contribuiu para os R-blogueiros]. (Você pode relatar um problema sobre o conteúdo desta página aqui)
Deseja compartilhar seu conteúdo com R-blogueiros? clique aqui se você tiver um blog ou aqui se não tiver.
Riddler Express da FiveThirtyEight
Dakota Jones está de volta em ação. Após o fracasso na tentativa de viver com um site de cupom de desconto, ele mudou seu rumo. Em sua busca para localizar o Templo de
Diametra, ela encontrou outro cristal altamente simétrico. Contudo,
agentes nefastos novamente descobriram seus planos, e agora Dakota
e o cristal não está em lugar algum.
E assim, você deve recriar novamente o cristal usando os dados de
Scanner a laser de Dakota. Como lembrete, o scanner pega um objeto 3D
e registra fatias transversais 2D ao longo da terceira dimensão.
Aqui está o arquivo de animação em loop que o scanner produziu para o
cristal desta vez:
Que tipo de forma tridimensional é o cristal? Sem pressão –
Dakota Jones, ou seja, o mundo inteiro, conta com você para localizar o
templo perdido!
Plano
Meu plano é ler as fatias do GIF, extrair as informações como
uma máscara binária, identifique os cantos das maks e plote esses cantos
no espaço 3D.
Configuração
knitr::opts_chunk$set(echo = TRUE, comment = "#>")
library(plotly)
library(htmlwidgets)
library(tidyverse)
theme_set(theme_minimal())
Preparando os dados da imagem
Eu li os dados da imagem GIF usando o read.gif()
função do
Pacote ‘caTools’.
gif_path
#> Warning in caTools::read.gif(gif_path): write.gif: file 'assets/crystal_538.gif'
#> contains multiple color-maps. Use 'frame' > 0.
A imagem está contida como uma matriz 3D no image
slot. A imagem
matriz é numérica em que cada número corresponde a uma cor na fatia.
names(gif)
#> [1] "image" "col" "transparent" "comment"
dim(gif$image)
#> [1] 558 586 101
gif$image[1:5, 1:5, 1:3]
#> , , 1
#>
#> [,1] [,2] [,3] [,4] [,5]
#> [1,] 0 0 0 0 0
#> [2,] 0 0 0 0 0
#> [3,] 0 0 0 0 0
#> [4,] 0 0 0 0 0
#> [5,] 0 0 0 0 0
#>
#> , , 2
#>
#> [,1] [,2] [,3] [,4] [,5]
#> [1,] 2 2 2 2 2
#> [2,] 2 2 2 2 2
#> [3,] 2 2 2 2 2
#> [4,] 2 2 2 2 2
#> [5,] 2 2 2 2 2
#>
#> , , 3
#>
#> [,1] [,2] [,3] [,4] [,5]
#> [1,] 1 1 1 1 1
#> [2,] 1 1 1 1 1
#> [3,] 1 1 1 1 1
#> [4,] 1 1 1 1 1
#> [5,] 1 1 1 1 1
O built-in image()
A função pode plotar uma matriz de valores numéricos.
image(gif$image[, , 21])
Eu esperava encontrar apenas duas cores, o primeiro e o segundo plano, mas
a borda do primeiro plano e do fundo às vezes tinha um leve
cor diferente.
table(gif$image[, , 20])
#>
#> 0 1 2
#> 38164 288012 812
table(gif$image[, , 21])
#>
#> 0 1 2 3
#> 40000 802 285986 200
table(gif$image[, , 22])
#>
#> 0 1 2
#> 41184 285012 792
Felizmente, o primeiro plano sempre teve a cor 0
. Portanto, eu fiz um
cópia do gif$image
onde todos os 0 valores foram TRUE
e o resto
estavam FALSE
.
img
Finalmente, o primeiro e o último quadro estão vazios, então eu os removi.
img
“Arrumar” os dados da imagem
Eu queria colocar os dados da imagem em um formato organizado, para que cada linha fosse um
ponto de dados com colunas x
, y
e z
(a fatia do GIF) para o
coordenadas do ponto de dados e uma coluna value
para o valor (seja
TRUE
ou FALSE
) do ponto. Isso foi feito pelo get_tidy_xy()
funciton que leva um índice para o qual fatiar img
.
Então, eu precisava pegar apenas os cantos da máscara. Felizmente, isso foi
fácil porque todos os shages individuais eram retângulos. Portanto, eu
poderia apenas filtrar os pontos no mínimo e no máximo x
valores
e então o mínimo e o máximo y
valores. Isso foi feito por
get_mask_corners()
que ocupa um quadro de dados organizado como o produzido por
get_tidy_xy()
.
Ambas as funções foram usadas em conjunto para produzir o tidy_img
mexer.
get_tidy_xy %
as_tibble() %>%
mutate(y = 1:n()) %>%
pivot_longer(-y, names_to = "x", values_to = "value") %>%
mutate(x = as.numeric(x))
}
get_mask_corners %
filter(value) %>%
filter(x == min(x) | x == max(x)) %>%
filter(y == min(y) | y == max(y))
}
tidy_img %
mutate(xy = map(z, get_tidy_xy),
xy = map(xy, get_mask_corners)) %>%
unnest(xy)
tidy_img
#> # A tibble: 396 x 4
#> z y x value
#>
#> 1 1 278 46 TRUE
#> 2 1 278 541 TRUE
#> 3 1 281 46 TRUE
#> 4 1 281 541 TRUE
#> 5 2 275 49 TRUE
#> 6 2 275 538 TRUE
#> 7 2 284 49 TRUE
#> 8 2 284 538 TRUE
#> 9 3 273 51 TRUE
#> 10 3 273 536 TRUE
#> # … with 386 more rows
Usei a biblioteca ‘ggforce’ para plotar as primeiras camadas, individualmente,
antes de ir para 3D.
tidy_img %>%
filter(z %in% 1:9) %>%
ggplot(aes(x = x, y = y, group = z)) +
facet_wrap(~ z) +
ggforce::geom_shape() +
scale_x_continuous(breaks = c(100, 300, 500)) +
scale_y_continuous(breaks = c(100, 300, 500)) +
theme(
panel.grid = element_line()
) +
labs(x = "x", y = "y",
title = "Surfaces through the image",
subtitle = "Each plot is a slice of the gif")
Em vez de retângulos bonitos, isso produzia uma forma de gravata borboleta. Isso foi
por causa da ordem dos pontos – eles não estavam na ordem correta
para desenhar um retângulo, mas o ponto superior esquerdo foi seguido pelo
ponto inferior direito, causando a diagonal. Portanto, o
arrange_tidy_xy()
função apenas reorganiza cada coordenada para
desenhe um retângulo.
arrange_tidy_xy %
group_by(z) %>%
nest() %>%
mutate(data = map(data, arrange_tidy_xy)) %>%
unnest(data)
tidy_img
#> # A tibble: 396 x 4
#> # Groups: z [99]
#> z y x value
#>
#> 1 1 278 46 TRUE
#> 2 1 278 541 TRUE
#> 3 1 281 541 TRUE
#> 4 1 281 46 TRUE
#> 5 2 275 49 TRUE
#> 6 2 275 538 TRUE
#> 7 2 284 538 TRUE
#> 8 2 284 49 TRUE
#> 9 3 273 51 TRUE
#> 10 3 273 536 TRUE
#> # … with 386 more rows
tidy_img %>%
filter(z %in% round(seq(1, 99, length.out = 25))) %>%
ggplot(aes(x = x, y = y, group = z)) +
facet_wrap(~ z) +
ggforce::geom_shape() +
scale_x_continuous(breaks = c(100, 300, 500)) +
scale_y_continuous(breaks = c(100, 300, 500)) +
theme(
panel.grid = element_line()
) +
labs(x = "x", y = "y",
title = "Surfaces through the image",
subtitle = "Each plot is a slice of the gif")
Plotagem 3D
Por fim, pude plotar os pontos em 3D para criar a forma de “Dakota
Jones ‘crystal “, como o Riddle solicita.
plot_ly(data = tidy_img, x = ~x, y = ~y, z = ~z, size = 1,
mode = "markers", opacity = 1.0, type = "scatter3d")
plot_ly(data = tidy_img, x = ~x, y = ~y, z = ~z, size = 1,
mode = "line", opacity = 0.5, type = "scatter3d")
plot_ly(data = tidy_img, x = ~x, y = ~y, z = ~z, type = "mesh3d")
Relacionado
[ad_2]