Statistics
| Revision:

root / tmp / org.txm.analec.rcp / src / org / txm / macro / analec / exploit / StructuresIndexMacro.groovy @ 875

History | View | Annotate | Download (18.4 kB)

1
package org.txm.macro.analec.exploit
2
// Copyright © 2017 ENS de Lyon, CNRS, University of Franche-Comté
3
// Licensed under the terms of the GNU General Public License (http://www.gnu.org/licenses)
4
// @author sheiden
5

    
6
/*
7
 Macro affichant les statistiques de différentes structures d'un corpus
8
 Paramètres de la macro :
9
 - corpus : le corpus sélectionné dans la vue Corpus
10
 - structures : liste des structures à interroger. Séparer les noms par une virgule.
11
 - structProperties : liste des propriétés de structures. Séparer les noms par une virgule.
12
 Il doit y avoir autant de propriétés de structures que de structures indiquées dans le paramètre structures.
13
 Les structures doivent posséder la propriété demandée.
14
 Ce paramètre peut être laissé vide, dans ce cas la colonne 'prop' n'est pas affichée.
15
 - query : requête CQL de sélection de mots exprimée obligatoirement en format complet : [...]
16
 Par exemple :
17
 - [frpos="N.*"] pour sélectionner les noms communs et les noms propres
18
 - [] pour sélectionner tous les mots
19
 - wordProperty : propriété de mot utilisée pour calculer le vocabulaire et les fréquences
20
 - displayIndex : calculer l'index hiérarchique des valeurs de la propriété wordProperty pour la requête query sur chaque structure
21
 - Vmax : nombre maximum des mots les plus fréquents à afficher dans l'index
22
 Résultat :
23
 Le résultat est un tableau TSV affiché dans la console.
24
 On peut l'exploiter avec un copier/coller dans Calc.
25
 Chaque ligne correspond à une structure du corpus.
26
 Les lignes sont ordonnées par ordre hiérarchique des structures du début à la fin du corpus.
27
 Les colonnes sont :
28
 - struct : nom de la structure
29
 - prop : valeur de la propriété de la structure
30
 (si le paramètre structProperties est vide, cette colonne est absente du résultat)
31
 - start : position du premier mot de la structure dans le corpus
32
 (les positions du corpus sont numérotées à partir de 0).
33
 Les colonnes start et end sont pratiques quand on n'a pas de propriété de structure à afficher pour se repérer dans le corpus.
34
 - end : position du dernier mot de la structure
35
 - T : taille de la structure (end-start)
36
 - t : nombre de mots sélectionnés dans la structure
37
 - v : nombre de valeurs différentes de la propriété des mots sélectionnés dans la structure
38
 - fmin : fréquence minimale des valeurs de la propriété de mots sélectionnés dans la structure
39
 - fmax : fréquence maximale des valeurs de la propriété de mots sélectionnés dans la structure
40
 - index : l'index hiérarchique des valeurs de la propriété de mot choisie des mots sélectionnés par la requête CQL
41
 Exemple de résultats sur le texte "Essais sur la peinture" de Diderot :
42
 struct        prop        start        end        T        t        v        fmin        fmax        index
43
 text        DiderotEssais        46203        56871        10668        2011        903        1        38        [nature, couleur, homme, tableau, lumière, objets, œil, toile, art, effet, corps, artiste, ombre, ombres, deux, peintre, peinture, dessin, couleurs, tête]
44
 div        0        46214        49223        3009        549        327        1        16        [nature, homme, modèle, figure, deux, école, artiste, chose, âge, figures, dessin, actions, fois, professeur, action, attitude, manière, femme, col, tête]
45
 p        0        46220        46259        39        5        5        1        1        [nature, forme, cause, êtres, un]
46
 p        1        46260        46456        196        36        25        1        3        [yeux, col, épaules, gorge, femme, jeunesse, nature, accroissement, orbe, paupières, cavité, absence, organe, sourcils, joues, lèvre, mouvement, altération, parties, visage]
47
 p        2        46457        46578        121        28        26        1        2        [pieds, nature, regards, homme, dos, poitrine, forme, cartilages, col, vertèbres, tête, mains, articulation, poignet, coudes, arrière, membres, centre, gravité, système]
48
 p        3        46579        46622        43        5        4        1        2        [causes, effets, êtres, imitation]
49
 p        4        46623        46727        104        22        20        1        2        [ignorance, règles, effets, causes, convention, suites, peine, artiste, imitation, nature, pieds, jambes, genoux, têtes, tact, observation, phénomènes, liaison, enchaînement, difformités]
50
 p        5        46728        46797        69        10        6        1        4        [nez, Antinoüs, nature, difformité, altérations, reste]
51
 p        6        46798        46859        61        9        7        1        2        [règles, nature, homme, rue, chose, statue, proportions]
52
 p        7        46860        46942        82        13        11        1        2        [extrémité, pied, voile, bossu, Venus, Medicis, nature, figure, crayons, monstre, chose]
53
 p        8        46943        46982        39        11        11        1        1        [figure, système, suites, inconséquence, principe, production, art, mille, lieues, œuvre, nature]
54
 p        9        46983        47196        213        38        30        1        5        [homme, figure, âge, fonctions, mystères, art, artiste, proportions, despotisme, nature, condition, sacrifice, cent, manières, organisation, habitude, facilité, grandeur, proportion, membre]
55
 ... [13 paragraphes] ...
56
 div        1        49224        52163        2939        531        307        1        23        [couleur, nature, chair, artiste, toile, art, homme, yeux, œil, couleurs, tableau, harmonie, effet, dessin, palette, organe, ton, coloriste, vie, ami]
57
 p        24        49230        49258        28        7        7        1        1        [C', dessin, forme, êtres, couleur, vie, souffle]
58
 p        25        49259        49284        25        6        6        1        1        [maîtres, art, juges, dessin, monde, couleur]
59
 p        26        49285        49354        69        16        16        1        1        [dessinateurs, coloristes, littérature, Cent, froids, orateur, Dix, orateurs, poète, intérêt, homme, Helvétius, dix, bons, peine, mort]
60
 p        27        49355        49485        130        24        21        1        2        [artiste, besoin, échelle, ami, atelier, teintes, demi-, palette, quart, heure, travail, ordre, pendant, passage, auteur, bureau, ligne, livre, place, allure]
61
 p        28        49486        49680        194        46        42        1        2        [yeux, toile, chaos, œuvre, sentiment, couleur, bouche, palette, image, pinceau, création, oiseaux, nuances, plumage, fleurs, velouté, arbres, verdures, azur, ciel]
62
 p        29        49681        49967        286        48        43        1        3        [nature, organe, homme, arbre, artistes, chose, monde, variété, coloristes, couleur, disposition, doute, œil, couleurs, tableau, effets, rouges, blancs, tapisserie, murs]
63
 p        30        49968        50068        100        20        17        1        3        [fois, organe, peintre, ouvrage, littérateur, caractère, disposition, pente, homme, voix, explosion, état, silence, artiste, tableau, couleur, coloris]
64
 p        31        50069        50105        36        7        7        1        1        [coup, organe, affection, corps, vapeur, nature, imitation]
65
 p        32        50106        50267        161        26        19        1        4        [couleur, palette, artiste, effet, tableau, teintes, couleurs, idée, endroit, fois, appréciation, scène, composition, manie, travail, teinte, composé, substances, unes]
66
 p        33        50268        50319        51        7        7        1        1        [général, harmonie, composition, peintre, effet, pinceau, couleur]
67
 ... [etc.]
68
 Avec les paramètres :
69
 - structures : text,div,p
70
 - structProperties : id,n,n
71
 - query : [frpos="N.*"]
72
 - wordProperty : word
73
 - displayIndex : true
74
 - Vmax : 20
75
 */
