- Javaclass : can't make it work. However, seems to need work in order to pass data between Java and Python worlds.
- JCC : a little bit clumsy, it particular it needs to add very large options when launching Python.
- JPype : the simplest. A Python module with an easy usage.
JPype installation
It is quite regular:unzip JPype-0.5.4.1.zip
cd JPype-0.5.4.1
sage -python setup.py buildHere a bug usually appears. JPype rely on the sets module, which is neither included in recent Python. There is not a lot of modification. You can download a modified version here. Beware, you must still follow the README to provide the path to your JDK installation. If the compilation is OK (you can send me a mail if not), the module is installed with:
sage -python setup.py installNow we can work inside a Sage notebook (downloadable here).
JPype use
Quite simple:- import the module,
- launch a java virtual machine,
- import one or more Java packages.
startJVM() is the path to the dynamical library of the JVM. To find it, you can do:
locate libjvm.soThe second parameter allows to extend the classpath in order to add libraries, here Image. At this point, all the methods of Java-package
ij are available, directly. We can by example load an image and display it in the notebook:
JPype allows to convert Java objects in Python objects (and vis versa), very easily. Thus a Python object is constructed from a Java one, all the methods available:
<jpype._jclass.ij.process.ByteProcessor object at 0xb7ad52c> <jpype._jclass.ij.process.ByteProcessor object at 0xb7ad52c> |
We can now find Lena edges by calling a ByteProcessor method:
<jpype._jclass.ij.process.ByteProcessor object at 0xb313652c> <jpype._jclass.ij.process.ByteProcessor object at 0xb313652c> |
To display side by side Lena and its edges, i need to get the pixel array of each ImageProcessor, by calling getIntArrray():
<jpype._jarray.int[][] object at 0xb313a20c> <jpype._jarray.int[][] object at 0xb313a20c> |
This object is Python indexable, as a regular array. I can give it directly to the Python functions that display images. Here, the implicit conversion between primitive types is realized by JPype:
|
|
Java and Python arrays are not identical, one is row-major and the other is column-major. The display is thus turned. In order to obtain a correct display, a transposition is needed. This can be done with numpy. I just need to construct a numpy array from the Java array:
array([[ 4, 1, 0, ..., 24, 9, 8],
[ 6, 4, 1, ..., 34, 50, 53],
[ 4, 8, 6, ..., 26, 31, 27],
...,
[ 73, 65, 57, ..., 13, 14, 21],
[174, 169, 163, ..., 29, 25, 22],
[113, 113, 111, ..., 33, 35, 14]], dtype=uint8)
array([[ 4, 1, 0, ..., 24, 9, 8],
[ 6, 4, 1, ..., 34, 50, 53],
[ 4, 8, 6, ..., 26, 31, 27],
...,
[ 73, 65, 57, ..., 13, 14, 21],
[174, 169, 163, ..., 29, 25, 22],
[113, 113, 111, ..., 33, 35, 14]], dtype=uint8)
|
Here is now a conversion function from an ImageProcessor to a numpy array, including the transposition:
The display is now correct:
Best of the best
A Sage notebook has a nice feature that allows to display images interactively depending on a adjustable parameter, with a slider. As Java objects are directly converted in Python ones, i can manipulate them as well and take advantage of Sage for Scientific graphisms:
|
ok
|
Finally, the JPype documentation advises to close the virtual machine, when leaving. Even if i nether had any problems when closing directly the notebook, here is the way:
JVM activity report :
classes loaded : 28
JVM has been shutdown
JVM activity report :
classes loaded : 28
JVM has been shutdown
|
FMN.
Appendix (donwloadables)
- Une sélection (automatique) de billets similaires :
- Plugin ImageJ minimal en Clojure: inverser une image
- Représentation d'images (Clojure et Java), première partie.
- Image numérique et tableau : est-ce identique ?
- Représentation d'images (Clojure et Java) #2
- Aggrégateur de blogs sur l'analyse et le traitement d'images
Merci beaucoup pour ce tutoriel qui est une excellente mise en jambe et ouvre à Python, par ce biais un champ immense de possibilités en traitement d'image. Votre exemple marche parfaitement sous Windows aussi. Par contre, je me casse les dents sur l'appel de classes de plugins installés sur ImageJ. Si vous avez surmonté cette difficulté, un tuyau serait le bienvenu. Dans tous les cas, merci beaucoup d'avoir partagé votre précieuse expérience.
Laurent, je suis ravi d'apprendre que ce tutoriel est profitable.
Concernant l'appel de plugins, quelle est exactement la difficulté : appeler des plugins depuis python ou appeler des plugins tout court ?
Bonjour et merci de la réponse. La difficulté était d'appeler des plugins ImageJ depuis Python. Pour une raison simple (voir après) mais mal indiquée par le module, je n'arrivais pas à accéder aux classes de ces plugins. En fait, pour un plugin imagej donné, il faut fournir non seulement le chemin de son .jar mais aussi ceux de toutes les classes dont il dépend dans le path java. Le message d'erreur de jpype ne m'a pas beaucoup aidé, puisqu'il indiquait toujours qu'il ne trouvait pas la classe principale que je tentais de lier, alors qu'en fait c'était les dépendances qui n'étaient pas trouver. Ce problème est donc résolu.
Par contre, au final, je suis très déçu : les échanges de donnés entre python et imagej sont très très lents. Sur mon pc, une image de 1024x1024 octets met un temps > 5 sec pour transiter d'un objet numpy.array vers un objet ByteProcessor, et vice versa (voir votre exemple avec une image 1024x1024). Je pensais accéder à toute la puissance des traitements disponibles sur imagej depuis le projet que je développe en Python. C'est le cas, mais cette lenteur est, pour mon projet, inacceptable : je vise un temps de traitement global inférieur à la seconde (ce qui est déjà énorme sur les ordinateurs d'aujourd'hui). Et je voulais éviter de devoir coder les plugins imagej qui m'intéressaient de java vers C et créer une dll appelable depuis Python. Savez-vous si cette lenteur de transfert est normal et s'il n'y a pas moyen d'accélérer les choses ? Pouvez-vous faire l'essai avec votre exemple et une image de 1024x1024 pixels d'un octet ? A titre de comparaison, le transfert en python et une dll à l'aide de ctypes est quasi-instantané.
Merci encore une fois de partager votre expérience.
Laurent,
effectivement avec java il faut inclure dans le classpath, le chemin de toutes les librairies.
Sinon pour le problème de transfert. Mes temps de conversions sont du même ordre. Par exemple
donne un temps d'env. 5s (pour une image 1024 par 1024). Par contre en ajoutant une indication du type du tableau crée: le temps de conversion "tombe" à ~2s. Ce qui reste assez inexploitable si des transferts fréquents sont nécessaire. L'auteur de JPype (voir Performances dans la doc) est conscient de cette inéfficacité, due à jni semble-t-il. La comparaison avec le transfert en utilisant ctypes est forcément en faveur de ctypes, puisque l'on reste dans le même "monde". Ici il y a un transfert forcé de/vers la jvm.Dans ton cas, je procéderai autrement. Des tests rapides montrent qu'en passant par une écriture/lecture disque, on peut transférer une image beaucoup plus rapidement. Par exemple, avec:
ij.IJ.save(im, 'tmp.gif') ar_python = pylab.imread('tmp.gif')le temps de transfert est de 0,08s.J'ai aussi fait un test sur une machine plus musclée d'un collègue. Le temps passe de 5 à 3 secondes. Donc pas de miracle. Je voulais éviter le mécanisme d'échanges de données sur disque, car, pour assurer une fiabilité correcte, il faut prévoir et coder un "vrai" protocole d'échange (synchronisation des deux logiciels, gestion de message d'erreur, adaptation des chemins si distribution du logiciel, "handshake" etc...) entre les deux programmes. Et ce, des deux côtés ce qui implique de coder un plugins imagej dédié. Effectivement, l'auteur indique bien des problèmes de performance mais je n'aurais jamais imaginé que cela pouvait tomber si bas (1024x1024 octects ce n'est jamais qu'un objet "petit" objet de 1 Mo). J'ai regardé s'il y avait moyen de partager entre imagej et python l'espace mémoire directement sans copie préalable (transmission simple de pointeurs). Mais, de ce point de vue, java et l'api imagej ne semblent pas offrir cette possibilité. Enfin, il y a encore une possibilité : je regarde s'il est possible de transférer non pas l'image brute mais déjà compressée en mémoire. Mais cela semble aussi complexe.
Je suis donc sur le point de renoncer et de m'en remettre à la conversion de plugins java en C au sein d'une dll.
En tout cas merci d'avoir pris le temps de jeter un oeil à ces problèmes. Si jamais je trouve une astuce, je ne manquerai pas de la partager sur votre blog.
Laurent
Il semble que jni permette d'obtenir depuis C un pointeur sur un tableau (voir http://java.sun.com/docs/books/jni/html/objtypes.html#27346). Mais je n'ai jamais testé cela. En fait pour moi l'intérêt de JPype est d'accéder à java, sans mettre les mains dans JNI. Mais il semble qu'il y ait un certain prix à ce confort.
Peut-être que les autres méthodes que je mentionne en début de billet (JCC et JavaClass) sont plus performantes? En tout cas, merci d'avoir sur ce problème et je suis preneur d'infos sur ce point.
FMN.
J'ai fait un nouveau test, en utilisant les wrappers de jpype. Et je me suis aperçu de la chose suivante : (image est de type numpy.array de 2 dimensions, 1024*1024, dtype=uint8)
JArray(JInt,2)(image) : 4 sec. Permet de transférer vers un objet ByteProcessor de imagej avec la méthode setIntArray de ImageProcessor
JArray(JInt,1)(image) : 0.003 sec !!! Mais je n'ai pas trouvé de possibilité, dans l'api imagej, de transférer vers un objet ImageProcessor.
JArray(JByte,1)(image) : 0.0012 sec. Je pensais pouvoir transférer les données vers un objet ByteProcessor grâce à la méthode setPixels. Mais jpype me retourne une erreur d'exception alors que le code de cette méthode devrait marcher (pour ce que je connais de java).
Conclusion : je ne sais pas ce qui entraine ce facteur 1000 entre les deux types de conversion (1D ou 2D). Le code python ou le code java ? En outre, je ne trouve pas de point d'entrer dans le code imagej pour convertir JArray(JByte,1) vers ByteProcessor. C'est vraiment frustrant, car là je sens que j'approche d'une solution. Une solution serait de créer un plugin imagej permettant ce transfert. Mais là je touche à mes limites : je ne suis pas codeur java.
Je vous tiens au courant si je progresse.
Il semble que la conversion dans les deux cas ne porte pas sur le même nombre d'éléments :
renvoit la valeur 1024. Mais le tableau renvoyé ne contient que des uint8. Comme en Java, un tableau 2D est un tableau de tableau, seuls les premiers éléments soient retenus. Par contre: donne la bonne valeur 1048576, mais pour une durée ~0.67s.Sinon mes essais actuels pour affecter le tableau ainsi créer à un ByteProcessor echouent également. Je continue...
Hum, exact. Bien vu pour le coup du 2D python vers 1D java. J'étais passé à côté. Grand merci.
Je n'ai pas trouvé d'autres solutions. Finalement, je m'en remets à l'échange d'images par lecture/écriture : les problèmes de synchronisation et de gestion d'erreurs entre les deux machines sont gérés par jpype, et les performances sont correctes, sauf pour l'écriture par imagej qui semble prendre environ 0.5 sec sur un pc avec peu de ram (1 Go) alors que ce chiffre tombe à 0.05 sur un autre pc avec 2 Go de ram, ou lors du premier chargement de la machine java. C'est un comportement étrange qui semble lié à la mise en cache de la machine. Merci pour tout. Laurent
Après quelques discussions avec des collègues, ce genre de transfert est bien le point faible de JNI. Si j'ai le temps, je regarderais les performances de JNA qui indique : Java array and NIO Buffer arguments (primitive types and pointers) as pointer-to-buffer. A voir.