Introdução
Uma das primeiras etapas para resolução de um problema de machine learning é a obtenção dos dados para treinar o modelo. Mas nem sempre os dados estão disponíveis de forma simples. Além disso, como é envolvido grande número de dados, muitas vezes é necessária a obtenção automatizada desses dados. Neste artigo será comentada formas de fazer download de grande quantidade de imagens do SDSS por um script.
Como o SDSS disponibiliza suas imagens?
A forma mais comum de acessar o catálogo de imagens do SDSS é pela página do SkyServer, que é uma interface gráfica de usuário (GUI), ou seja, um método onde há interação do usuário com o sistema de forma visual. Mas além da interface de usuário, o SDSS disponibiliza uma interface de programação de aplicação (API), que é uma forma do servidor se comunicar diretamente com outro programa (cliente).
A API do SDSS está documentada nesta página. Ela disponibiliza mais dados além de images, mas aqui veremos como usá-la para obter imagens.
Como funciona uma API?
Simplificadamente, o funcionamento de uma API é análogo ao de uma função. A diferença é que você não está invocando uma função do seu programa, mas de um outro programa, escrito em outra linguagem e executado em outro computador. Sendo assim, você precisa chamar essa função pela internet.
Mas como é possível executar uma função escrita em outra linguagem e executando em outro computador? Isso é possível porque os programas que são projetados para comunicar com outros programas usam um padrão de comunicação. No caso do SDSS, é implementada uma arquitetura de software chamada REST, que é uma das mais comuns usadas para esta finalidade.
Os programas que se comunicam por uma API REST usam o protocolo HTTP para trocar informações. Assim, para que um programa executar uma determinada função no servidor, basta que acesse uma URL. Cada URL do servidor representa uma função, que também pode ser chamada de endpoint.
Mecanismo de uma API
No exemplo ilustrado pelo diagama acima, vemos um programa escrito em Ruby se comunicando com um servidor escrito em Java. Este diagrama mostra as principais características de uma comunicação REST. Vemos que ela é constituída de dois principais eventos: a requisição (Request) e a resposta (Response).
Na requisição, observamos que a aplicação acessa uma URI do servidor. A primeira parte da URI http://coolhomes.api.com
é chamada de caminho base (base path) e a segunda parte /home?limit=5
é a rota. Na rota, o que vemos antes da interrogação é seu nome e depois são seus parâmetros. O endpoint é o ponto final de um canal de comunicação, ou seja, é a URL acessada para executar alguma função no servidor. No diagrama acima, o endpoint é http://coolhomes.api.com/home
.
Se considerarmos que, ao acessar uma URL, o servidor executa uma função específica, então um endpoint é o endereço de uma função na rede. Desta maneira, acessar a URL http://coolhomes.api.com/home?limit=5
seria como invocar uma função home com o parâmetro limit igual a 5: home(limit=5)
.
Uma API pode retornar diversos tipos de repostas (que é o retorno da função executada internamente no servidor). Na arquitetura REST, os objetos retornados são comumente serializados como JSON, que é a representação do objeto na forma de uma string. Desta forma, o cliente consegue receber e interpretar tipos de dados arbitrários independentemente da linguagem de programação utilizada.
Além disso, notamos que o fluxo de comunicação entre dois sistemas é: o cliente acessa uma URL, isso faz com que o servidor execute a função relacionada àquela rota e retorne a resposta para o cliente.
A API do SDSS
Depois desta breve introdução sobre API’s, queremos agora saber como usamos a API do SDSS para obter as imagens. Lendo a documentação do SDSS, vemos que devemos usar o seguinte endereço para o endpoint:
- endpoint:
http://skyserver.sdss.org/dr15/SkyServerWS/ImgCutout/getJpeg
Além disso, vemos que esta URL aceita os seguintes parâmetros:
Parâmetro | Descrição | Valor padrão |
---|---|---|
ra | Ascensão Reta (em graus) | - |
dec | Declinação (em graus) | - |
scale | Escala da imagem (em arsec por pixel) | 0.4 |
height | Altura da imagem (em pixels) | 512 |
width | Largura da imagem (em pixels) | 512 |
opt | Uma string com as overlays que serão carregadas sobre a imagem | - |
Os parâmtros ra e dec são obrigatórios, já os demais são opcionais, visto que têm um valor padrão.
Neste exemplo, usaremos os 5 primeiros parâmetros, o sexto parâmetro opt
são as overlays que podem ser opcionalmente carregadas nas imagens, são as mesmas overlays disponíveis quando se obtém as imagens pelo site do SkyServer.
Exemplo de requisições
Para ilustrar nosso problema, vamos fazer algumas requisições para API do SDSS para ver qual sua resposta.
Imagine que temos 3 objetos identificados pela sua ascensão reta e declinação, como na tabela abaixo, e queiramos obter suas respectivas imagens.
Objeto | RA | DEC |
---|---|---|
1 | 19.023342 | -0.014911 |
2 | 24.398026 | -0.135213 |
3 | 19.196877 | -0.103266 |
Para todos os objetos usaremos width=180
, height=180
e scale=0.3
. A tabela abaixo mostra a URL de requisição com os parâmetros que definimos e sua respectiva resposta.
Observando a tabela acima, percebemos que a requisição é composta de dois parâmetros principais: RA e DEC, e a resposta é uma imagem jpeg.
Metodologia
Agora que já entendemos o mecanismo da API do SDSS, da mesma forma que obtemos imagens de 3 objetos é bem simples pensar em como implementar um programa que faz download de todos os objetos de um DataFrame do python, por exemplo.
Podemos baixar as imagens que desejamos em dois passos:
- Iterar sobre uma coleção de dados (DataFrame, Lista, etc.) e montar as URL’s para fazer a requisição.
- Salvar a resposta da API (imagem) no disco.
Implementação
Primeiro, vamos implementar uma função que recebe os parâmetros aceitos pela rota da API do SDSS e montar nossa URL.
def create_request_url(ra, dec, width=200, height=200, scale=.3):
ENDPOINT = 'http://skyserver.sdss.org/dr15/SkyServerWS/ImgCutout/getjpeg'
return f'{ENDPOINT}?ra={ra}&dec={dec}&width={width}&height={height}&scale={scale}'
A função create_request_url
possui dois parâmetros obrigatórios: ra e dec. Os demais são opicionais.
Agora vamos implementar uma função que receba uma URL, faça a requisição e salve a resposta (imagem) no disco. Para isso, vamos utilizar a biblioteca requests, que é responsável pelo envio e recebimento de pacotes através da rede.
import requests
def download_image(url, filename):
output_path = '/content/images/'
resp = requests.get(url)
if (resp.status_code == 200):
with open(f'{output_path}/{filename}', 'wb') as f:
f.write(resp.content)
print(f'Image {filename} downloaded')
A função download_image
baixa uma imagem, que é a resposta da URL acessada. Mas, na maioria das vezes, não queremos baixar apenas uma imagem, mas várias. Então vamos implementar uma terceira função que chama a função acima para cada linha de um DataFrame
do pandas.
import pandas as pd
def batch_download(df):
for index, row in df.iterrows():
url = create_request_url(row['ra'], row['dec'])
filename = f'{int(row["id"])}.jpg'
download_image(url, filename)
A função batch_download
itera sobre um DataFrame e chama a função download_image
para cada objeto da coleção recebida como parâmetro.
Para ilustrar este exemplo, vamos usar um dataset de amostra com 50 objetos. Estes dados estão num arquivo CSV que contém informações de RA e DEC de cada objeto. O trecho de código abaixo carrega o dataset e chama a função batch_download
, nossa função principal.
data = pd.read_csv('https://iagml.github.io/assets/example_data.csv')
batch_download(data)
A figura abaixo mostra as imagens baixadas pelo programa.
Jupyter Notebooks
Um exemplo deste código está disponível no Google Colabs e pode ser acessado pelo link abaixo
Abaixo tem outro exemplo um pouco mais complexo, mas com performance muito superior pois utiliza computação concorrente para baixar as imagens. Isto é, ao invés de baixar as imagens sequencialmente, esperando uma imagem baixar para depois baixar a próxima, o programa cria vários processos paralelos que baixam várias imagens simultaneamente.