76

    
77
// Déclarations
78

    
79
import org.kohsuke.args4j.*
80

    
81
import groovy.transform.Field
82

    
83
import org.txm.rcp.swt.widget.parameters.*
84
import org.txm.Toolbox
85
import org.eclipse.ui.console.*
86
import org.txm.macro.cqp.*
87
import org.txm.searchengine.cqp.corpus.Corpus
88
import org.txm.searchengine.cqp.corpus.Partition
89
import org.txm.searchengine.cqp.corpus.Property
90
import org.txm.searchengine.cqp.corpus.QueryResult
91
import org.txm.searchengine.cqp.corpus.Subcorpus;
92
import org.txm.searchengine.cqp.corpus.query.Query
93
import org.txm.rcp.commands.*
94

    
95
byte CQI_CONST_FIELD_MATCH = (byte) 0x10
96

    
97
def scriptName = this.class.getSimpleName()
98

    
99
def selection = []
100
for (def s : corpusViewSelections) {
101
        if (s instanceof Corpus) selection << s
102
        else if (s instanceof Partition) selection.addAll(s.getParts())
103
}
104

    
105
if (selection.size() == 0) {
106
        println "** $scriptName: please select a Corpus or a Partition in the Corpus view: "+corpusViewSelections
107
        return false
108
}
109
println "WORKING WITH $selection"
110
// BEGINNING OF PARAMETERS
111

    
112
@Field @Option(name="structures", usage="act,scene", widget="String", required=true, def="text,div,p")
113
                def structures
