sexta-feira, 28 de abril de 2017

Configurando programas usando arquivos INI

Uma boa prática ao desenvolver programas é tornar algumas partes configuráveis pelo usuário. Isto pode ser feito usando arquivos de configuração em texto puro. Desta maneira, o usuário pode abrir o arquivo e entender seu conteúdo facilmente, além de poder fazer alterações controladas na configuração do programa. Em sistemas unix-like estes arquivos costumam ficar na pasta ~/.config do usuário. Em sistemas Windows arquivos com a extensão ini são usados e costumam ficar na pasta da aplicação.

Uma vantagem de se usar arquivos ini é que eles são de fácil leitura e podem ser lidos pela biblioteca configparser da biblioteca padrão do Python. Arquivos ini seguem a seguinte formatação:

[DEFAULT]
chave=valor
chave2=valor2
chave3 = valor3
; comentário em uma linha separada!
# outro comentário!

[Seção 2]
chave=valor_atualizado
; o restante dos valores é igual ao encontrado em DEFAULT

[Seção 3]
chave3=valor_novo3
chave2=valor_novo2
; o mesmo ocorre para esta seção.

A configuração possui várias Seções, cada uma representando uma configuração possível do programa, que contém diversas propriedades representadas como um par (chave, valor) em que ambas a chave e o valor podem conter espaços (assim como o nome das seções). Para evitar a repetição de valores padrão para cada propriedade, toda chave não definida em uma seção assume, automaticamente, o valor declarado na seção DEFAULT.

>>> import codecs
>>> from configparser import ConfigParser
>>> config = ConfigParser()
>>> with codecs.open('exemplo.ini', 'r', 'utf-8') as f:
...     config.readfp(f)
...
>>> config.sections()
['Seção 2', 'Seção 3']
>>> for k in config.sections():
...     print('Seção:', k)
...     for prop in config[k].keys():
...         print('\t',prop,':',config[k][prop])
...
Seção: Seção 2
         chave : valor_atualizado
         chave2 : valor2
         chave3 : valor3
Seção: Seção 3
         chave3 : valor_novo3
         chave2 : valor_novo2
         chave : valor

Veja que, como esperado, todas as seções possuem as mesmas 3 propriedades, mesmo que Seção 2 e Seção 3 tenham declarado somente parte delas.

Em um contexto de programação científica podemos usar arquivos ini para representar configurações de um experimento e grupos parâmetros a serem variados de maneira conjunta. Isto facilita também a reprodução de experimentos, já que os parâmetros usados estão descritos de forma clara fora do código fonte do programa. Não é necessário caçar nenhuma informação no meio do programa!

Veja abaixo um exemplo que faz detecção de objetos de uma certa cor usando scikit-image. A principal configuração do programa é qual a Matiz da cor a ser identificada. Ao invés de fixar um valor no código fonte ou criar arquivos de configuração em modo texto vamos criar um arquivo ini que permite ao usuário configurar diversos perfis para identificação de cores. Veja abaixo o arquivo que usaremos

[DEFAULT]
cor_h = 0

[vermelho]
cor_h = 0

[azul]
cor_h = 202

Rodaremos o programa de segmentação por cor na imagem abaixo. Cada configuração realiza a segmentação de uma das canetinhas da imagem.

Abaixo está a listagem de código que faz a segmentação por cor em duas imagens.

import sys
import skimage
import skimage.color
import skimage.io
import numpy as np
import matplotlib.pyplot as plt
from configparser import ConfigParser

def segmenta_cor(imagem, cor):
    imagem_hsv = skimage.color.rgb2hsv(imagem)
    cor_h = cor/360
    color_range = 0.05

    canal_h = imagem_hsv[:,:,0]
    canal_h_cor = np.logical_and(canal_h > (cor_h - 0.05), canal_h <
(cor_h + 0.05))
    if cor_h < 0.05:
        canal_h_cor = np.logical_or(canal_h_cor, canal_h > 1 - 0.05 +
cor_h)
    return 255*canal_h_cor

config = ConfigParser()
config.read('cores.ini')

if __name__ == '__main__':
    secname = sys.argv[1]
    if secname in config.sections():
        cor_h = int(config[secname]['cor_h'])
        img = skimage.io.imread('foto.jpg')
        msk = segmenta_cor(img, cor_h)
        skimage.io.imsave('mask_%s.png'%secname, msk)

Chamamos o programa com o nome da seção que contém a configuração desejada.

python segmenta_cores.py vermelho
python segmenta_cores.py azul

E cada execução realmente produz uma segmentação diferente da imagem:

Se quisermos segmentar a cor verde podemos simplesmente adicionar uma seção no arquivo cores.ini. Não há necessidade de mudar o código fonte!

Qualquer dúvida ou sugestão deixe um comentário abaixo ;) Se gostou, compartilhe. Até a próxima!