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

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

Remove old unirect blob encodings for sending data to nanovis. Unirect2d/3d are
now treated as deprecated mesh types that are converted to VTK, like cloud.

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