114
@Field @Option(name="structProperties", usage="n,n", widget="String", required=false, def="id,n,n")
115
                def structProperties
116
@Field @Option(name="query", usage="[word!='\\p{P}']", widget="String", required=true, def="[pos=\"NOM.*\"|frpos=\"N.*\"]")
117
                def query
118
@Field @Option(name="wordProperty", usage="word", widget="String", required=true, def="word")
119
                def wordProperty
120
@Field @Option(name="displayIndex", usage="display a hierarchical index", widget="Boolean", required=true, def="true")
121
                def displayIndex
122
@Field @Option(name="Vmax", usage="size of index", widget="Integer", required=false, def="20")
123
                def Vmax
124
// END OF PARAMETERS
125

    
126
// Open the parameters input dialog box
127
if (!ParametersDialog.open(this)) return;
128

    
129
def CQI = CQPSearchEngine.getCqiClient()
130

    
131
def corpusStructs = structures.split(",")                         // ["act", "scene"]
132
structProperties = structProperties.trim()
133

    
134
if (structProperties.size() > 0) {
135
        propParam = true
136
        corpusStructPropNames = structProperties.split(",")        // ["n", "n"]
137
        corpusStructProps = [corpusStructs, corpusStructPropNames].transpose().collectEntries()
138
} else {
139
        propParam = false
140
}
141

    
142
// First define the order theory over corpus structures intervals
143
// by defining a binary comparator that will be used to build the
144
// TreeSet of intervals
145

    
146
// function to print the hierarchical index of a query
147
def print_index = { c, q, p, cut ->
148

    
149
        QueryResult qr = c.query(new Query(q), "RES1", false);
150
        Subcorpus subcorpus = c.createSubcorpus("RES1", qr);
151
        p = subcorpus.getProperty(p)
152
        def tC = subcorpus.getSize()
153
        def matches_target_p = CQI.cpos2Str(p.getQualifiedName(), CQI.dumpSubCorpus(qr.getQualifiedCqpId(), CQI_CONST_FIELD_MATCH, 0, tC-1))
154
        if (cut > 0) {
155
                println matches_target_p.countBy { it }.sort { -it.value }.take(cut)
156
        } else {
157
                println matches_target_p.countBy { it }.sort { -it.value }
158
        }
159
        subcorpus.delete()
160
}
161

    
162
// function to print the statistics of an index of a query
163
def print_freq = { Corpus c, q, p ->
164

    
165
        // appel du moteur
166
        //println "QUERY=$q"
167
        QueryResult qr = c.query(new Query(q), "RES1", false);
168
        Subcorpus subcorpus = c.createSubcorpus("RES1", qr);
169
        p = subcorpus.getProperty(p)
170
        int csize = c.getSize()
171
        if (csize == 0) {
172
                if (displayIndex) {
173
                        println "0\t0\t0\t0\t[]"
174
                } else {
175
                        println "0\t0\t0\t0"
176
                }
177
        } else {
178
                def tC = CQI.subCorpusSize(subcorpus.getQualifiedCqpId())
179
                def matches_target_p = CQI.cpos2Id(p.getQualifiedName(), CQI.dumpSubCorpus(subcorpus.getQualifiedCqpId(), CQI_CONST_FIELD_MATCH, 0, tC-1))
180

    
181
                //println ""
182

    
183
                // afficher les positions de mots du résultat
184
                //println CQI.dumpSubCorpus("${c}:RES1", CQI_CONST_FIELD_MATCH, 0, CQI.subCorpusSize("${c}:RES1")-1)
185

    
186
                // afficher les codes des occurrences de la propriété du résultat
187
                //println matches_target_p
188

    
189
                // afficher l'index hiérarchique des codes du résultat
190
                //println matches_target_p.collect { it }.countBy { it }.sort { -it.value }
191

    
192
                // calculer la fréquence de chaque valeur et ne garder que les fréquences
193
                def index = matches_target_p.collect { it }.countBy { it }
194
                def freqs = index.values()
195

    
196
                // afficher la liste décroissante des fréquences du résultat
197
                //println freqs.sort { -it.value }
198

    
199
                
200
                //def tF = freqs.sum() // control value
201
                def v = freqs.size()
202
                def fmin = freqs.min()
203
                def fmax = freqs.max()
204
                //println sprintf("t %d, v %d, fmin %d, fmax %d", tC, v, fmin, fmax)
205
                print sprintf("%d\t%d\t%d\t%d", tC, v, fmin, fmax)
206
                // afficher les valeurs des occurrences de la propriété du résultat
207
                if (displayIndex) {
208
                        heads = index.sort { -it.value }.take(Vmax).keySet()
209
                        println "\t"+heads.collect { CQI.id2Str(p.getQualifiedName(), it)[0] }
210
                } else {
211
                        println ""
212
                }
213
        }
214
        subcorpus.delete()
215
}
216

    
217
def r = RWorkspace.getRWorkspaceInstance()
218

    
219
/**
220
 * group units by CQP match
221
 *
222
 * units are sorted for faster processing
223
 *
224
 * @param allUnites
225
 * @param matches
226
 * @param strict_inclusion
227
 * @return
228
 */
