source: trunk/gui/scripts/field.tcl @ 5582

Last change on this file since 5582 was 5582, checked in by ldelgass, 9 years ago

Add validation checks on valid <elemesize> values for given <elemtype>.

File size: 63.6 KB
Line 
1# -*- mode: tcl; indent-tabs-mode: nil -*-
2# ----------------------------------------------------------------------
3#  COMPONENT: field - extracts data from an XML description of a field
4#
5#  This object represents one field in an XML description of a device.
6#  It simplifies the process of extracting data vectors that represent
7#  the field.
8# ======================================================================
9#  AUTHOR:  Michael McLennan, Purdue University
10#  Copyright (c) 2004-2012  HUBzero Foundation, LLC
11#
12#  See the file "license.terms" for information on usage and
13#  redistribution of this file, and for a DISCLAIMER OF ALL WARRANTIES.
14# ======================================================================
15
16# TODO:
17#    Vector field limits are wrong: need to compute magnitude limits and
18#    component-wise limits.
19
20#
21# Possible field dataset types:
22#
23# 1D Datasets
24#       1D              A field contained in a structure
25# 2D Datasets
26#       vtk             (range of z-axis is zero).
27#       unirect2d       (deprecated)
28#       cloud           (x,y point coordinates) (deprecated)
29#       mesh
30# 3D Datasets
31#       vtk
32#       unirect3d       (deprecated)
33#       cloud           (x,y,z coordinates) (deprecated)
34#       mesh
35#       dx              (FIXME: make dx-to-vtk converter work)
36#       ucd             (AVS ucd format)
37#
38# Viewers:
39#       Format     Dim  Description                     Viewer          Server
40#       1D          1   structure field                 DeviceViewer1D  N/A
41#       vtk         2   vtk file data.                  contour         vtkvis
42#       vtk         3   vtk file data.                  isosurface      vtkvis
43#       mesh        2   points-on-mesh                  heightmap       vtkvis
44#       mesh        3   points-on-mesh                  isosurface      vtkvis
45#       dx          3   DX                              volume          nanovis
46#       unirect2d   2   unirect2d + extents > 1 flow    flow            nanovis
47#       unirect3d   3   unirect3d + extents > 1 flow    flow            nanovis
48#
49# With <views>, can specify which viewer for specific datasets.  So it's OK
50# for the same dataset to be viewed in more than one way.
51#  o Any 2D dataset can be viewed as a contour/heightmap.
52#  o Any 3D dataset can be viewed as a isosurface.
53#  o Any 2D dataset with vector data can be streamlines or flow.
54#  o Any 3D uniform rectilinear dataset can be viewed as a volume.
55#  o Any 3D dataset with vector data can be streamlines or flow.
56#
57# Need <views> to properly do things like qdot: volume with a polydata
58# transparent shell.  The view will combine the two objects <field> and
59# <drawing> (??) into a single viewer.
60#
61package require Itcl
62package require BLT
63
64namespace eval Rappture {
65    # forward declaration
66}
67
68itcl::class Rappture::Field {
69    constructor {xmlobj path} {
70        # defined below
71    }
72    destructor {
73        # defined below
74    }
75    public method blob { cname }
76    public method components {args}
77    public method controls {option args}
78    public method fieldinfo { fname } {
79        lappend out $_fld2Label($fname)
80        lappend out $_fld2Units($fname)
81        lappend out $_fld2Components($fname)
82        return $out
83    }
84    public method fieldlimits {}
85    public method fieldnames { cname } {
86        if { ![info exists _comp2fldName($cname)] } {
87            return ""
88        }
89        return $_comp2fldName($cname)
90    }
91    public method flowhints { cname }
92    public method hints {{key ""}}
93    public method isunirect2d {}
94    public method isunirect3d {}
95    public method isvalid {} {
96        return $_isValid
97    }
98    public method limits {axis}
99    public method mesh {{cname -overall}}
100    public method numComponents {cname}
101    public method style { cname }
102    public method type {}
103    public method valueLimits { cname }
104    public method values { cname }
105    public method viewer {} {
106        return $_viewer
107    }
108    public method vtkdata {cname}
109    public method xErrorValues { cname } {
110    }
111    public method yErrorValues { cname } {
112    }
113
114    protected method Build {}
115    protected method _getValue {expr}
116    protected method GetAssociation { cname }
117    protected method GetTypeAndSize { cname }
118    protected method ReadVtkDataSet { cname contents }
119
120    private method AvsToVtk { cname contents }
121    private method DicomToVtk { cname contents }
122    private method BuildPointsOnMesh { cname }
123    private method InitHints {}
124    private method VerifyVtkDataSet { contents }
125    private method VectorLimits { vector vectorsize {comp -1} }
126    private method VtkDataSetToXy { dataset }
127
128    protected variable _dim 0;          # Dimension of the mesh
129
130    private variable _xmlobj "";        # ref to XML obj with field data
131    private variable _path "";          # Path of this object in the XML
132    private variable _field "";         # This field element as XML obj
133
134    private variable _type "";          # Field type: e.g. file type
135    private variable _hints;            # Hints array
136    private variable _limits;           # maps axis name => {z0 z1} limits
137    private variable _units "";         # system of units for this field
138    private variable _viewer "";        # Hints which viewer to use
139    private variable _isValid 0;        # Indicates if the field contains
140                                        # valid data.
141    private variable _isValidComponent; # Array of valid components found
142    private variable _zmax 0;           # length of the device (1D only)
143
144    private variable _fld2Components;   # field name => number of components
145    private variable _fld2Label;        # field name => label
146    private variable _fld2Units;        # field name => units
147
148    private variable _comp2fldName;     # cname => field names.
149    private variable _comp2type;        # cname => type (e.g. "vectors")
150    private variable _comp2size;        # cname => # of components in element
151    private variable _comp2assoc;       # cname => association (e.g. pointdata)
152    private variable _comp2dims;        # cname => dimensionality
153    private variable _comp2xy;          # cname => x,y vectors
154    private variable _comp2vtk;         # cname => vtk file data
155    private variable _comp2dx;          # cname => OpenDX data
156    private variable _comp2unirect2d;   # cname => unirect2d obj
157    private variable _comp2unirect3d;   # cname => unirect3d obj
158    private variable _comp2style;       # cname => style settings
159    private variable _comp2cntls;       # cname => x,y control points (1D only)
160    private variable _comp2limits;      # cname => List of axis, min/max list
161    private variable _comp2flowhints;   # cname => Rappture::FlowHints obj
162    private variable _comp2mesh;        # list: mesh obj, BLT vector of values
163                                        # valid for cloud,mesh,unirect2d
164    private variable _values "";        # Only for unirect2d - list of values
165
166    private common _alwaysConvertDX 0;  # If set, convert DX and store as VTK,
167                                        # even if viewer is nanovis/flowvis
168    private common _counter 0;          # counter for unique vector names
169}
170
171# ----------------------------------------------------------------------
172# CONSTRUCTOR
173# ----------------------------------------------------------------------
174itcl::body Rappture::Field::constructor {xmlobj path} {
175    package require vtk
176    if {![Rappture::library isvalid $xmlobj]} {
177        error "bad value \"$xmlobj\": should be Rappture::library"
178    }
179    set _xmlobj $xmlobj
180    set _path $path
181    set _field [$xmlobj element -as object $path]
182    set _units [$_field get units]
183
184    set xunits [$xmlobj get units]
185    if {"" == $xunits || "arbitrary" == $xunits} {
186        set xunits "um"
187    }
188
189    # determine the overall size of the device
190    set z0 [set z1 0]
191    foreach elem [$_xmlobj children components] {
192        switch -glob -- $elem {
193            box* {
194                if {![regexp {[0-9]$} $elem]} {
195                    set elem "${elem}0"
196                }
197                set z0 [$_xmlobj get components.$elem.corner0]
198                set z0 [Rappture::Units::convert $z0 \
199                    -context $xunits -to $xunits -units off]
200
201                set z1 [$_xmlobj get components.$elem.corner1]
202                set z1 [Rappture::Units::convert $z1 \
203                    -context $xunits -to $xunits -units off]
204
205                set _limits($elem) [list $z0 $z1]
206            }
207        }
208    }
209    set _zmax $z1
210
211    # build up vectors for various components of the field
212    Build
213    InitHints
214}
215
216# ----------------------------------------------------------------------
217# DESTRUCTOR
218# ----------------------------------------------------------------------
219itcl::body Rappture::Field::destructor {} {
220    itcl::delete object $_field
221    # don't destroy the _xmlobj! we don't own it!
222
223    foreach name [array names _comp2xy] {
224        eval blt::vector destroy $_comp2xy($name)
225    }
226    foreach name [array names _comp2unirect2d] {
227        itcl::delete object $_comp2unirect2d($name)
228    }
229    foreach name [array names _comp2unirect3d] {
230        itcl::delete object $_comp2unirect3d($name)
231    }
232    foreach name [array names _comp2flowhints] {
233        itcl::delete object $_comp2flowhints($name)
234    }
235    foreach name [array names _comp2mesh] {
236        # Data is in the form of a mesh and a vector.
237        foreach { mesh vector } $_comp2mesh($name) break
238        # Release the mesh (may be shared)
239        set class [$mesh info class]
240        ${class}::release $mesh
241        # Destroy the vector
242        blt::vector destroy $vector
243    }
244}
245
246# ----------------------------------------------------------------------
247# USAGE: components ?-name|-dimensions|-style? ?<pattern>?
248#
249# Returns a list of names or types for the various components of
250# this field.  If the optional glob-style <pattern> is specified,
251# then it returns only the components with names matching the pattern.
252# ----------------------------------------------------------------------
253itcl::body Rappture::Field::components {args} {
254    Rappture::getopts args params {
255        flag what -name default
256        flag what -dimensions
257        flag what -style
258        flag what -particles
259        flag what -flow
260        flag what -box
261    }
262
263    set pattern *
264    if {[llength $args] > 0} {
265        set pattern [lindex $args 0]
266        set args [lrange $args 1 end]
267    }
268    if {[llength $args] > 0} {
269        error "wrong # args: should be \"components ?switches? ?pattern?\""
270    }
271
272    # There's only one dimension of the field.  Components can't have
273    # different dimensions in the same field.  They would by definition be
274    # using different meshes and viewers.
275    if { $params(what) == "-dimensions" } {
276        return "${_dim}D"
277    }
278    # BE CAREFUL: return component names in proper order
279    set rlist ""
280    set components {}
281    # First compile a list of valid components that match the pattern
282    foreach cname [$_field children -type component] {
283        if { ![info exists _isValidComponent($cname)] } {
284            continue
285        }
286        if { [string match $pattern $cname] } {
287            lappend components $cname
288        }
289    }
290    # Now handle the tests.
291    switch -- $params(what) {
292        -name {
293            set rlist $components
294        }
295        -style {
296            foreach cname $components {
297                if { [info exists _comp2style($cname)] } {
298                    lappend rlist $_comp2style($cname)
299                }
300            }
301        }
302    }
303    return $rlist
304}
305
306# ----------------------------------------------------------------------
307# USAGE: mesh ?<name>?
308#
309# For 1D data (curve), returns a BLT vector of x values for the field
310# component <name>.  Otherwise, this method is unused
311# ----------------------------------------------------------------------
312itcl::body Rappture::Field::mesh {{cname -overall}} {
313    if {$cname == "-overall" || $cname == "component0"} {
314        set cname [lindex [components -name] 0]
315    }
316    if {[info exists _comp2xy($cname)]} {
317        return [lindex $_comp2xy($cname) 0]  ;# return xv
318    }
319    if {[info exists _comp2vtk($cname)]} {
320        # FIXME: extract mesh from VTK file data.
321        error "method \"mesh\" is not implemented for VTK file data"
322    }
323    if {[info exists _comp2dx($cname)]} {
324        error "method \"mesh\" is not implemented for DX file data"
325    }
326    if {[info exists _comp2mesh($cname)]} {
327        # FIXME: This only works for cloud
328        set mesh [lindex $_comp2mesh($cname) 0]
329        return [$mesh points]
330    }
331    if {[info exists _comp2unirect2d($cname)]} {
332        # FIXME: unirect2d mesh is a list: xMin xMax xNum yMin yMax yNum
333        return [$_comp2unirect2d($cname) mesh]
334    }
335    if {[info exists _comp2unirect3d($cname)]} {
336        # This returns a list of x,y,z points
337        return [$_comp2unirect3d($cname) mesh]
338    }
339    error "can't get field mesh: Unknown component \"$cname\": should be one of [join [lsort [array names _comp2dims]] {, }]"
340}
341
342# ----------------------------------------------------------------------
343# USAGE: values ?<name>?
344#
345# For 1D data (curve), returns a BLT vector of field values (y coords)
346# for the field component <name>.  Otherwise, this method is unused
347# ----------------------------------------------------------------------
348itcl::body Rappture::Field::values {cname} {
349    if {$cname == "component0"} {
350        set cname "component"
351    }
352    if {[info exists _comp2xy($cname)]} {
353        return [lindex $_comp2xy($cname) 1]  ;# return yv
354    }
355    if { [info exists _comp2vtk($cname)] } {
356        # FIXME: extract the values from the VTK file data
357        error "method \"values\" is not implemented for VTK file data"
358    }
359    if {[info exists _comp2dx($cname)]} {
360        error "method \"values\" is not implemented for DX file data"
361    }
362    if { [info exists _comp2mesh($cname)] } {
363        set vector [lindex $_comp2mesh($cname) 1]
364        return [$vector range 0 end]
365    }
366    if {[info exists _comp2unirect2d($cname)]} {
367        return $_values
368    }
369    if {[info exists _comp2unirect3d($cname)]} {
370        return [$_comp2unirect3d($cname) values]
371    }
372    error "can't get field values. Unknown component \"$cname\": should be one of [join [lsort [array names _comp2dims]] {, }]"
373}
374
375# ----------------------------------------------------------------------
376# USAGE: blob ?<name>?
377#
378# Returns a string representing the blob of data for the mesh and values.
379# ----------------------------------------------------------------------
380itcl::body Rappture::Field::blob {cname} {
381    if {$cname == "component0"} {
382        set cname "component"
383    }
384    if {[info exists _comp2dx($cname)]} {
385        return $_comp2dx($cname)  ;# return gzipped, base64-encoded DX data
386    }
387    if {[info exists _comp2unirect2d($cname)]} {
388        set blob [$_comp2unirect2d($cname) blob]
389        lappend blob "values" $_values
390        return $blob
391    }
392    if {[info exists _comp2unirect3d($cname)]} {
393        return [$_comp2unirect3d($cname) blob]
394    }
395    if { [info exists _comp2vtk($cname)] } {
396        error "blob not implemented for VTK file data"
397    }
398    if {[info exists _comp2xy($cname)]} {
399        error "blob not implemented for XY data"
400    }
401    error "can't get field blob: Unknown component \"$cname\": should be one of [join [lsort [array names _comp2dims]] {, }]"
402}
403
404# ----------------------------------------------------------------------
405# USAGE: valueLimits <cname>
406#
407# Returns an array for the requested component with a list {min max}
408# representing the limits for each axis.
409# ----------------------------------------------------------------------
410itcl::body Rappture::Field::valueLimits { cname } {
411    if { [info exists _comp2limits($cname)] } {
412        return $_comp2limits($cname)
413    }
414    return ""
415}
416
417# ----------------------------------------------------------------------
418# USAGE: limits <axis>
419#
420# Returns a list {min max} representing the limits for the specified
421# axis.
422# ----------------------------------------------------------------------
423itcl::body Rappture::Field::limits {which} {
424    set min ""
425    set max ""
426
427    foreach cname [array names _comp2dims] {
428        switch -- $_comp2dims($cname) {
429            1D {
430                switch -- $which {
431                    x - xlin {
432                        set pos 0; set log 0; set axis x
433                    }
434                    xlog {
435                        set pos 0; set log 1; set axis x
436                    }
437                    y - ylin - v - vlin {
438                        set pos 1; set log 0; set axis y
439                    }
440                    ylog - vlog {
441                        set pos 1; set log 1; set axis y
442                    }
443                    default {
444                        error "bad axis \"$which\": should be x, xlin, xlog, y, ylin, ylog, v, vlin, vlog"
445                    }
446                }
447
448                set vname [lindex $_comp2xy($cname) $pos]
449
450                if {$log} {
451                    blt::vector tmp zero
452                    # on a log scale, use abs value and ignore zeros
453                    $vname dup tmp
454                    $vname dup zero
455                    zero expr {tmp == 0}            ;# find the zeros
456                    tmp expr {abs(tmp)}             ;# get the abs value
457                    tmp expr {tmp + zero*max(tmp)}  ;# replace 0s with abs max
458                    set axisMin [blt::vector expr min(tmp)]
459                    set axisMax [blt::vector expr max(tmp)]
460                    blt::vector destroy tmp zero
461                } else {
462                    set axisMin [$vname min]
463                    set axisMax [$vname max]
464                }
465
466                if {"" == $min} {
467                    set min $axisMin
468                } elseif {$axisMin < $min} {
469                    set min $axisMin
470                }
471                if {"" == $max} {
472                    set max $axisMax
473                } elseif {$axisMax > $max} {
474                    set max $axisMax
475                }
476            }
477            default {
478                if {[info exists _comp2limits($cname)]} {
479                    array set limits $_comp2limits($cname)
480                    switch -- $which {
481                        x - xlin - xlog {
482                            set axis x
483                            foreach {axisMin axisMax} $limits(x) break
484                        }
485                        y - ylin - ylog {
486                            set axis y
487                            foreach {axisMin axisMax} $limits(y) break
488                        }
489                        z - zlin - zlog {
490                            set axis z
491                            foreach {axisMin axisMax} $limits(z) break
492                        }
493                        v - vlin - vlog {
494                            set axis v
495                            foreach {axisMin axisMax} $limits(v) break
496                        }
497                        default {
498                            if { ![info exists limits($which)] } {
499                                error "limits: unknown axis \"$which\""
500                            }
501                            set axis v
502                            foreach {axisMin axisMax} $limits($which) break
503                        }
504                    }
505                } else {
506                    set axisMin 0  ;# HACK ALERT! must be OpenDX data
507                    set axisMax 1
508                    set axis v
509                }
510            }
511        }
512        if { "" == $min || $axisMin < $min } {
513            set min $axisMin
514        }
515        if { "" == $max || $axisMax > $max } {
516            set max $axisMax
517        }
518    }
519    set val [$_field get "${axis}axis.min"]
520    if {"" != $val && "" != $min} {
521        if {$val > $min} {
522            # tool specified this min -- don't go any lower
523            set min $val
524        }
525    }
526    set val [$_field get "${axis}axis.max"]
527    if {"" != $val && "" != $max} {
528        if {$val < $max} {
529            # tool specified this max -- don't go any higher
530            set max $val
531        }
532    }
533    return [list $min $max]
534}
535
536# ----------------------------------------------------------------------
537# USAGE: fieldlimits
538#
539# Returns a list {min max} representing the limits for the specified
540# axis.
541# ----------------------------------------------------------------------
542itcl::body Rappture::Field::fieldlimits {} {
543    foreach cname [array names _comp2limits] {
544        array set limits $_comp2limits($cname)
545        foreach fname [fieldnames $cname] {
546            if { ![info exists limits($fname)] } {
547                puts stderr "ERROR: field \"$fname\" unknown in \"$cname\""
548                continue
549            }
550            foreach {min max} $limits($fname) break
551            if { ![info exists overall($fname)] } {
552                set overall($fname) $limits($fname)
553                continue
554            }
555            foreach {omin omax} $overall($fname) break
556            if { $min < $omin } {
557                set omin $min
558            }
559            if { $max > $omax } {
560                set omax $max
561            }
562            set overall($fname) [list $min $max]
563        }
564    }
565    if { [info exists overall] } {
566        return [array get overall]
567    }
568    return ""
569}
570
571# ----------------------------------------------------------------------
572# USAGE: controls get ?<name>?
573# USAGE: controls validate <path> <value>
574# USAGE: controls put <path> <value>
575#
576# Returns a list {path1 x1 y1 val1  path2 x2 y2 val2 ...} representing
577# control points for the specified field component <name>.
578# ----------------------------------------------------------------------
579itcl::body Rappture::Field::controls {option args} {
580    switch -- $option {
581        get {
582            set cname [lindex $args 0]
583            if {[info exists _comp2cntls($cname)]} {
584                return $_comp2cntls($cname)
585            }
586            return ""
587        }
588        validate {
589            set path [lindex $args 0]
590            set value [lindex $args 1]
591            set units [$_xmlobj get $path.units]
592
593            if {"" != $units} {
594                set nv [Rappture::Units::convert \
595                    $value -context $units -to $units -units off]
596            } else {
597                set nv $value
598            }
599            if {![string is double $nv]
600                  || [regexp -nocase {^(inf|nan)$} $nv]} {
601                error "Value out of range"
602            }
603
604            set rawmin [$_xmlobj get $path.min]
605            if {"" != $rawmin} {
606                set minv $rawmin
607                if {"" != $units} {
608                    set minv [Rappture::Units::convert \
609                        $minv -context $units -to $units -units off]
610                    set nv [Rappture::Units::convert \
611                        $value -context $units -to $units -units off]
612                }
613                # fix for the case when the user tries to
614                # compare values like minv=-500 nv=-0600
615                set nv [format "%g" $nv]
616                set minv [format "%g" $minv]
617
618                if {$nv < $minv} {
619                    error "Minimum value allowed here is $rawmin"
620                }
621            }
622
623            set rawmax [$_xmlobj get $path.max]
624            if {"" != $rawmax} {
625                set maxv $rawmax
626                if {"" != $units} {
627                    set maxv [Rappture::Units::convert \
628                        $maxv -context $units -to $units -units off]
629                    set nv [Rappture::Units::convert \
630                        $value -context $units -to $units -units off]
631                }
632                # fix for the case when the user tries to
633                # compare values like maxv=-500 nv=-0600
634                set nv [format "%g" $nv]
635                set maxv [format "%g" $maxv]
636
637                if {$nv > $maxv} {
638                    error "Maximum value allowed here is $rawmax"
639                }
640            }
641
642            return "ok"
643        }
644        put {
645            set path [lindex $args 0]
646            set value [lindex $args 1]
647            $_xmlobj put $path.current $value
648            Build
649        }
650        default {
651            error "bad field controls option \"$option\": should be get or put"
652        }
653    }
654}
655
656# ----------------------------------------------------------------------
657# USAGE: hints ?<keyword>?
658#
659# Returns a list of key/value pairs for various hints about plotting
660# this field.  If a particular <keyword> is specified, then it returns
661# the hint for that <keyword>, if it exists.
662# ----------------------------------------------------------------------
663itcl::body Rappture::Field::hints {{keyword ""}} {
664    if { $keyword != "" } {
665        if {[info exists _hints($keyword)]} {
666            return $_hints($keyword)
667        }
668        return ""
669    }
670    return [array get _hints]
671}
672
673
674# ----------------------------------------------------------------------
675# USAGE: InitHints
676#
677# Returns a list of key/value pairs for various hints about plotting
678# this field.  If a particular <keyword> is specified, then it returns
679# the hint for that <keyword>, if it exists.
680# ----------------------------------------------------------------------
681itcl::body Rappture::Field::InitHints {} {
682    foreach {key path} {
683        camera          camera.position
684        color           about.color
685        default         about.default
686        group           about.group
687        label           about.label
688        scale           about.scale
689        seeds           about.seeds
690        style           about.style
691        type            about.type
692        xlabel          about.xaxis.label
693        ylabel          about.yaxis.label
694        zlabel          about.zaxis.label
695        xunits          about.xaxis.units
696        yunits          about.yaxis.units
697        zunits          about.zaxis.units
698        units           units
699        updir           updir
700        vectors         about.vectors
701    } {
702        set str [$_field get $path]
703        if { "" != $str } {
704            set _hints($key) $str
705        }
706    }
707    foreach cname [components] {
708        if { ![info exists _comp2mesh($cname)] } {
709            continue
710        }
711        set mesh [lindex $_comp2mesh($cname) 0]
712        foreach axis {x y z} {
713            if { ![info exists _hints(${axis}units)] } {
714                set _hints(${axis}units) [$mesh units $axis]
715            }
716            if { ![info exists _hints(${axis}label)] } {
717                set _hints(${axis}label) [$mesh label $axis]
718            }
719        }
720    }
721    foreach {key path} {
722        toolid          tool.id
723        toolname        tool.name
724        toolcommand     tool.execute
725        tooltitle       tool.title
726        toolrevision    tool.version.application.revision
727    } {
728        set str [$_xmlobj get $path]
729        if { "" != $str } {
730            set _hints($key) $str
731        }
732    }
733    # Set toolip and path hints
734    set _hints(path) $_path
735    if { [info exists _hints(group)] && [info exists _hints(label)] } {
736        # pop-up help for each curve
737        set _hints(tooltip) $_hints(label)
738    }
739}
740
741# ----------------------------------------------------------------------
742# USAGE: Build
743#
744# Used internally to build up the vector representation for the
745# field when the object is first constructed, or whenever the field
746# data changes.  Discards any existing vectors and builds everything
747# from scratch.
748# ----------------------------------------------------------------------
749itcl::body Rappture::Field::Build {} {
750
751    # Discard any existing data
752    foreach name [array names _comp2xy] {
753        eval blt::vector destroy $_comp2xy($name)
754    }
755    array unset _comp2vtk
756    foreach name [array names _comp2unirect2d] {
757        eval itcl::delete object $_comp2unirect2d($name)
758    }
759    foreach name [array names _comp2unirect3d] {
760        eval itcl::delete object $_comp2unirect3d($name)
761    }
762    catch {unset _comp2xy}
763    catch {unset _comp2dx}
764    catch {unset _comp2dims}
765    catch {unset _comp2style}
766    array unset _comp2unirect2d
767    array unset _comp2unirect3d
768    array unset _dataobj2type
769    #
770    # Scan through the components of the field and create
771    # vectors for each part.
772    #
773    array unset _isValidComponent
774    foreach cname [$_field children -type component] {
775        set type ""
776        if { ([$_field element $cname.constant] != "" &&
777              [$_field element $cname.domain] != "") ||
778              [$_field element $cname.xy] != "" } {
779            set type "1D"
780        } elseif { [$_field element $cname.mesh] != "" &&
781                   [$_field element $cname.values] != ""} {
782            set type "points-on-mesh"
783        } elseif { [$_field element $cname.vtk] != ""} {
784            set type "vtk"
785        } elseif {[$_field element $cname.opendx] != ""} {
786            set type "opendx"
787        } elseif {[$_field element $cname.dx] != ""} {
788            set type "dx"
789        } elseif {[$_field element $cname.ucd] != ""} {
790            set type "ucd"
791        } elseif {[$_field element $cname.dicom] != ""} {
792            set type "dicom"
793        }
794        set _comp2style($cname) ""
795        if { $type == "" } {
796            puts stderr "WARNING: Ignoring field component \"$_path.$cname\": no data found."
797            continue
798        }
799        set _type $type
800
801        GetTypeAndSize $cname
802        GetAssociation $cname
803
804        if { $_comp2size($cname) < 1 ||
805             $_comp2size($cname) > 9 } {
806            puts stderr "ERROR: Invalid <elemsize>: $_comp2size($cname)"
807            continue
808        }
809        # Some types have restrictions on number of components
810        if { $_comp2type($cname) == "vectors" &&
811             $_comp2size($cname) != 3 } {
812            puts stderr "ERROR: vectors <elemtype> must have <elemsize> of 3"
813            continue
814        }
815        if { $_comp2type($cname) == "normals" &&
816             $_comp2size($cname) != 3 } {
817            puts stderr "ERROR: normals <elemtype> must have <elemsize> of 3"
818            continue
819        }
820        if { $_comp2type($cname) == "tcoords" &&
821             $_comp2size($cname) > 3 } {
822            puts stderr "ERROR: tcoords <elemtype> must have <elemsize> <= 3"
823            continue
824        }
825        if { $_comp2type($cname) == "scalars" &&
826             $_comp2size($cname) > 4 } {
827            puts stderr "ERROR: scalars <elemtype> must have <elemsize> <= 4"
828            continue
829        }
830        if { $_comp2type($cname) == "colorscalars" &&
831             $_comp2size($cname) > 4 } {
832            puts stderr "ERROR: colorscalars <elemtype> must have <elemsize> <= 4"
833            continue
834        }
835        if { $_comp2type($cname) == "tensors" &&
836             $_comp2size($cname) != 9 } {
837            puts stderr "ERROR: tensors <elemtype> must have <elemsize> of 9"
838            continue
839        }
840
841        if { [$_field element $cname.flow] != "" } {
842            set haveFlow 1
843        } else {
844            set haveFlow 0
845        }
846        set viewer [$_field get "about.view"]
847        if { $viewer != "" } {
848            set _viewer $viewer
849        }
850        if { $_viewer == "" } {
851            if { $_comp2size($cname) > 1 && ! $haveFlow } {
852                set _viewer "glyphs"
853            } elseif { $_comp2size($cname) > 1 && $haveFlow } {
854                set _viewer "flowvis"
855            }
856        }
857
858        if {$type == "1D"} {
859            #
860            # 1D data can be represented as 2 BLT vectors,
861            # one for x and the other for y.
862            #
863            set xv ""
864            set yv ""
865            set _dim 1
866            set val [$_field get $cname.constant]
867            if {$val != ""} {
868                set domain [$_field get $cname.domain]
869                if {$domain == "" || ![info exists _limits($domain)]} {
870                    set z0 0
871                    set z1 $_zmax
872                } else {
873                    foreach {z0 z1} $_limits($domain) { break }
874                }
875                set xv [blt::vector create x$_counter]
876                $xv append $z0 $z1
877
878                foreach {val pcomp} [_getValue $val] break
879                set yv [blt::vector create y$_counter]
880                $yv append $val $val
881
882                if {$pcomp != ""} {
883                    set zm [expr {0.5*($z0+$z1)}]
884                    set _comp2cntls($cname) \
885                        [list $pcomp $zm $val "$val$_units"]
886                }
887            } else {
888                set xydata [$_field get $cname.xy]
889                if {"" != $xydata} {
890                    set xv [blt::vector create x$_counter]
891                    set yv [blt::vector create y$_counter]
892                    set tmp [blt::vector create \#auto]
893                    $tmp set $xydata
894                    $tmp split $xv $yv
895                    blt::vector destroy $tmp
896                }
897            }
898
899            if {$xv != "" && $yv != ""} {
900                # sort x-coords in increasing order
901                $xv sort $yv
902                set _dim 1
903                set _comp2dims($cname) "1D"
904                set _comp2xy($cname) [list $xv $yv]
905                incr _counter
906            }
907        } elseif {$type == "points-on-mesh"} {
908            if { ![BuildPointsOnMesh $cname] } {
909                continue;               # Ignore this component
910            }
911        } elseif {$type == "vtk"} {
912            set contents [$_field get $cname.vtk]
913            if { $contents == "" } {
914                puts stderr "WARNING: No data for \"$_path.$cname.vtk\""
915                continue;               # Ignore this component
916            }
917            ReadVtkDataSet $cname $contents
918            set _comp2vtk($cname) $contents
919            set _comp2style($cname) [$_field get $cname.style]
920            incr _counter
921        } elseif {$type == "dx" || $type == "opendx" } {
922            #
923            # Extract gzipped, base64-encoded OpenDX data
924            #
925            if { $_viewer == "" } {
926                if {$haveFlow} {
927                    set _viewer "flowvis"
928                } else {
929                    global env
930                    if { [info exists env(VTKVOLUME)] } {
931                        set _viewer "vtkvolume"
932                    }
933                    set _viewer "nanovis"
934                }
935            }
936            set data [$_field get -decode no $cname.$type]
937            set contents [Rappture::encoding::decode -as zb64 $data]
938            if { $contents == "" } {
939                puts stderr "WARNING: No data for \"$_path.$cname.$type\""
940                continue;               # Ignore this component
941            }
942            if 0 {
943                set f [open /tmp/$_path.$cname.dx "w"]
944                puts -nonewline $f $contents
945                close $f
946            }
947            # Convert to VTK
948            if { [catch { Rappture::DxToVtk $contents } vtkdata] == 0 } {
949                unset contents
950                # Read back VTK: this will set the field limits and the mesh
951                # dimensions based on the bounds (sets _dim).  We rely on this
952                # conversion for limits even if we send DX data to nanovis.
953                ReadVtkDataSet $cname $vtkdata
954                if 0 {
955                    set f [open /tmp/$_path.$cname.vtk "w"]
956                    puts -nonewline $f $vtkdata
957                    close $f
958                }
959            } else {
960                unset contents
961                # vtkdata variable holds error message
962                puts stderr "Can't parse DX data\n$vtkdata"
963                continue;               # Ignore this component
964            }
965            if { $_alwaysConvertDX ||
966                 ($_viewer != "nanovis" && $_viewer != "flowvis") } {
967                set _type "vtk"
968                set _comp2vtk($cname) $vtkdata
969            } else {
970                set _type "dx"
971                set _comp2dx($cname) $data
972            }
973            unset data
974            unset vtkdata
975            set _comp2style($cname) [$_field get $cname.style]
976            if {$haveFlow} {
977                set _comp2flowhints($cname) \
978                    [Rappture::FlowHints ::\#auto $_field $cname $_units]
979            }
980            incr _counter
981        } elseif { $type == "dicom"} {
982            set contents [$_field get $cname.dicom]
983            if { $contents == "" } {
984                continue;               # Ignore this component
985            }
986            set vtkdata [DicomToVtk $cname $contents]
987            if { $_viewer == "" } {
988                set _viewer [expr {($_dim == 3) ? "vtkvolume" : "vtkimage"}]
989            }
990            set _comp2vtk($cname) $vtkdata
991            set _comp2style($cname) [$_field get $cname.style]
992            incr _counter
993        } elseif { $type == "ucd"} {
994            set contents [$_field get $cname.ucd]
995            if { $contents == "" } {
996                continue;               # Ignore this component
997            }
998            set vtkdata [AvsToVtk $cname $contents]
999            ReadVtkDataSet $cname $vtkdata
1000            set _comp2vtk($cname) $vtkdata
1001            set _comp2style($cname) [$_field get $cname.style]
1002            incr _counter
1003        }
1004        set _isValidComponent($cname) 1
1005        #puts stderr "Field $cname type is: $_type"
1006    }
1007    if { [array size _isValidComponent] == 0 } {
1008        puts stderr "ERROR: All components of field \"$_path\" are invalid."
1009        return 0
1010    }
1011    # Sanity check.  Verify that all components of the field have the same
1012    # dimension.
1013    set dim ""
1014    foreach cname [array names _comp2dims] {
1015        if { $dim == "" } {
1016            set dim $_comp2dims($cname)
1017            continue
1018        }
1019        if { $dim != $_comp2dims($cname) } {
1020            puts stderr "WARNING: A field can't have components of different dimensions: [join [array get _comp2dims] ,]"
1021            return 0
1022        }
1023    }
1024
1025    # FIXME: about.scalars and about.vectors are temporary.  With views
1026    #        the label and units for each field will be specified there.
1027    #
1028    # FIXME: Test that every <field><component> has the same field names,
1029    #        units, components.
1030    #
1031    # Override what we found in the VTK file with names that the user
1032    # selected.  We override the field label and units.
1033    foreach { fname label units } [$_field get about.scalars] {
1034        if { ![info exists _fld2Name($fname)] } {
1035            set _fld2Name($fname) $fname
1036            set _fld2Components($fname) 1
1037        }
1038        set _fld2Label($fname) $label
1039        set _fld2Units($fname) $units
1040    }
1041    foreach { fname label units } [$_field get about.vectors] {
1042        if { ![info exists _fld2Name($fname)] } {
1043            set _fld2Name($fname) $fname
1044            # We're just marking the field as vector (> 1) for now.
1045            set _fld2Components($fname) 3
1046        }
1047        set _fld2Label($fname) $label
1048        set _fld2Units($fname) $units
1049    }
1050    set _isValid 1
1051    return 1
1052}
1053
1054# ----------------------------------------------------------------------
1055# USAGE: _getValue <expr>
1056#
1057# Used internally to get the value for an expression <expr>.  Returns
1058# a list of the form {val parameterPath}, where val is the numeric
1059# value of the expression, and parameterPath is the XML path to the
1060# parameter representing the value, or "" if the <expr> does not
1061# depend on any parameters.
1062# ----------------------------------------------------------------------
1063itcl::body Rappture::Field::_getValue {expr} {
1064    #
1065    # First, look for the expression among the <parameter>'s
1066    # associated with the device.
1067    #
1068    set found 0
1069    foreach pcomp [$_xmlobj children parameters] {
1070        set id [$_xmlobj element -as id parameters.$pcomp]
1071        if {[string equal $id $expr]} {
1072            set val [$_xmlobj get parameters.$pcomp.current]
1073            if {"" == $val} {
1074                set val [$_xmlobj get parameters.$pcomp.default]
1075            }
1076            if {"" != $val} {
1077                set expr $val
1078                set found 1
1079                break
1080            }
1081        }
1082    }
1083    if {$found} {
1084        set pcomp "parameters.$pcomp"
1085    } else {
1086        set pcomp ""
1087    }
1088
1089    if {$_units != ""} {
1090        set expr [Rappture::Units::convert $expr \
1091            -context $_units -to $_units -units off]
1092    }
1093
1094    return [list $expr $pcomp]
1095}
1096
1097#
1098# isunirect2d  --
1099#
1100# Returns if the field is a unirect2d object.
1101#
1102itcl::body Rappture::Field::isunirect2d { } {
1103    return [expr [array size _comp2unirect2d] > 0]
1104}
1105
1106#
1107# isunirect3d  --
1108#
1109# Returns if the field is a unirect3d object.
1110#
1111itcl::body Rappture::Field::isunirect3d { } {
1112    return [expr [array size _comp2unirect3d] > 0]
1113}
1114
1115#
1116# flowhints  --
1117#
1118# Returns the hints associated with a flow vector field.
1119#
1120itcl::body Rappture::Field::flowhints { cname } {
1121    if { [info exists _comp2flowhints($cname)] } {
1122        return $_comp2flowhints($cname)
1123    }
1124    return ""
1125}
1126
1127#
1128# style  --
1129#
1130# Returns the style associated with a component of the field.
1131#
1132itcl::body Rappture::Field::style { cname } {
1133    if { [info exists _comp2style($cname)] } {
1134        return $_comp2style($cname)
1135    }
1136    return ""
1137}
1138
1139#
1140# type  --
1141#
1142# Returns the data storage type of the field.
1143#
1144# FIXME: What are the valid types?
1145#
1146itcl::body Rappture::Field::type {} {
1147    return $_type
1148}
1149
1150#
1151# numComponents --
1152#
1153# Returns the number of components in the field component.
1154#
1155itcl::body Rappture::Field::numComponents {cname} {
1156    set name $cname
1157    regsub -all { } $name {_} name
1158    if {[info exists _fld2Components($name)] } {
1159        return $_fld2Components($name)
1160    }
1161    return 1;                           # Default to scalar.
1162}
1163
1164itcl::body Rappture::Field::VerifyVtkDataSet { contents } {
1165    package require vtk
1166
1167    set reader $this-datasetreader
1168    vtkDataSetReader $reader
1169
1170    # Write the contents to a file just in case it's binary.
1171    set tmpfile file[pid].vtk
1172    set f [open "$tmpfile" "w"]
1173    fconfigure $f -translation binary -encoding binary
1174    puts $f $contents
1175    close $f
1176
1177    $reader SetFileName $tmpfile
1178    $reader ReadAllNormalsOn
1179    $reader ReadAllTCoordsOn
1180    $reader ReadAllScalarsOn
1181    $reader ReadAllColorScalarsOn
1182    $reader ReadAllVectorsOn
1183    $reader ReadAllTensorsOn
1184    $reader ReadAllFieldsOn
1185    $reader Update
1186    file delete $tmpfile
1187
1188    set dataset [$reader GetOutput]
1189    set dataAttrs [$dataset GetPointData]
1190    if { $dataAttrs == ""} {
1191        puts stderr "WARNING: No point data found in \"$_path\""
1192        rename $reader ""
1193        return 0
1194    }
1195    rename $reader ""
1196}
1197
1198itcl::body Rappture::Field::ReadVtkDataSet { cname contents } {
1199    package require vtk
1200
1201    set reader $this-datasetreader
1202    vtkDataSetReader $reader
1203
1204    # Write the contents to a file just in case it's binary.
1205    set tmpfile file[pid].vtk
1206    set f [open "$tmpfile" "w"]
1207    fconfigure $f -translation binary -encoding binary
1208    puts $f $contents
1209    close $f
1210
1211    $reader SetFileName $tmpfile
1212    $reader ReadAllNormalsOn
1213    $reader ReadAllTCoordsOn
1214    $reader ReadAllScalarsOn
1215    $reader ReadAllColorScalarsOn
1216    $reader ReadAllVectorsOn
1217    $reader ReadAllTensorsOn
1218    $reader ReadAllFieldsOn
1219    $reader Update
1220    file delete $tmpfile
1221
1222    set dataset [$reader GetOutput]
1223    set limits {}
1224    foreach {xmin xmax ymin ymax zmin zmax} [$dataset GetBounds] break
1225    # Figure out the dimension of the mesh from the bounds.
1226    set _dim 0
1227    if { $xmax > $xmin } {
1228        incr _dim
1229    }
1230    if { $ymax > $ymin } {
1231        incr _dim
1232    }
1233    if { $zmax > $zmin } {
1234        incr _dim
1235    }
1236    if { $_viewer == "" } {
1237        if { $_dim == 2 } {
1238            set _viewer contour
1239        } else {
1240            set _viewer isosurface
1241        }
1242    }
1243    set _comp2dims($cname) ${_dim}D
1244    lappend limits x [list $xmin $xmax]
1245    lappend limits y [list $ymin $ymax]
1246    lappend limits z [list $zmin $zmax]
1247    set dataAttrs [$dataset GetPointData]
1248    if { $dataAttrs == ""} {
1249        puts stderr "WARNING: No point data found in \"$_path\""
1250        rename $reader ""
1251        return 0
1252    }
1253    set vmin 0
1254    set vmax 1
1255    set numArrays [$dataAttrs GetNumberOfArrays]
1256    if { $numArrays > 0 } {
1257        for {set i 0} {$i < [$dataAttrs GetNumberOfArrays] } {incr i} {
1258            set array [$dataAttrs GetArray $i]
1259            set fname  [$dataAttrs GetArrayName $i]
1260            foreach {min max} [$array GetRange -1] break
1261            if {$i == 0} {
1262                set vmin $min
1263                set vmax $max
1264            }
1265            lappend limits $fname [list $min $max]
1266            set _fld2Units($fname) ""
1267            set _fld2Label($fname) $fname
1268            # Let the VTK file override the <type> designated.
1269            set _fld2Components($fname) [$array GetNumberOfComponents]
1270            lappend _comp2fldName($cname) $fname
1271        }
1272    }
1273
1274    lappend limits v [list $vmin $vmax]
1275    set _comp2limits($cname) $limits
1276
1277    rename $reader ""
1278}
1279
1280#
1281# VtkDataSetToXy --
1282#
1283#        Attempt to convert 0 or 1 dimensional VTK DataSet to XY data (curve).
1284#
1285itcl::body Rappture::Field::VtkDataSetToXy { dataset } {
1286    foreach {xmin xmax ymin ymax zmin zmax} [$dataset GetBounds] break
1287    # Only X can have non-zero range.  X can have zero range if there is
1288    # only one point
1289    if { $ymax > $ymin } {
1290        return 0
1291    }
1292    if { $zmax > $zmin } {
1293        return 0
1294    }
1295    set numPoints [$dataset GetNumberOfPoints]
1296    set dataAttrs [$dataset GetPointData]
1297    if { $dataAttrs == ""} {
1298        puts stderr "WARNING: No point data found"
1299        return 0
1300    }
1301    set array [$dataAttrs GetScalars]
1302    if { $array == ""} {
1303        puts stderr "WARNING: No scalar point data found"
1304        return 0
1305    }
1306    # Multi-component scalars (e.g. color scalars) are not supported
1307    if { [$array GetNumberOfComponents] != 1 } {
1308        return 0
1309    }
1310    set numTuples [$array GetNumberOfTuples]
1311    set xv [blt::vector create \#auto]
1312    for { set i 0 } { $i < $numPoints } { incr i } {
1313        set point [$dataset GetPoint $i]
1314        $xv append [lindex $point 0]
1315    }
1316    set yv [blt::vector create \#auto]
1317    for { set i 0 } { $i < $numTuples } { incr i } {
1318        $yv append [$array GetComponent $i 0]
1319    }
1320    $xv sort $yv
1321    set _comp2xy($cname) [list $xv $yv]
1322    return 1
1323}
1324
1325#
1326# vtkdata --
1327#
1328#        Returns a string representing the mesh and field data for a specific
1329#        component in the legacy VTK file format.
1330#
1331itcl::body Rappture::Field::vtkdata {cname} {
1332    if {$cname == "component0"} {
1333        set cname "component"
1334    }
1335    # VTK file data:
1336    if { [info exists _comp2vtk($cname)] } {
1337        return $_comp2vtk($cname)
1338    }
1339    # DX: Convert DX to VTK
1340    if {[info exists _comp2dx($cname)]} {
1341        set data $_comp2dx($cname)
1342        set data [Rappture::encoding::decode -as zb64 $data]
1343        return [Rappture::DxToVtk $data]
1344    }
1345    # unirect2d (deprecated)
1346    # This can be removed when the nanovis server with support for loading VTK
1347    # vector data is released
1348    if {[info exists _comp2unirect2d($cname)]} {
1349        set label $cname
1350        regsub -all { } $label {_} label
1351        set elemSize [numComponents $cname]
1352        set numValues [$_comp2unirect2d($cname) numpoints]
1353        append out "# vtk DataFile Version 3.0\n"
1354        append out "[hints label]\n"
1355        append out "ASCII\n"
1356        append out [$_comp2unirect2d($cname) vtkdata]
1357        append out "POINT_DATA $numValues\n"
1358        if {$elemSize == 3} {
1359            append out "VECTORS $label double\n"
1360        } else {
1361            append out "SCALARS $label double $elemSize\n"
1362            append out "LOOKUP_TABLE default\n"
1363        }
1364        # values for VTK are x-fastest
1365        append out $_values
1366        append out "\n"
1367        return $out
1368    }
1369    # unirect3d (deprecated)
1370    if {[info exists _comp2unirect3d($cname)]} {
1371        set vector [$_comp2unirect3d($cname) values]
1372        set label $cname
1373        regsub -all { } $label {_} label
1374        set elemSize [numComponents $cname]
1375        set numValues [expr [$vector length] / $elemSize]
1376        append out "# vtk DataFile Version 3.0\n"
1377        append out "[hints label]\n"
1378        append out "ASCII\n"
1379        append out [$_comp2unirect3d($cname) vtkdata]
1380        append out "POINT_DATA $numValues\n"
1381        if {$elemSize == 3} {
1382            append out "VECTORS $label double\n"
1383        } else {
1384            append out "SCALARS $label double $elemSize\n"
1385            append out "LOOKUP_TABLE default\n"
1386        }
1387        # values for VTK are x-fastest
1388        append out [$vector range 0 end]
1389        append out "\n"
1390        return $out
1391    }
1392    # Points on mesh:  Construct VTK file output.
1393    if { [info exists _comp2mesh($cname)] } {
1394        # Data is in the form mesh and vector
1395        foreach {mesh vector} $_comp2mesh($cname) break
1396        set label $cname
1397        regsub -all { } $label {_} label
1398        append out "# vtk DataFile Version 3.0\n"
1399        append out "[hints label]\n"
1400        append out "ASCII\n"
1401        append out [$mesh vtkdata]
1402
1403        if { $_comp2assoc($cname) == "pointdata" } {
1404            set vtkassoc "POINT_DATA"
1405        } elseif { $_comp2assoc($cname) == "celldata" } {
1406            set vtkassoc "CELL_DATA"
1407        } elseif { $_comp2assoc($cname) == "fielddata" } {
1408            set vtkassoc "FIELD"
1409        } else {
1410            error "unknown association \"$_comp2assoc($cname)\""
1411        }
1412        set elemSize [numComponents $cname]
1413        set numValues [expr [$vector length] / $elemSize]
1414        if { $_comp2assoc($cname) == "fielddata" } {
1415            append out "$vtkassoc FieldData 1\n"
1416            append out "$label $elemSize $numValues double\n"
1417        } else {
1418            append out "$vtkassoc $numValues\n"
1419            if { $_comp2type($cname) == "colorscalars" } {
1420                # Must be float for ASCII, unsigned char for BINARY
1421                append out "COLOR_SCALARS $label $elemSize\n"
1422            } elseif { $_comp2type($cname) == "normals" } {
1423                # elemSize must equal 3
1424                append out "NORMALS $label double\n"
1425            } elseif { $_comp2type($cname) == "scalars" } {
1426                # elemSize can be 1, 2, 3 or 4
1427                append out "SCALARS $label double $elemSize\n"
1428                append out "LOOKUP_TABLE default\n"
1429            } elseif { $_comp2type($cname) == "tcoords" } {
1430                # elemSize must be 1, 2, or 3
1431                append out "TEXTURE_COORDINATES $label $elemSize double\n"
1432            } elseif { $_comp2type($cname) == "tensors" } {
1433                # elemSize must equal 9
1434                append out "TENSORS $label double\n"
1435            } elseif { $_comp2type($cname) == "vectors" } {
1436                # elemSize must equal 3
1437                append out "VECTORS $label double\n"
1438            } else {
1439                error "unknown element type \"$_comp2type($cname)\""
1440            }
1441        }
1442        append out [$vector range 0 end]
1443        append out "\n"
1444        if 0 {
1445            VerifyVtkDataSet $out
1446        }
1447        return $out
1448    }
1449    error "can't find vtkdata for $cname"
1450}
1451
1452#
1453# BuildPointsOnMesh --
1454#
1455#        Parses the field XML description to build a mesh and values vector
1456#        representing the field.  Right now we handle the deprecated types
1457#        of "cloud", "unirect2d", and "unirect3d" (mostly for flows).
1458#
1459itcl::body Rappture::Field::BuildPointsOnMesh {cname} {
1460    #
1461    # More complex 2D/3D data is represented by a mesh
1462    # object and an associated vector for field values.
1463    #
1464    set path [$_field get $cname.mesh]
1465    if {[$_xmlobj element $path] == ""} {
1466        # Unknown mesh designated.
1467        puts stderr "ERROR: Unknown mesh \"$path\""
1468        return 0
1469    }
1470    set element [$_xmlobj element -as type $path]
1471    set name $cname
1472    regsub -all { } $name {_} name
1473    set _fld2Label($name) $name
1474    set label [hints zlabel]
1475    if { $label != "" } {
1476        set _fld2Label($name) $label
1477    }
1478    set _fld2Units($name) [hints zunits]
1479    set _fld2Components($name) $_comp2size($cname)
1480    lappend _comp2fldName($cname) $name
1481
1482    # Handle bizarre cases that are due to be removed.
1483    if { $element == "unirect3d" } {
1484        # Special case: unirect3d (deprecated) + flow.
1485        set vectorsize [numComponents $cname]
1486        set _type unirect3d
1487        set _dim 3
1488        if { $_viewer == "" } {
1489            set _viewer flowvis
1490        }
1491        set _comp2dims($cname) "3D"
1492        set _comp2unirect3d($cname) \
1493            [Rappture::Unirect3d \#auto $_xmlobj $_field $cname $vectorsize]
1494        set _comp2style($cname) [$_field get $cname.style]
1495        set limits {}
1496        foreach axis { x y z } {
1497            lappend limits $axis [$_comp2unirect3d($cname) limits $axis]
1498        }
1499        # Get the data limits
1500        set vector [$_comp2unirect3d($cname) values]
1501        set minmax [VectorLimits $vector $vectorsize]
1502        lappend limits $cname $minmax
1503        lappend limits v      $minmax
1504        set _comp2limits($cname) $limits
1505        if {[$_field element $cname.flow] != ""} {
1506            set _comp2flowhints($cname) \
1507                [Rappture::FlowHints ::\#auto $_field $cname $_units]
1508        }
1509        incr _counter
1510        return 1
1511    }
1512    if { $element == "unirect2d" && [$_field element $cname.flow] != "" } {
1513        # Special case: unirect2d (deprecated) + flow.
1514        set vectorsize [numComponents $cname]
1515        set _type unirect2d
1516        set _dim 2
1517        if { $_viewer == "" } {
1518            set _viewer "flowvis"
1519        }
1520        set _comp2dims($cname) "2D"
1521        set _comp2unirect2d($cname) \
1522            [Rappture::Unirect2d \#auto $_xmlobj $path]
1523        set _comp2style($cname) [$_field get $cname.style]
1524        set _comp2flowhints($cname) \
1525            [Rappture::FlowHints ::\#auto $_field $cname $_units]
1526        set _values [$_field get $cname.values]
1527        set limits {}
1528        foreach axis { x y z } {
1529            lappend limits $axis [$_comp2unirect2d($cname) limits $axis]
1530        }
1531        set xv [blt::vector create \#auto]
1532        $xv set $_values
1533        set minmax [VectorLimits $xv $vectorsize]
1534        lappend limits $cname $minmax
1535        lappend limits v $minmax
1536        blt::vector destroy $xv
1537        set _comp2limits($cname) $limits
1538        incr _counter
1539        return 1
1540    }
1541    switch -- $element {
1542        "cloud" {
1543            set mesh [Rappture::Cloud::fetch $_xmlobj $path]
1544            set _type cloud
1545        }
1546        "mesh" {
1547            set mesh [Rappture::Mesh::fetch $_xmlobj $path]
1548            set _type mesh
1549        }
1550        "unirect2d" {
1551            if { $_viewer == "" } {
1552                set _viewer "heightmap"
1553            }
1554            set mesh [Rappture::Unirect2d::fetch $_xmlobj $path]
1555            set _type unirect2d
1556        }
1557    }
1558    if { ![$mesh isvalid] } {
1559        puts stderr "Mesh is invalid"
1560        return 0
1561    }
1562    set _dim [$mesh dimensions]
1563    if { $_dim == 3 } {
1564        set dim 0
1565        foreach axis {x y z} {
1566            foreach {min max} [$mesh limits $axis] {
1567                if { $min < $max } {
1568                    incr dim
1569                }
1570            }
1571        }
1572        if { $dim != 3 } {
1573            set _dim $dim
1574        }
1575    }
1576
1577    if {$_dim < 2} {
1578        puts stderr "ERROR: Can't convert 1D cloud/mesh to curve.  Please use curve output for 1D meshes."
1579        return 0
1580
1581        # 1D data: Create vectors for graph widget.
1582        # The prophet tool currently outputs 1D clouds with fields
1583        # Band Structure Lab used to (see isosurface1 test in rappture-bat)
1584        #
1585        # Is there a natural growth path in generating output from 1D to
1586        # higher dimensions?  If there isn't, let's kill this in favor
1587        # or explicitly using a <curve> instead.  Otherwise, the features
1588        # (methods such as xmarkers) of the <curve> need to be added
1589        # to the <field>.
1590        #
1591        #set xv [blt::vector create x$_counter]
1592        #set yv [blt::vector create y$_counter]
1593
1594        # This only works with a Cloud mesh type, since the points method
1595        # is not implemented for the Mesh object
1596        #$xv set [$mesh points]
1597        # TODO: Put field values in yv
1598        #set _comp2dims($cname) "1D"
1599        #set _comp2xy($cname) [list $xv $yv]
1600        #incr _counter
1601        #return 1
1602    }
1603    if {$_dim == 2} {
1604        # 2D data: By default surface or contour plot using heightmap widget.
1605        set v [blt::vector create \#auto]
1606        $v set [$_field get $cname.values]
1607        if { [$v length] == 0 } {
1608            return 0
1609        }
1610        if { $_viewer == "" } {
1611            set _viewer "contour"
1612        }
1613        set numFieldValues [$v length]
1614        set numComponentsPerTuple [numComponents $cname]
1615        if { [expr $numFieldValues % $numComponentsPerTuple] != 0 } {
1616            puts stderr "ERROR: Number of field values ($numFieldValues) not divisble by elemsize ($numComponentsPerTuple)"
1617            return 0
1618        }
1619        set numFieldTuples [expr $numFieldValues / $numComponentsPerTuple]
1620        if { $_comp2assoc($cname) == "pointdata" } {
1621            set numPoints [$mesh numpoints]
1622            if { $numPoints != $numFieldTuples } {
1623                puts stderr "ERROR: Number of points in mesh ($numPoints) and number of field tuples ($numFieldTuples) don't agree"
1624                return 0
1625            }
1626        } elseif { $_comp2assoc($cname) == "celldata" } {
1627            set numCells [$mesh numcells]
1628            if { $numCells != $numFieldTuples } {
1629                puts stderr "ERROR: Number of cells in mesh ($numCells) and number of field tuples ($numFieldTuples) don't agree"
1630                return 0
1631            }
1632        }
1633        set _comp2dims($cname) "[$mesh dimensions]D"
1634        set _comp2mesh($cname) [list $mesh $v]
1635        set _comp2style($cname) [$_field get $cname.style]
1636        if {[$_field element $cname.flow] != ""} {
1637            set _comp2flowhints($cname) \
1638                [Rappture::FlowHints ::\#auto $_field $cname $_units]
1639        }
1640        incr _counter
1641        array unset _comp2limits $cname
1642        foreach axis { x y z } {
1643            lappend _comp2limits($cname) $axis [$mesh limits $axis]
1644        }
1645        set minmax [VectorLimits $v $_comp2size($cname)]
1646        lappend _comp2limits($cname) $cname $minmax
1647        lappend _comp2limits($cname) v $minmax
1648        return 1
1649    }
1650    if {$_dim == 3} {
1651        # 3D data: By default isosurfaces plot using isosurface widget.
1652        if { $_viewer == "" } {
1653            set _viewer "isosurface"
1654        }
1655        set v [blt::vector create \#auto]
1656        $v set [$_field get $cname.values]
1657        if { [$v length] == 0 } {
1658            puts stderr "ERROR: No field values"
1659            return 0
1660        }
1661        set numFieldValues [$v length]
1662        set numComponentsPerTuple [numComponents $cname]
1663        if { [expr $numFieldValues % $numComponentsPerTuple] != 0 } {
1664            puts stderr "ERROR: Number of field values ($numFieldValues) not divisble by elemsize ($numComponentsPerTuple)"
1665            return 0
1666        }
1667        set numFieldTuples [expr $numFieldValues / $numComponentsPerTuple]
1668        if { $_comp2assoc($cname) == "pointdata" } {
1669            set numPoints [$mesh numpoints]
1670            if { $numPoints != $numFieldTuples } {
1671                puts stderr "ERROR: Number of points in mesh ($numPoints) and number of field tuples ($numFieldTuples) don't agree"
1672                return 0
1673            }
1674        } elseif { $_comp2assoc($cname) == "celldata" } {
1675            set numCells [$mesh numcells]
1676            if { $numCells != $numFieldTuples } {
1677                puts stderr "ERROR: Number of cells in mesh ($numCells) and number of field tuples ($numFieldTuples) don't agree"
1678                return 0
1679            }
1680        }
1681        set _comp2dims($cname) "[$mesh dimensions]D"
1682        set _comp2mesh($cname) [list $mesh $v]
1683        set _comp2style($cname) [$_field get $cname.style]
1684        if {[$_field element $cname.flow] != ""} {
1685            set _comp2flowhints($cname) \
1686                [Rappture::FlowHints ::\#auto $_field $cname $_units]
1687        }
1688        incr _counter
1689        foreach axis { x y z } {
1690            lappend _comp2limits($cname) $axis [$mesh limits $axis]
1691        }
1692        set minmax [VectorLimits $v $_comp2size($cname)]
1693        lappend _comp2limits($cname) $cname $minmax
1694        lappend _comp2limits($cname) v $minmax
1695        return 1
1696    }
1697    error "unhandled case in field dim=$_dim element=$element"
1698}
1699
1700itcl::body Rappture::Field::AvsToVtk { cname contents } {
1701    package require vtk
1702
1703    set reader $this-datasetreader
1704    vtkAVSucdReader $reader
1705
1706    # Write the contents to a file just in case it's binary.
1707    set tmpfile $cname[pid].ucd
1708    set f [open "$tmpfile" "w"]
1709    fconfigure $f -translation binary -encoding binary
1710    puts $f $contents
1711    close $f
1712    $reader SetFileName $tmpfile
1713    $reader Update
1714    file delete $tmpfile
1715
1716    set tmpfile $cname[pid].vtk
1717    set writer $this-datasetwriter
1718    vtkDataSetWriter $writer
1719    $writer SetInputConnection [$reader GetOutputPort]
1720    $writer SetFileName $tmpfile
1721    $writer SetFileTypeToBinary
1722    $writer Write
1723    rename $reader ""
1724    rename $writer ""
1725
1726    set f [open "$tmpfile" "r"]
1727    fconfigure $f -translation binary -encoding binary
1728    set vtkdata [read $f]
1729    close $f
1730    file delete $tmpfile
1731    return $vtkdata
1732}
1733
1734itcl::body Rappture::Field::DicomToVtk { cname path } {
1735    package require vtk
1736
1737    if { ![file exists $path] } {
1738        puts stderr "path \"$path\" doesn't exist."
1739        return 0
1740    }
1741
1742    if { [file isdir $path] } {
1743        set files [glob -nocomplain $path/*.dcm]
1744        if { [llength $files] == 0 } {
1745            set files [glob -nocomplain $path/*]
1746            if { [llength $files] == 0 } {
1747                puts stderr "no dicom files found in \"$path\""
1748                return 0
1749            }
1750        }
1751
1752        #array set data [Rappture::DicomToVtk files $files]
1753        array set data [Rappture::DicomToVtk dir $path]
1754    } else {
1755        array set data [Rappture::DicomToVtk files [list $path]]
1756    }
1757
1758    if 0 {
1759        foreach key [array names data] {
1760            if {$key == "vtkdata"} {
1761                if 0 {
1762                    set f [open /tmp/$cname.vtk "w"]
1763                    fconfigure $f -translation binary -encoding binary
1764                    puts -nonewline $f $data(vtkdata)
1765                    close $f
1766                }
1767            } else {
1768                puts stderr "$key = \"$data($key)\""
1769            }
1770        }
1771    }
1772
1773    ReadVtkDataSet $cname $data(vtkdata)
1774    return $data(vtkdata)
1775}
1776
1777itcl::body Rappture::Field::GetTypeAndSize { cname } {
1778    array set type2components {
1779        "colorscalars"         4
1780        "normals"              3
1781        "scalars"              1
1782        "tcoords"              2
1783        "tensors"              9
1784        "vectors"              3
1785    }
1786    set type [$_field get $cname.elemtype]
1787    # <extents> is a deprecated synonym for <elemsize>
1788    set extents  [$_field get $cname.extents]
1789    if { $type == "" } {
1790        if { $extents != "" && $extents > 1 } {
1791            set type "vectors"
1792        } else {
1793            set type "scalars"
1794        }
1795    }
1796    if { ![info exists type2components($type)] } {
1797        error "unknown <elemtype> \"$type\" in field"
1798    }
1799    set size [$_field get $cname.elemsize]
1800    if { $size == "" } {
1801        if { $extents != "" } {
1802            set size $extents
1803        } else {
1804            set size $type2components($type)
1805        }
1806    }
1807    set _comp2type($cname) $type
1808    set _comp2size($cname) $size
1809}
1810
1811itcl::body Rappture::Field::GetAssociation { cname } {
1812    set assoc [$_field get $cname.association]
1813    if { $assoc == "" } {
1814        set _comp2assoc($cname) "pointdata"
1815        return
1816    }
1817    switch -- $assoc {
1818        "pointdata" - "celldata" - "fielddata" {
1819            set _comp2assoc($cname) $assoc
1820            return
1821        }
1822        default {
1823            error "unknown field association \"$assoc\""
1824        }
1825    }
1826}
1827
1828#
1829# Compute the per-component limits or limits of vector magnitudes
1830#
1831itcl::body Rappture::Field::VectorLimits {vector vectorsize {comp -1}} {
1832    if {$vectorsize == 1} {
1833        set minmax [$vector limits]
1834    } else {
1835        set len [$vector length]
1836        if {[expr $len % $vectorsize] != 0} {
1837            error "Invalid vectorsize: $vectorsize"
1838        }
1839        if {$comp > $vectorsize-1} {
1840            error "Invalid vector component: $comp"
1841        }
1842        set numTuples [expr ($len/$vectorsize)]
1843        for {set i 0} {$i < $numTuples} {incr i} {
1844            if {$comp >= 0} {
1845                set idx [expr ($i * $vectorsize + $comp)]
1846                set val [$vector index $idx]
1847            } else {
1848                set idx [expr ($i * $vectorsize)]
1849                set mag 0
1850                for {set j 0} {$j < $vectorsize} {incr j} {
1851                    set val [$vector index $idx]
1852                    set mag [expr ($mag + $val * $val)]
1853                    incr idx
1854                }
1855                set val [expr (sqrt($mag))]
1856            }
1857            if (![info exists minmax]) {
1858                set minmax [list $val $val]
1859            } else {
1860                if {$val < [lindex $minmax 0]} {
1861                    lset minmax 0 $val
1862                }
1863                if {$val > [lindex $minmax 1]} {
1864                    lset minmax 1 $val
1865                }
1866            }
1867        }
1868    }
1869    return $minmax
1870}
Note: See TracBrowser for help on using the repository browser.