<?xml version="1.0" encoding="UTF-8"?><rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>OCaml de pied en cap &#187; Camlimages</title>
	<atom:link href="https://blog.developpez.com/ocamlblog/pcategory/informatique/camlimages/feed" rel="self" type="application/rss+xml" />
	<link>https://blog.developpez.com/ocamlblog</link>
	<description></description>
	<lastBuildDate>Fri, 22 Mar 2013 03:12:26 +0000</lastBuildDate>
	<language>fr-FR</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
	<generator>https://wordpress.org/?v=4.1.42</generator>
	<item>
		<title>Aller simple pour le pays des fantômes</title>
		<link>https://blog.developpez.com/ocamlblog/p8646/informatique/aller_simple_pour_le_pays_des_fantomes</link>
		<comments>https://blog.developpez.com/ocamlblog/p8646/informatique/aller_simple_pour_le_pays_des_fantomes#comments</comments>
		<pubDate>Fri, 19 Feb 2010 20:04:04 +0000</pubDate>
		<dc:creator><![CDATA[Cacophrene]]></dc:creator>
				<category><![CDATA[Biologie]]></category>
		<category><![CDATA[Camlimages]]></category>
		<category><![CDATA[Informatique]]></category>
		<category><![CDATA[LablGTK2]]></category>
		<category><![CDATA[OCaml]]></category>

		<guid isPermaLink="false"></guid>
		<description><![CDATA[Après un long moment d&#8217;absence, je reviens pour vous parler d&#8217;une notion dont l&#8217;intérêt m&#8217;a été révélé par un article sur le blog de bluestorm. Il s&#8217;agit des types fantômes, dont je souhaite vous montrer ici tout le bien. Naturellement, je vais partir d&#8217;un cas concret autour de ce que je développe actuellement. Le contexte Une application pour microscopistes Je suis actuellement en train d&#8217;écrire les grandes lignes d&#8217;un programme destiné à des microscopistes amateurs, [&#8230;]]]></description>
				<content:encoded><![CDATA[<p><center></p>
<table>
<tr>
<td><a href="http://wwwfun.kurims.kyoto-u.ac.jp/soft/lsl/lablgtk.html" target="_blank"><img src="http://wwwfun.kurims.kyoto-u.ac.jp/soft/lsl/gtk-logo-half.gif"/></a></td>
<td><a href="http://pauillac.inria.fr/camlimages/" target="_blank"><img src="http://pauillac.inria.fr/camlimages/fumicaml2-small.jpg"/></a></td>
<td><a href="http://caml.inria.fr/" target="_blank"><img src="http://caml.inria.fr//pub/logos/caml-inria-fr.128x58.gif"/></a></td>
</tr>
</table>
<p></center></p>
<p>Après un long moment d&rsquo;absence, je reviens pour vous parler d&rsquo;une notion dont l&rsquo;intérêt m&rsquo;a été révélé par un article sur le blog de <a href="http://blog.huoc.org/7-en-bref-aussi.html" target="_blank">bluestorm</a>. Il s&rsquo;agit des <strong>types fantômes</strong>, dont je souhaite vous montrer ici tout le bien. Naturellement, je vais partir d&rsquo;un cas concret autour de ce que je développe actuellement.</p>
<p><span id="more-7"></span></p>
<h3>Le contexte</h3>
<h4>Une application pour microscopistes</h4>
<p>Je suis actuellement en train d&rsquo;écrire les grandes lignes d&rsquo;un programme destiné à des microscopistes amateurs, dont il existe déjà une version en Visual Basic (à réécrire et faire évoluer). Ce programme doit permettre de mettre en forme des images pour un forum en leur ajoutant un cartouche contenant diverses informations utiles (échelle, type de matériel, coloration éventuelle, etc.). Je vous donne une image ci-dessous pour vous faire une idée. Une caractéristique importante de ce logiciel réside dans sa capacité à dessiner des échelles à partir d&rsquo;une relation entre un nombre X de pixels et une taille Y en micromètres, déterminée sur une image étalon.</p>
<p><center><img src="http://blog.developpez.com/media/Helvella_leucomelaena-1_small.jpg" width="800" height="600" alt="" /><br />
<strong>Spores d&rsquo;helvelle après coloration.<br />
Objectif Zeiss Planapo 100/1.3</strong><br />
</center></p>
<h4>Image d&rsquo;étalon</h4>
<p>Une image étalon est une photo dont les éléments ont une taille connue, prise dans les mêmes conditions qu&rsquo;une image classique. Le plus souvent, il s&rsquo;agit d&rsquo;une « règle » dont les graduations sont espacées de 10 µm (la bagatelle de 100 graduations dans un millimètre&#8230;). Le programme charge l&rsquo;image d&rsquo;étalon, l&rsquo;analyse à la manière d&rsquo;un outil de reconnaissance de caractères (en beaucoup plus simple !), et en déduit la taille d&rsquo;une graduation, exprimée en pixels. De son côté, l&rsquo;utilisateur indique la taille réelle correspondante, en micromètres cette fois-ci <em>(cf. l&rsquo;image ci-dessous)</em>.</p>
<p><center><img src="http://blog.developpez.com/media/IMG_0029_small.JPG" width="800" height="533" alt="Micromètre" /><br />
<strong>Image d&rsquo;étalon (1 graduation correspond à 10 µm).<br />
Objectif Zeiss Plan Neofluar 63/1.25</strong><br />
</center></p>
<h3>Manipulation d&rsquo;images</h3>
<p>Pour automatiser la reconnaissance des graduations, on souhaite implémenter l&rsquo;algorithme suivant (plutôt une recette de cuisine) :</p>
<ul>
<li>Charger l&rsquo;image en couleur (24 bits).</li>
<li>La transformer en niveaux de gris.</li>
<li>Augmenter la luminosité pour éclaircir le fond.</li>
<li>Passer en noir et blanc.</li>
<li>Effacer les poussières pour uniformiser le fond.</li>
<li>Reboucher les trous dans les traits de graduations.</li>
<li>Mesurer les traits de graduation et les espaces qu&rsquo;ils délimitent.</li>
<li>En déduire la taille d&rsquo;une graduation (un espace + un trait).</li>
</ul>
<p>Pour y parvenir, nous allons utiliser <a href="http://wwwfun.kurims.kyoto-u.ac.jp/soft/lsl/lablgtk.html">LablGTK</a> et <a href="http://pauillac.inria.fr/camlimages/">CamlImages</a>, deux bibliothèques qui dialoguent plutôt bien. Nous allons nous fixer comme objectif d&rsquo;écrire un type <code class="codecolorer text default"><span class="text">picture</span></code> polymorphe. Nous nous en servirons pour distinguer les images en couleur (24 bits), en niveaux de gris et en noir et blanc. Les fonctions telles que <code class="codecolorer text default"><span class="text">get_width</span></code> ou <code class="codecolorer text default"><span class="text">get_pixbuf</span></code> s&rsquo;appliqueront à tout type d&rsquo;image. </p>
<h3>Choix d&rsquo;une interface</h3>
<p>Pour faire court, nous voudrions quelque chose comme ceci :</p>
<pre><strong>type</strong> 'a picture

<strong>type</strong> fullcolor
<strong>type</strong> grayscale
<strong>type</strong> monochrom

<strong>val</strong> from_file : string -> fullcolor picture
<em>(** Charge une image en couleurs. *)</em>

<em>(** Fonctions usuelles, valables pour tout type d'image. *)</em>
<strong>val</strong> copy : 'a picture -> 'a picture
<strong>val</strong> get_width : 'a picture -> int
<strong>val</strong> get_height : 'a picture -> int
<strong>val</strong> get_pixbuf : 'a picture -> GdkPixbuf.pixbuf

<strong>val</strong> grayscale : 
  ?filter:(Color.rgb -> int) -> 
  fullcolor picture -> grayscale picture
<em>(** Conversion en niveaux de gris d'une image couleur (24 bits). *)</em>

<strong>val</strong> black_and_white :
  ?threshold:int ->
  grayscale picture -> monochrom picture
<em>(** Conversion en noir et blanc d'une image en niveaux de gris. *)</em>

<strong>val</strong> noise_reduction :
  ?threshold:int ->
  monochrom picture -> monochrom picture
<em>(** Effacement des poussières sur une image en noir et blanc. *)</em>

<strong>val</strong> fill_gaps :
  ?threshold:int ->
  monochrom picture -> monochrom picture
<em>(** Rebouchage des trous sur une image en noir et blanc. *)</em></pre>
<p>Pour que cet algorithme ne reste pas nébuleux, voici <a href="http://www.box.net/shared/88aynv0ory" target="_blank">un PDF d&rsquo;illustration</a>.</p>
<h3>Du côté de l&rsquo;implémentation</h3>
<p>Après l&rsquo;interface, voyons l&rsquo;implémentation. <em>A priori</em>, cela peut sembler difficile. Pourtant, grâce aux types fantômes, nous n&rsquo;avons presque rien à faire ! Définissons d&rsquo;abord le type <code class="codecolorer text default"><span class="text">picture</span></code> :</p>
<pre><strong>type</strong> 'a picture = {pict: OImages.rgb24_class}</pre>
<p>Ne vous étonnez pas de trouver ici un record alors qu&rsquo;il paraît inutile. Le vrai type <code class="codecolorer text default"><span class="text">picture</span></code>, que j&rsquo;utilise dans mon application, contient d&rsquo;autres champs (mais <strong>aucun</strong> n&rsquo;est de type <code class="codecolorer text default"><span class="text">'a</span></code>, c&rsquo;est très important). Il a été simplifié ici par souci de pédagogie.</p>
<p>Définissons maintenant trois types fantômes :</p>
<pre><strong>type</strong> fullcolor
<strong>type</strong> grayscale
<strong>type</strong> monochrom</pre>
<p><font color="#000080">Un <strong>type fantôme</strong> est un <strong>type utilisé comme paramètre d&rsquo;un autre type</strong> (à la manière du type <code class="codecolorer text default"><span class="text">int</span></code> dans <code class="codecolorer text default"><span class="text">int list</span></code>), mais <strong>qui ne sert pas dans la définition de ce type</strong> (comme <code class="codecolorer text default"><span class="text">'a</span></code> dans <code class="codecolorer text default"><span class="text">'a picture</span></code> ci-dessus). Le type fantôme vous permet d&rsquo;ajouter une information (une <em>contrainte</em>) qui sera propagée correctement par l&rsquo;algorithme d&rsquo;inférence de types, sans impact sur l&rsquo;implémentation. Dans mon cas, il s&rsquo;agit surtout de s&rsquo;assurer que l&rsquo;on passe bien une image en niveaux de gris à la fonction <code class="codecolorer text default"><span class="text">black_and_white</span></code>, par exemple.</font></p>
<p>Nous pouvons maintenant écrire la fonction de création :</p>
<pre><strong>let</strong> from_file str = {pict = OImages.rgb24 (OImages.load path [])}</pre>
<p>puis les fonctions utilitaires :</p>
<pre><strong>let</strong> get_width t = t.pict#width
<strong>let</strong> get_height t = t.pict#height
<strong>let</strong> get_pixbuf t = Imagegdk.to_pixbuf t.pict#coerce
<strong>let</strong> copy t = {t with pict = Oo.copy t.pict}</pre>
<p>Nous pouvons maintenant en venir aux fonctions qui agissent directement sur l&rsquo;image et, comme vous allez le constater, on ne se doute de rien en lisant cette partie du code  :</p>
<pre><strong>let</strong> grayscale ?(filter = Color.brightness) pic =
  <strong>let</strong> res = copy pic <strong>in</strong>
  <strong>let</strong> img = res.pict <strong>in</strong>
  <strong>for</strong> x = 0 <strong>to</strong> img#width - 1 <strong>do</strong>
    <strong>for</strong> y = 0 <strong>to</strong> img#height - 1 <strong>do</strong>
      <strong>let</strong> res = filter (img#unsafe_get x y) <strong>in</strong>
      img#unsafe_set x y {Color.r = res; g = res; b = res}
    <strong>done</strong>
  <strong>done</strong>;
  res

<strong>let</strong> black = {Color.r = 0; g = 0; b = 0}
<strong>let</strong> white = {Color.r = 255; g = 255; b = 255}

<strong>let</strong> black_and_white ?(threshold = 0x90) pic =
  <strong>let</strong> res = copy pic <strong>in</strong>
  <strong>let</strong> img = res.pict <strong>in</strong>
  <strong>for</strong> x = 0 <strong>to</strong> img#width - 1 <strong>do</strong>
    <strong>for</strong> y = 0 <strong>to</strong> img#height - 1 <strong>do</strong>
      <strong>let</strong> rgb = img#unsafe_get x y <strong>in</strong>
      img#unsafe_set x y (<strong>if</strong> rgb.Color.r > threshold <strong>then</strong> white <strong>else</strong> black)
    <strong>done</strong>
  <strong>done</strong>;
  res</pre>
<p>et ainsi de suite (le détail d&rsquo;implémentation des fonctions <code class="codecolorer text default"><span class="text">&nbsp;</span></code> et <code class="codecolorer text default"><span class="text">fill_gaps</span></code> sort du cadre de ce billet; je peux développer sur demande, mais il ne me semble pas pertinent de le faire ici). Avec un appel à <code class="codecolorer text default"><span class="text">ocamlc -i</span></code>, toutes ces fonctions accepteraient des images de type <code class="codecolorer text default"><span class="text">'a picture</span></code>. La contrainte que nous avons posée dans l&rsquo;interface est à la fois valide et transparente du côté de l&rsquo;implémentation.</p>
<p>Il est intéressant de noter que les types fantômes apportent une information supplémentaire sans impact sur l&rsquo;implémentation. Cela renforce la sûreté du code sans nuire aux performances (imaginez la lourdeur qui consisterait à s&rsquo;assurer d&rsquo;abord que tous les pixels sont bien des teintes de gris !).</p>
<h3>En guise de conclusion&#8230;</h3>
<p>Je parlais d&rsquo;un <em>aller simple</em> vers le monde des fantômes. Vous savez maintenant pourquoi : quand on y a goûté, on ne peut plus s&rsquo;en passer. <strong>Les types fantômes, c&rsquo;est bien, mangez-en !</strong></p>
<p>À bientôt,<br />
Cacophrène</p>
]]></content:encoded>
			<wfw:commentRss></wfw:commentRss>
		<slash:comments>2</slash:comments>
		</item>
	</channel>
</rss>