229
static def inter(def allUnites, def matches) {
230
        //println allUnites.collect() {it -> it[0]}
231
        allUnites = allUnites.sort() { a, b -> a[0] <=> b[0] ?: a[1] <=> b[1] }
232
        //println allUnites.collect() {it -> it[0]}
233
        def unitsSize = allUnites.size()
234
        def iCurrentUnit = 0
235
        def selectedUnits = []
236

    
237
        def matchesSize = matches.size()
238
        def iCurrentMatch = 0
239

    
240

    
241
        while (iCurrentMatch < matchesSize && iCurrentUnit < unitsSize) {
242

    
243
                def unit = allUnites[iCurrentUnit]
244
                def match = matches[iCurrentMatch]
245
                if (unit[1] < match.getStart()) {
246
                                iCurrentUnit++
247
                } else if (unit[0] > match.getEnd()) {
248
                                iCurrentMatch++
249
                } else {
250

    
251
                                if (match.getStart() <= unit[0] && unit[1] <= match.getEnd()) {
252
                                        selectedUnits << unit
253
                                }
254

    
255
                        iCurrentUnit++
256
                }
257
        }
258
        return selectedUnits
259
}
260

    
261
selection.each { corpus ->
262

    
263
        corpusName = corpus.getName()
264
        mainCorpusName = corpus.getMainCorpus().getName()
265
        println "Corpus = "+corpusName
266
        println "Corpus QualifiedCqpId = "+corpus.getCqpId()
267
        println "MainCorpus = "+mainCorpusName
268
        println "Corpus QualifiedCqpId = "+corpus.getMainCorpus().getCqpId()
269
        
270
        def struct_names = (CQI.corpusStructuralAttributes(corpus.getMainCorpus().getCqpId()) as List)
271
        struct_names.removeAll { it.contains('_') }
272
        struct_names=(struct_names-"txmcorpus").grep(corpusStructs)
273
        //println "struct_names = "+struct_names
274

    
275
        if (struct_names.size() == 0) {
276
                println "** Impossible to find the structures (${corpusStructs}), aborting."
277
                return
278
        }
279

    
280
        def level = [:]
281

    
282
        // Now build the TreeSet of corpus structures intervals
283

    
284
        def h = new TreeSet<Struct>()
285

    
286
        struct_names.each {
287
                def matches = []
288
                for (i in 0..CQI.attributeSize("${mainCorpusName}.${it}")-1) {
289
                        (start, end) = CQI.struc2Cpos("${mainCorpusName}.${it}", i)
290
                        matches << [start, end]
291
                        //println sprintf("Adding %s[%d, %d]", it, start, end)
292
                }
293
                def intersection = inter(matches, corpus.getMatches())
294
                for (def item : intersection)
295
                        h.add(new Struct(it, item[0], item[1]))
296
        }
297

    
298
        if (propParam) {
299
                print sprintf("struct\tprop\tstart\tend\tT\tt\tv\tfmin\tfmax")
300
        } else {
301
                print sprintf("struct\tstart\tend\tT\tt\tv\tfmin\tfmax")
302
        }
303

    
304
        if (displayIndex) {
305
                println sprintf("\tindex")
306
        } else {
307
                println ""
308
        }
309

    
310
        def env = System.getenv()
311
        def localPath = env["HOME"]+"/Documents/d3test"
312
        new File(localPath).mkdirs()
313

    
314
        // reset output file
315
        def resultFile = new File(localPath, "desc-partition.html")
316
        def result = new PrintWriter(resultFile)
317
        result.print("")
318
        result.close()
319

    
320
        resultFile << '''\
321
<!DOCTYPE html>
322
<html>
323
  <head>
324
    <meta http-equiv="Content-Type" content="text/html;charset=utf-8" charset="UTF-8"/>
325
    <link type="text/css" rel="stylesheet" href="style.css"/>
326
    <script type="text/javascript" src="d3/d3.v3.js" charset="utf-8"></script>
327
    <script type="text/javascript" src="d3/layout/partition.js" charset="utf-8"></script>
328
    <style type="text/css">
329

330
.chart {
331
  display: block;
332
  margin: auto;
333
  margin-top: 60px;
334
  font-size: 11px;
335
}
336

337
rect {
338
  stroke: #eee;
339
  fill: #aaa;
340
  fill-opacity: .8;
341
}
342

343
rect.parent {
344
  cursor: pointer;
345
  fill: steelblue;
346
}
347

348
text {
349
  pointer-events: none;
350
}
351

352
    </style>
353
  </head>
354
  <body>
355
    <div id="body">
356
      <div id="footer">
357
        Structures hierarchy
358
        <div class="hint">click or shift-alt-click to zoom-in or out</div>
359
      </div>
360
    </div>
361
    <script type="text/javascript">
362

363
var w = 1120,
364
    h = 600,
365
    x = d3.scale.linear().range([0, w]),
366
    y = d3.scale.linear().range([0, h]);
367

368
var vis = d3.select("#body").append("div")
369
    .attr("class", "chart")
370
    .style("width", w + "px")
371
    .style("height", h + "px")
372
  .append("svg:svg")
373
    .attr("width", w)
374
    .attr("height", h);
375

376
var partition = d3.layout.partition()
377
    .value(function(d) { return d.size; }).sort(null);
378

379
var tree = `{'''
380

    
381
        // Now iterate on the TreeSet to get a depth first search on the structure intervals
382

    
383
        def rec_struct_regex = /([^0-9]+)[0-9]+/
384

    
385
        /*
386
         "name": "sha-hamlet",
387
         "children": [
388
         {
389
         "name": "sha-hamcast",
390
         "children": [
391
         {
392
         "name": "sha-ham1",
393
         "children": [
394
         {"name": "sha-ham102", "size": 855},
395
         {"name": "sha-ham103", "size": 464},
396
         {"name": "sha-ham104", "size": 296},
397
         {"name": "sha-ham105", "size": 635}
398
         ]
399
         }
400
         ]
401
         }
402
         ]
403
         }`;
404
         */
405

    
406
        def displayTree = { head ->
407
                if (head) {
408
                        subtree = h.tailSet(head)
409
                        subtree.each { print sprintf("%s[%d, %d], ", it.name, it.start, it.end) }
410
                        println ""
411
                        if (subtree.size() == 0) {
412
                                println sprintf("%s[%d, %d]", head.name, head.start, head.end)
413
                        } else {
414
                                displayTree(subtree)
415
                        }
416
                }
417
        }
418

    
419
        //displayTree(h.first())
420

    
421
        def divPropVals = []
422
        def divLengths = []
423
        def textDivPropVals = []
424
        def textDivLengths = []
425

    
426
        h.each {
427

    
428
                //println sprintf("Displaying %s[%d, %d]", it.name, it.start, it.end)
429
                if (propParam) {
430

    
431
                        def rec_match = (it.name =~ rec_struct_regex)
432
                        if (rec_match.size() == 1) {
433
                                println "Rec struct match = "+rec_match[0][1]
434
                                istruct_name = rec_match[0][1]
435
                        } else {
436
                                //println "Struct match = "+it.name
437
                                istruct_name = it.name
438
                        }
439

    
440
                        def struct_name = "${mainCorpusName}.${istruct_name}_${corpusStructProps[it.name]}"
441
                        def propVal = CQI.struc2Str(struct_name, CQI.cpos2Struc(struct_name, [it.start] as int[]))[0]
442
                        if (it.name == "text") {
443
                                textDivPropVals.push(divPropVals)
444
                                divPropVals = []
445
                                textDivLengths.push(divLengths)
446
                                divLengths = []
447
                        } else if (it.name == "div") {
448
                                divPropVals.push(propVal)
449
                                divLengths.push(it.end-it.start)
450
                        }
451

    
452

    
453
                        print sprintf("%s\t%s\t%d\t%d\t%d\t", it.name, propVal, it.start, it.end, it.end-it.start)
454
                } else {
455
                        def struct_name = "${mainCorpusName}.${it.name}"
456
                        print sprintf("%s\t%d\t%d\t%d\t", it.name, it.start, it.end, it.end-it.start)
457
                }
458
                print_freq(corpus, sprintf("a:%s :: a>=%d & a<=%d", query, it.start, it.end), wordProperty)
459
        }
460

    
461
        textDivPropVals.push(divPropVals)
462
        textDivPropVals.remove(0)
463
        textDivLengths.push(divLengths)
464
        textDivLengths.remove(0)
465

    
466
        println textDivPropVals
467
        println textDivLengths
468

    
469
        def textDivPropVals1 = textDivPropVals[0] as String[]
470
        r.addVectorToWorkspace("textDivPropVals1", textDivPropVals1)
471
        def textDivLengths1 = textDivLengths[0] as int[]
472
        r.addVectorToWorkspace("textDivLengths1", textDivLengths1)
473

    
474
        def PNGFile = File.createTempFile("txm", ".png", new File(Toolbox.getTxmHomePath(), "results"))
475
        def PNGFilePath = PNGFile.getAbsolutePath()
476
        println "PNG file: "+PNGFilePath
477

    
478
        def SVGFile = File.createTempFile("txm", ".svg", new File(Toolbox.getTxmHomePath(), "results"))
479
        def SVGFilePath = SVGFile.getAbsolutePath()
480
        println "SVG file: "+SVGFilePath
481

    
482
        /// BEGINNING OF R SCRIPT
483
        def script ="""
484
df <- data.frame(structure=textDivPropVals1,
485
                 longueur=textDivLengths1)
486
p<-ggplot(data=df, aes(x=structure, y=longueur)) +
487
  geom_bar(stat="identity", fill="steelblue") +
488
  geom_text(aes(label=longueur), vjust=1.6, color="white", size=3.5) +
489
  labs(title="${corpusName}", x="Structure div", y = "Longueur") +
490
  theme_minimal()
491
"""
492
        /// END OF R SCRIPT
493

    
494
        // execute R script
495
        r.eval("library(ggplot2)")
496
        r.eval(script+"ggsave(file=\"${PNGFilePath}\", plot=p)")
497
        r.eval(script+"ggsave(file=\"${SVGFilePath}\", plot=p)")
498

    
499
        //display the SVG results graphic
500
        monitor.syncExec(new Runnable() {
501
                                @Override
502
                                public void run() { try { OpenSVGGraph.OpenSVGFile(SVGFilePath, "Longueur des structures de "+corpusName) } catch(Exception e) {e.printStackTrace()} }
503
                        })
504

    
505
}
506