Pour mettre au point un petit annotateur graphique d'image (placement de symboles graphique, différents mode de sélection de patch), j'utilise Apache Batik.
Cela me permet de contenir les annotations en
SVG, ce qui est bien pratique, autant pour la gestion en interne des annotations (e.g. les layers sont des groupes de symboles graphiques dont la propriétés
SVG visibility change), un arbre
XML c'est la panacée.
J'avais déjà travaillé un peu là dessus, mais c'était codé à la va-vite et uniquement pour afficher les annotations LabelMe:
C'est ainsi que je me suis frotté aux obscurs rouages du pipeline graphique de Batik... et un petit problème que je viens de résoudre est celui-ci:
Si un objet JSVGCanvas est dans le viewport d'un JScrollPane, comment positionner une texture de rendu sur le background du JSVGCanvas (i.e. positionner un background particulier sur l'objet qui est dans le viewport du JScrollPane ! ).
Il faut faire attention à ce que fait la méthode de rendu
paint(...) de JSVGCanvas. En fait, cette méthode
paint(...) est héritée de JComponent:
Un objet JSVGCanvas est un JComponent qui contient une liste de CanvasGraphicsNode. Positionnner un background sur JSVGCanvas, revient à positionner un background sur un JComponent, et la méthode paintComponent(Graphics g) est redéfinie dans la classe JGVTComponent, c'est donc dans cette classe que l'on doit faire quelque chose.
Il n'y a rien de prévu pour cela dans Batik, la solution que j'ai mise en place est de modifier les sources, ça se fait facilement en modifiant la classe org.apache.batik.swing.gvt.JGVTComponent:
private TexturePaint texpaint;
public void setTexturePaint(TexturePaint texpaint){
this.texpaint = texpaint;
}
/**
* Paints this component.
*/
public void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D)g;
Rectangle visRect = getRenderRect();
g2d.setComposite(AlphaComposite.SrcOver);
//les deux lignes suivantes sont commentées.
//g2d.setPaint(getBackground());
//g2d.fillRect(visRect.x, visRect.y, visRect.width, visRect.height);
//et on ajoute les deux lignes suivantes:
g2d.setPaint(texpaint);
g2d.fillRect(0, 0, getWidth(), getHeight());
if (image != null) {
if (paintingTransform != null) {
g2d.transform(paintingTransform);
}
g2d.drawRenderedImage(image, null);
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_OFF);
Iterator it = overlays.iterator();
while (it.hasNext()) {
((Overlay)it.next()).paint(g);
}
}
}
Rien de bien sorcier: on ajoute de quoi positionner un TexturePaint, et on modifie un brin paintComponent(Graphics g) pour texturer le panel plutôt que peindre avec la couleur de background.
that's it !
Quelques notes à propos de Apache Batik
- le format PGM n'est pas supporté dans un SVG Batik.
Mémo de l'installation sur mon serveur: Je ne veux pas installer les paquets tomcat-5.5, tomcat5.5-admin et tomcat5.5-webapps présent dans les dépôts Ubuntu. Car avec ces paquets:
- le fichier de configuration du service tomcat est
/etc/default/tomcat.
- les fichiers
server.xml et web.xml se trouvent dans /etc/tomcat, mais le reste des fichiers de configuration sont dans /var/lib/tomcat.
- les webapps sont dans
/var/lib/tomcat/webapps, mais l'application Tomcat se trouve dans /usr/share/tomcat.
- on a un script de gestion du service
/etc/init.d/tomcat.
Cette intégration dans le système peut être intéressante sous certains abords, mais je n'en ai pas l'utilité et puis, c'est surtout pas pratique du tout. Il est beaucoup plus propre d'installer manuellement un Tomcat dans
/home/tomcat, et donc sur la partition
/home du système sous l'utilisateur et le groupe
www-data. Ainsi, ça ne pourri pas
/usr, et de plus les applications Web à déployer peuvent être volumineuses, ou si une application Web écrit des données dans son
webContent (le répertoire où elle est déployée), c'est pas très pro. sans doute, mais c'est pratique. Pour créer un service système pour un Tomcat installé manuellement, il suffit d'écrire un fichier
/etc/init.d/tomcat tel que:
# Tomcat auto-start
#
# description: Auto-starts tomcat
# processname: tomcat
# pidfile: /var/run/tomcat.pid
export JAVA_HOME=/opt/jdk1.6.0_10
case "$1" in
start)
start-stop-daemon --start --chuid www-data:www-data --exec /home/tomcat/apache-tomcat-6.0.16/bin/startup.sh -- -d -r /home/tomcat/apache-tomcat-6.0.16/
;;
stop)
#start-stop-daemon --stop --exec /home/tomcat/apache-tomcat-6.0.16/bin/shutdown.sh
/home/tomcat/apache-tomcat-6.0.16/bin/shutdown.sh
;;
restart)
$0 stop
$0 start
;;
esac
exit 0
Il faut disposer les droits idoines sur le fichier:
chmod 755 /etc/init.d/tomcat
Puis l'ajouter aux scripts systèmes:
ln -s /etc/init.d/tomcat /etc/rc1.d/K99tomcat
ln -s /etc/init.d/tomcat /etc/rc2.d/S99tomcat
L'étape suivante pour une installation qui va bien, est d'utiliser le
connecteur Tomcat jk_connector. Pour Apache, c'est le module mod_jk pour accéder à Tomcat depuis Apache, et ainsi l'abstraire du port 8080 pour accéder aux applications web déployées sous Tomcat. Ce qui en plus d'être plus simple et pratique dans l'écriture des
URL, peut s'avérer nécessaire, par exemple si vous faites un appel à une servlet depuis un script
PHP qui est lui, hébergé sur un autre serveur sur lequel la création d'une socket sur un port autre que 80 est interdit (par exemple, les serveurs de free.fr). Pour mod_jk, installer le paquet libapache2-mod-jk, et éditer le fichier
/etc/apache2/mods-available/jk.load:
LoadModule jk_module /usr/lib/apache2/modules/mod_jk.so
JkLogFile /var/log/apache2/mod_jk.log
JkLogLevel debug
JkLogStampFormat "[%a %b %d %H:%M:%S %Y] "
JkMount / worker1
JkMount /* worker1
Les JkMount sont les points de montages des applications Web que vous voulez monter. Editer ensuite le fichier
/etc/apache2/workers.properties
:
workers.tomcat_home=/home/tomcat/apache-tomcat-6.0.16
workers.java_home=/opt/jdk1.6.0_10
ps=/
worker.list=worker1
worker.worker1.port=8009
worker.worker1.host=localhost
worker.worker1.type=ajp13
worker.worker1.lbfactor=1
Il ne suffit plus qu'a redémarrer Apache. Cette configuration fonctionne bien, mais n'est pas sensitive aux sous-domaines éventuels de votre configuration d'Apache, ainsi
http://trevize.net/Bouquinoscope et
http://njames.trevize.net/Bouquinoscope pointe vers la même webapp Tomcat. Une solution assez simple est de créer un VirtualHost pour notre Tomcat, par exemple
http://webapps.trevize.net, pour cela éditer le fichier worker.properties:
workers.tomcat_home=/home/tomcat/apache-tomcat-6.0.16
workers.java_home=/opt/jdk1.6.0_10
ps=/
worker.list=worker1
worker.webapps.port=8009
worker.webapps.host=localhost
worker.webapps.type=ajp13
worker.webapps.lbfactor=1
Commentez les points de montage JkMount indiqués précédemment dans
/etc/apache2/mods-available/jk.load. Pour finir, il suffit d'ajouter le VirtualHost en éditant un fichier
/etc/apache2/sites-available/webapps:
<VirtualHost *:80>
ServerAdmin nicolas.james@gmail.com
ServerName webapps.trevize.net
# Send servlet for context /servlets-examples to worker named worker1
JkMount /* worker1
# Send JSPs for context /jsp-examples to worker named worker1
JkMount /*.jsp worker1
</VirtualHost>
Il ne suffit plus qu'a monter le site avec
a2ensite webapps, et redémarrer Apache. Configuré ainsi, Tomcat est accessible uniquement sur
http://webapps.trevize.net. Pour que l'accès aux webapps sur le site soit plus eye-candy, un coup d'
URL-rewriting avec le module
rewrite pourra par exemple faire pointer
http://trevize.net/Bouquinoscope vers
http://webapps.trevize.net/Bouquinoscope.
Comme je travaille avec un ami sur un même bout de code pour un petit projet prospectif, j'ai déployé un subversion sur mon serveur.
Voici un mémo de quelques tests:
La machine utilisée tourne sous Ubuntu8.04.
Pour un usage en local:
- installer le paquet subversion.
- pour créer un repo:
$svnadmin create projectName
- pour le checkout:
$svn co file:///path/to/the/repository
- pour flagger un fichier comme nouveau dans le repo (et donc à ajouter sur le repo distant)
- pour commiter:
$svn co fileToCommit -m "message"
Si échec du dernier commit et intégration nécessaire, il faut récupérer la version plus récente présente sur le serveur (pour faire la fusion à la main):
SVN va alors créer 3 fichiers: file.mine (copie de la version modifiée en local), file.r1 (version locale avant modification), file.r3 (version du serveur, donc plus récente que file.r1).
Quand l'intégration est effectuée (fusion de file.mine et r3) alors il faut flagger le conflit comme résolu (et recommiter le fichier ensuite):
$svn resolved fileResolved
flags possibles:
C : conflict (fichier conflictuel)
A : added (fichier ajouté dans le projet)
D : deleted (fichier supprimé du projet)
G : fusion (fichier plus récent sur le repo et version locale modifiée depuis le dernier checkout mais la fusion ne pose pas de conflit)
M : modified (modifications sur un fichier qui n'a pas été modifié localement, alors le fichier locale est écraser par sa version du repository).
- pour créer une nouvelle branche à partir du trunk:
$svn cp http://.../trunk http://.../branches/mybranch
Rien de particulier, il suffit soit de lancer le deamon à la main, soit de faire un script à placer dans
/etc/rc1.d/K20svnserve et
/etc/rc2.d/S20svnserve (ou placer le script dans
/etc/init.d, et utiliser l'outil Debian
rcconf).
#!/bin/sh
case "$1" in
start)
start-stop-daemon --start --chuid www-data:www-data --exec /usr/bin/svnserve -- -d -r /home/subversion/usvn/svn
;;
stop)
start-stop-daemon --stop --exec /usr/bin/svnserve
;;
restart)
$0 stop
$0 start
;;
esac
exit 0
En réseau avec Apache sur le port 80:
- installer les paquets
subversion, subversion-tools, libapache2-svn
- charger le module
$a2enmod dav_svn
- rendre le repo accessible par apache:
Éditer
/etc/apache2/mods-available/dav_svn.conf
<Location /svn>
DAV svn
Require valid-user
SVNParentPath /home/subversion/repo/
AuthType Basic
AuthName "Subversion repository"
AuthUserFile /home/subversion/conf/htpasswd
AuthzSVNAccessFile /home/subversion/conf/access
</Location>
- gérer les utilisateurs avec
$htpasswd /home/subversion/conf/passwd nicolas
- gérer les droits en éditant le fichier
/home/subversion/conf/authz
[groups]
developer = nicolas
[projectName:/]
@developer = rw
* = r
En réseau avec USVN:
C'est la solution utilisée à la date de rédaction du billet. Plus léger et pratique qu'un Trac, mais certes bien moins puissant (ce n'est même pas comparable en fait, Trac étant un bug tracker), USVN ne propose pas de wiki, ni d'exploration du code (mais on peut naviguer dans l'arborescence du dépôt dans une interface Web).
USVN, UserfriendlySVN est un projet de l'Epitech distribué sous licence CECILL, qui permet la gestion de repository
SVN par une application
PHP (création, gestion des droits), en fait c'est très partique pour créer des dépôts
SVN à distance et gérer les droits d'accès.
- installer les paquets
libapache2-mod-php5 php5-sqlite sqlite3
- activer le module
$a2enmod rewrite
- récupérer l'archive de USVN et la copier dans /var/www, la décompresser et modifier le propriétaire des fichiers
$chown -R www-data: usvn
- vérifier le /usvn est accessible sur votre website, et utilisez l'application php pour l'installation.
Eclipse et SVN:
Avec Eclipse Ganymede, un projet Eclipse est né pour intégrer
SVN dans eclipse,
subversive, mais le plugin subclipse fonctionne aussi avec Ganymede.
- installer le plugin subclipse disponible sur Tigris.
Par défaut, une dépendance (buckminster) est nécessaire à l'installation du plugin (et semble poser problème), mais l'installation du plugin peut s'effectuer en décochant l'option "Integrations (Optional)".
- un "new project" puis "checkout projects from SVN", et ça roule tout seul.
Avec Subversive:
- installer le plugin subversive depuis les dépôts Eclipse
http://download.eclipse.org/technology/subversive/0.7/update-site/.
- installer un SVN connector, par exemple celui de Polarion:
http://www.polarion.org/projects/subversive/download/eclipse/2.0/update-site/
On y trouve une implémentation de Polarion, SVNKit, et une autre de Tigris, JavaHL.
Cependant, il semble que si vous avez des projets anciennement gérer avec subclipse, le plugin subversive n'arrive pas à reprendre la main dessus...
Dans le cadre d'un prototype sur mes recherches, j'investigue sur
MPEG-7.
Au début, j'avais définit un format de fichier de description, j'ai écrit une grammaire
XML assez sommaire pour avancer assez rapidement, et produire des fichiers
XML pour décrire, stocker des descripteurs, et annoter mes documents multimédia, mais au fur et à mesure que j'avançais, je m'approchais vraiment de ce que propose la norme.
C'est pourquoi depuis quelques temps, je m'attaque à un
XML binding en Java de la norme
MPEG-7.
Le langage Java propose dans son
API un mécanisme de
XML binding (depuis Java1.6, dans les versions précédente de Java c'était l'
API Jaxb, Java
API for
XML binding, qui était située dans un package externe, le JWSDP Java Web Services Developer Pack), et pour les normes comme
MPEG-7 basé sur des
XML-schemas, c'est très pratique.
(J'aurais bientôt a travailler sur
MPEG-21 pour les besoins d'un projet de labo, j'essayerai la même approche pour la production d'une requête
MPEG-21, j'en reparlerai ici en temps et en heure).
Pour la génération du binding, il y a JAXB (Sun) et XMLBeans (Apache), avec ces 2 frameworks, il y a des problèmes de compilations du bindings (grammaire non valide), dans les deux cas il semble y avoir un problème avec des définitions d'éléments au noms identique, ce n'est pas un problème de définition dans la norme, mais plutôt des outils JAXB et XMLBeans2.3 qui ne gèrent pas correctement l'inheritance à outrance utilisé dans la norme
MPEG-7. (réf. nécessaire).
Quelques liens web parlant du problème:
Il semble plus sérieux d'utiliser XMLBeans2.3, car malgré une petite modification aux schemas
MPEG-7 à faire, la modification reste acceptable.
JAXB quand à lui nécessite de modifier le nom de certains éléments, ce qui pourrait amener à des problèmes (fichiers
MPEG-7 générés non
MPEG-7 compliant).
Avec JAXB
Avec JAXB (version java1.6) des collisions de noms sont un problème, voir
http://cbvr.ims.tuwien.ac.at/user-documentation/mpeg7-java-binding-1.pdf. Il y en a plusieurs, c'est bloquant. La correction à apporter aux schemas est de modifier les noms de plusieurs éléments (à priori pas très pratique donc, car le
MPEG-7 résultant ne valide plus la norme). La correction proposée ci-dessous pour XMLBeans2.3 fonctionne également avec JAXB, mais elle ne suffit pas, il y a des propriétés au nom identique dans
mds-2001.xsd et Jaxb génère une erreur "already define";
Une solution pour contourner cela semble l'utilisation de
custumizations JAXB.
Avec XMLBeans
Avec XMLBeans2.3, il y a un problème dans
audio-2001.xsd mais qui peut être corrigé rapidement, voir
http://iiss039.joanneum.at/cms/index.php?id=231 (recherche menée par Joanum Research propose également un profile mpeg-7, le DAVP detailed AudioVisual Profile, pour la description de contenu audiovisuel simple, dans le sens de single);
Quelques liens:
- VizIR web server project: équipe autrichienne, membre du noe DELOS, utilise JAXB pour la génération du binding;
- le laboratoire Joaennum propose une librairie en C++ pour manipuler des documents MPEG-7, (avec un XML-binding en C++ développé maison), cette librairie de contient pas de features extractors. Ce laboratoire propose également une génération de XML-binding en Java utilisant XMLBeans (déjà cité plus haut);
- mpeg-7 and mpeg-21 community portal, voir dans la section "software and tools" une liste de projet autour de ou utilisant MPEG-7;
- LAS, Lightweigth Application Server for mpeg-7, utilise XMLBeans, dans le cadre du projet MECCA;
- un projet d'élève de l'université polytechnique de catalogne d'annotation de photographie, utilise XMLBeans. D'autres projets d'élèves de la même université qui utilise également XMLBeans;
- le site du NIST pour récupérer les schemas, et qui propose un validateur;
Dernièrement dans le cadre de ma thèse je suis un train de développer un petit crawler web dédiés aux images.
Certains sites web renvoient une erreur 403 si le user-agent est unknown.
Ce crawler est codé en Java et utilise les bibliothèques
Apache HttpClient et
HTML Parser.
Pour changer le user-agent, il n'y a pas de méthode dans HttpClient, il faut directement ajouter le header dans le paquet HTTP:
//change user-agent (cause of some website which reply 403 if user-agent unknown).
Header ua = new Header("User-Agent", "Mozilla");
method.setRequestHeader(ua);
Il y a un bug que je n'explique pas dans
HTML Parser (ou bien n'est-ce pas un bug ? mais je ne comprends pas ce comportement): les caractères
URL-encoded, par exemple %20, deviennent %2520 (double
URL-encoding ???).
LIRe, Lucene Image Retrival, c'est une library de Mathias Lux, j'ai découvert son blog par del.ico.us, http://www.semanticmetadata.net/.
LIRe create a Lucene index of image feature for CBIR.
Three of the available image retrieval features are taken from the
MPEG-7 standard: ScalableColor, ColorLayout, EdgeHistogram, and an Auto Color Correlogram.
LIRe is part of Caliph&EMIR project.
Rien à voir avec le projet européen EMIR.
EMIR, European Multilingual Information Retrieval, finalisé en 1994, c'est un projet ancien maintenant, spirit est de la même époque.
Le système SPIRIT est monolingue, repose sur une analyse linguistique du document et une pondération statistique.
PRISME a pour objectif la conception et la réalisation d'un outil de veille technologique multilingue sur Internet.
PRISME utilise les technologies multilingues développées dans le cadre du projet européen EMIR, et du produit SPIRIT de T.GID.
Un test rapide de LireDemo, une application en java pour tester LIRe, ne fonctionne pas, pourtant sur le site de l'auteur il est bien indiqué qu'un JDK1.6 est requis mais des exceptions sont catchées lors de la construction de l'index.