Subphrase generators for graph resources

Problem statement

SMTK’s descriptive phrase view provides a tree that shows resources and/or components in a hierarchy. The top-level entries are generally specified as a filter to select persistent objects from a resource manager. The children of these entries (and all sub-entries) are computed by a subphrase generator class. SMTK has a default subphrase generator that handles model, mesh, and attribute resources. However, graph-resource components are presented as a flat list – which is undesirable. The problem is that

  • a graph resource need not be a hierarchy; arcs can create cycles which may confuse users.
  • graph resources may have multiple arc types, leaving it unclear which arcs should be used to define the child-phrases of a given component. Listing all components related by any outgoing arc could result in significant clutter and confusion.

We need some mechanism to specify how to use arcs to identify the children of a phrase’s subject component. There are several issues to consider. To illustrate them, examine the graph below. Let’s say we identify components A, B, and C as subjects for the top-level phrases.

You can see that depending on which arc types we wish to use, the hierarchy may be different. Furthermore, if we used arc types a, b, and c there would be duplicates and cycles (not shown since it would recurse infnitely if expanded fully).

Proposed design

A simple design for a custom subphrase generator would be to template it on a traits object that described the arcs to use, Although it does not support all use cases (especially using different arcs at different depths of the tree), configuration would be minimal:

template<typename SubphraseArcTraits, typename Resource>
class GraphSubphraseGenerator : public smtk::view::SubphraseGenerator
{
  // ... SMTK woud provide an implementation. 
};

Your plugin would then be required to register a version specialized to your graph resource with the SubphraseGeneratorFactory in your Registrar:

class Registar
{
public:
  void registerTo(const smtk::resource::Manager::Ptr& manager)
  {
    // Register your graph-resource as usual.
    manager->registerResource<ExampleResource>();
  }
  void registerTo(const smtk::view::Manager::Ptr& manager)
  {
    // This tuple list pairs of node+arc type.
    // For the given node type, the given arc
    // is requested and any destination nodes
    // are listed as children:
    using ExampleArcTraits = std::tuple<
      std::pair<NodeTypeA, ArcTypeA>,
      std::pair<NodeTypeA, ArcTypeB>,
      std::pair<NodeTypeB, ArcTypeA>,
      std::pair<NodeTypeC, ArcTypeC>
    >;
    // Note the traits and resource type are both passed to the
    // graph subphrase-generator so it can verify that the node and
    // arc classes belong to the resource in question.
    manager->subphraseGeneratorFactory().registerType<
      GraphSubphraseGenerator<ExampleArcTraits, ExampleResource>();
  }
};

Once registered, any qtResourceBrowser instance can override the default configuration (in smtk/extension/qt/ResourcePanelConfiguration.json) and name the custom subphrase generator by type (see smtk/common/TypeName.h for details on how the name is computed).

If you need a tree with nodes from multiple graph resources, then either

  • you would have to create a subphrase generator that used methods from 2 or more specialized GraphSubphraseGenerator<> instances; or
  • SMTK would need to make GraphSubphraseGenerator accept multiple arc-traits and resource types.

There are 2 specific use cases that have come up in terms of the work on a markup resource.

  • Collection hierarchies, where the primary organization of the phrase model shows user-created groupings/collections.
  • Inheritance hierarchies, where the primary organization of the phrase model shows nodes by their type, but in a hierarchy where the parent-child relationship is class inheritance.

They point to the template parameters of the GraphSubphraseGenerator being classes in their own right rather than just a pair of node-type and arc-type. This is especially important for the inheritance hierarchy, since it must insert phrases that do not have a corresponding persistent object (i.e., the phrase whose title is a class type-name).

Collection hierarchies

The advange of the “collection hierarchy” is that users can organize it to fit their data, workflow, and mental model. The disadvantage is that components may be added to the resource without grouping information, leaving them either not presented or requiring a “catch-all” grouping.

There is no canonical structure in a collection hierarchy. However, here is an example of how a collection hierarchy might display a tree using group membership.

  Resource
    Model A0
      Group B0
        some members of B0
        Model C0
          other members of C0
        other members of B0
      Group B1
        other members of B1
      other members of A0
    Group A1
      Group B2
        other members of B2
      Group B3
        other members of B3
    ...
    Group "Unused"  ⟵ special singleton created by Resource ctor
      members of "Unused" (nodes with no parent)

A PhraseModel implementation might display all Group/Model instances with no parent group at the top level. The subphrase generator would identify members of the parent grouping. Components with no parent grouping would automatically be inserted into the “Unused” group. If we assume that components are always assigned a “Group” arc pointing to a parent group, then

   using SubphraseTraits = std::tuple<
     ArcSubphrases<Group, GroupMembers>,
     ArcSubphrases<Model, ModelMembers>
   >;

   viewManager->subphraseGeneratorFactory().registerType<
     GraphSubphraseGenerator<SubphraseTraits, smtk::markup::Resource>();

would be all that was required to make this subphrase generator.

Inheritance hierarchies

The advantage to the “inheritance hierarchy” is that extracting the tree from the graph is simple… the placement of each component in the tree is determined by its classname and, within like items, by its user-assigned name.

The main disadvantage is that the layout is not always how users think of their data, nor does it illustrate relationships that are necessarily the most important.

The inheritance hierarchy might display components in a tree like so:

  Resource
    SpatialData
      AnalyticShapes
        Boxes
        Cones
        ImplicitShapes
        Planes
        Spheres
      DiscreteGeometries
        Grids
          SurfaceGrids
          VolumeGrids
        Images
      SideSets
      Subsets
        CompositeSubsets
        ExplicitSubsets
        RangedSubsets
    Groups
    Ontologies
    Features
      Landmarks
      Fields
        LabelMaps
          Segmentations
      Frames
      Labels
        OntologyIdentifiers
        Comments

A PhraseModel implementation would create a fixed set of phrases corresponding to types present in the resource whose immediate superclass was markup::Component. The subphrase generator would compute children of a parent as

  1. the set of subclasses of the parent phrase’s type followed by
  2. the set of components of the given type. Hiding types that have no components or subclassed components would be tedious but possible.
   using SubphraseTraits = std::tuple<
     NodeSubclassesAndInstances
   >;

   viewManager->subphraseGeneratorFactory().registerType<
     GraphSubphraseGenerator<SubphraseTraits, smtk::markup::Resource>();

NodeSubclassesAndInstances would, in its constructor, iterate over all of the graph-resource’s Traits::NodeTypes entries and introspect each type’s Superclass, creating a tree of class names. Then, when invoked to generate subphrases of a particular phrase, it would

  • return nothing if the parent phrase had a related object
  • create children if the parent phrase’s content was ObjectTypeContent: children would include more ObjectTypeContent phrases holding subclasses of the ObjectTypeContent's typeName() and instances of that type, sorted alphabetically by their user-assigned name.