Skip to content

N5 Container Tree

John Bogovic edited this page Oct 19, 2021 · 5 revisions

A container tree represents the metadata for all groups and datasets in a container. This structure is useful for metadata translation when the translation method needs

Tree Node

The container tree is made up of nodes. Each node corresponds to a group or dataset belonging to the n5 container. Each node is a json object with two keys: attributes and children like so:

{
    "attributes": {},
    "children" : {}
}

the value corresponding to "attributes" is a JSON object that contains the metadata for this group or dataset (i.e. the contents of attributes.json). The value for "children" is a JSON object whose keys are the paths of this node's children (if they exist), and whose values are the corresponding tree nodes.

Momentarily putting aside attributes, to represent a container with the group / dataset structure:

└─ <root>
    ├── c0
        ├─ s0
        └─ s1 
    └─ c1 
        ├── s0
        └─ s1 

we use the json:

{
    "children" : {
        "c0" : {
            "children" : {
                "s0" : { "children" : {} },
                "s1" : { "children" : {} }
            }
        },
        "c1" : {
            "children" : {
                "s0" : { "children" : {} },
                "s1" : { "children" : {} }
            }
        }
    }
}

adding the "attributes" we get:

{
    "attributes" : {},
    "children" : {
        "c0" : {
            "attributes" : {},
            "children" : {
                "s0" : { 
                    "attributes" : {},
                    "children" : {}
                },
                "s1" : { 
                    "attributes" : {},
                    "children" : {}
                }
            }
        },
        "c1" : {
            "attributes" : {},
            "children" : {
                "s0" : { 
                    "attributes" : {},
                    "children" : {}
                },
                "s1" : { 
                    "attributes" : {},
                    "children" : {}
                }
            }
        }
    }
}

where the attributes values were left empty for brevity.

Large example

The full container tree for the test.n5 container is given by this json representation.

The structure of the container

└─ test.n5 
    ├── c0
        ├─ s0
        └─ s1 
    ├── cosem
    ├── cosem_ms
        ├─ s0
        ├─ s1
        └─ s2 
    ├── n5v_ds
    ├── n5v_pr
    ├── n5v_pr-ds
    ├── n5v_pra
    ├── n5v_pra-ds
    └─ others 
        ├─ res
        └─ resOff 

For developers

Creating from an existing container

The static method ContainerMetadataNode.build creates a ContainerMetadataNode from a given n5 container.

// returns the root node 
ContainerMetadataNode root = ContainerMetadataNode.build(n5, n5.getGson());

// returns the subTree with c0 as the root
ContainerMetadataNode c0SubTree = ContainerMetadataNode.build(n5, "c0", n5.getGson());

see below for more information on sub-trees.

Writing the tree to an n5 container

ContainerMetadataWriter enables the writing of attributes stored in a ContainerMetadataNode tree to another n5 container.

N5Writer n5 = // some n5 writer
ContainerMetadataNode root = // some metadata tree
ContainerMetadataWriter containerWriter = new ContainerMetadataWriter( n5, root );

// write all the attributes in root to n5's container
// overwrites attributes in n5 that are also present in root
// but does not remove attributes in n5 not in root
containerWriter.writeAllAttributes();  

// writes all attributes in the group or dataset at path "cosem" 
containerWriter.writeAllAttributes("cosem");

// write the "transform" attributes in the group or dataset at path "cosem" 
// if the attribute is present in the tree.
containerWriter.writeAllAttributes("cosem", "transform");

N5Writer

ContainerMetadataNodes implement N5Writer, but only methods related to getting / setting attributes are functional (methods related to reading / writing data blocks do nothing).

Sub-tree behavior

ContainerMetadataNode root = // the root

// c0Node is the subtree containing the c0 node and all its descendents 
ContainerMetadataNode c0Node  = root.getNode("c0");

// operations on c0's children work, and use the full-path
c0Node.setAttribute("c0/s0", "myAttribute","the-value-of-myAttribute");
c0Node.getAttribute("c0/s0", "myAttribute", String.class);

// but operations other parts of the tree do not
c0Node.setAttribute("cosem", "myAttribute","the-value-of-myAttribute"); // does nothing

Translating with Jq

Jq functions can be applied to metadata trees to modify / add metadata, enabling chaning from one metadata dialect to another, for example. TreeTranslation provides this functionality.