SessionNode

The SessionNode class in Geomapi represents the data and metadata captured within a single epoch. The data itself can consist of various remote sensing inputs (point clouds, meshes, etc.) and mostly builds upon OPEN3D. The metadata of the SessionNode consists of the overarching properties and builds upon the RDFlib framework:

http://www.open3d.org/docs/latest/tutorial/Basic/mesh.html#

https://rdflib.readthedocs.io/

The code below shows how to create a SessionNode from various inputs.

First the geomapi and external packages are imported

#IMPORT PACKAGES
from rdflib import Graph, URIRef, Literal, RDF
import open3d as o3d
import os
from pathlib import Path
import ifcopenshell
import numpy as np
import ifcopenshell.util.selector 

#IMPORT MODULES
from context import geomapi 
from geomapi.nodes import *
import geomapi.utils as ut
from geomapi.utils import geometryutils as gmu
import geomapi.tools as tl
%load_ext autoreload
The autoreload extension is already loaded. To reload it, use:
  %reload_ext autoreload
%autoreload 2

SessionNode from properties

A placeholder SessionNode can be initialised without any data or metadata

node=SessionNode(subject='myNode',
                name='myName')
{key:value for key, value in node.__dict__.items() if not key.startswith('__') and not callable(key)}              
{'_linkedNodes': [],
 '_linkedSubjects': [],
 '_subject': rdflib.term.URIRef('file:///myNode'),
 '_graph': None,
 '_graphPath': None,
 '_path': None,
 '_name': 'myName',
 '_cartesianBounds': None,
 '_orientedBounds': None,
 '_orientedBoundingBox': None,
 '_timestamp': None,
 '_resource': None,
 '_cartesianTransform': None}

Add linkedNodes

Once a Sessionnode is established, any Node can be added to it that is part of the epoch. The instance variables linkedNodes and linkedSubjects (which is automatically updated) are used for this purpose.

filePath=os.path.join(Path(os.getcwd()).parents[2],'test','testfiles','MESH','week22.obj')
resourceNode1=MeshNode(path=filePath,getResource=True)
node.add_linked_nodes(resourceNode1)
{key:value for key, value in node.__dict__.items() if not key.startswith('__') and not callable(key)}              
{'_linkedNodes': [<geomapi.nodes.meshnode.MeshNode at 0x24e15ea11c0>],
 '_linkedSubjects': [rdflib.term.URIRef('file:///week22')],
 '_subject': rdflib.term.URIRef('file:///myNode'),
 '_graph': None,
 '_graphPath': None,
 '_path': None,
 '_name': 'myName',
 '_cartesianBounds': None,
 '_orientedBounds': None,
 '_orientedBoundingBox': None,
 '_timestamp': None,
 '_resource': None,
 '_cartesianTransform': None}

Compute metadata

If a set of nodes is present in a session, one can compute the overarching metadata. First, add a second node.

filePath=os.path.join(Path(os.getcwd()).parents[2],'test','testfiles','PCD','academiestraat week 22 a 20.pcd')
resourceNode2=PointCloudNode(path=filePath,getResource=True)
node.add_linked_nodes(resourceNode2)
node.get_metadata_from_linked_nodes()
{key:value for key, value in node.__dict__.items() if not key.startswith('__') and not callable(key)}              
{'_linkedNodes': [<geomapi.nodes.meshnode.MeshNode at 0x24e15ea11c0>,
  <geomapi.nodes.pointcloudnode.PointCloudNode at 0x24e15ea1af0>],
 '_linkedSubjects': [rdflib.term.URIRef('file:///week22'),
  rdflib.term.URIRef('file:///academiestraat_week_22_a_20')],
 '_subject': rdflib.term.URIRef('file:///myNode'),
 '_graph': None,
 '_graphPath': None,
 '_path': None,
 '_name': 'myName',
 '_cartesianBounds': array([-52.61117755, 122.50257926,  12.05279306, 165.88417048,
         -2.84643678,  24.03564393]),
 '_orientedBounds': array([[-23.32604321, 166.79579552,  25.50980916],
        [123.61436625, 128.38233267,  26.79035759],
        [-54.06530499,  49.13654472,  23.27674533],
        [-22.97196977, 167.22821067,  -2.1479756 ],
        [ 93.22917791,  11.15549702,  -3.10049101],
        [-53.71123155,  49.56895987,  -4.38103943],
        [123.96843969, 128.81474782,  -0.86742718],
        [ 92.87510447,  10.72308187,  24.55729376]]),
 '_orientedBoundingBox': OrientedBoundingBox: center: (34.9516, 88.9756, 11.2047), extent: 151.884, 121.629, 27.6634),
 '_timestamp': '2022-08-02T08:25:01',
 '_resource': TriangleMesh with 12 points and 20 triangles.,
 '_cartesianTransform': array([[ 1.        ,  0.        ,  0.        , 20.57065419],
        [ 0.        ,  1.        ,  0.        , 88.47905237],
        [ 0.        ,  0.        ,  1.        ,  8.41263647],
        [ 0.        ,  0.        ,  0.        ,  1.        ]])}

NOTE: the session’s geometric metadata i.e. cartesianBounds, orientedBounds, cartesianTransform and orientedBounding box encompas the entire session. As such, these properties represent the average and the extremes of the nodes in the session.

rendering

SessionNode from Nodes

SessionNodes can also be directly initialised from a set of nodes.

meshNode=MeshNode(path=os.path.join(Path(os.getcwd()).parents[2],'test','testfiles','Mesh','Basic Wall_211_WA_Ff1_Glued brickwork sandlime 150mm_1095339.obj'),getResource=True)
imgNode=ImageNode(xmpPath=os.path.join(Path(os.getcwd()).parents[2],'test','testfiles','IMG','IMG_2174.xmp'),getResource=True)
pcdNode=PointCloudNode(path=os.path.join(Path(os.getcwd()).parents[2],'test','testfiles','PCD','week22 photogrammetry - Cloud.pcd'),getResource=True)
bimNode=BIMNode(ifcPath=os.path.join(Path(os.getcwd()).parents[2],'test','testfiles','IFC','Academiestraat_parking.ifc'),getResource=True)
nodeList=[meshNode,imgNode,pcdNode,bimNode]     
node=SessionNode(linkedNodes=nodeList)
{key:value for key, value in node.__dict__.items() if not key.startswith('__') and not callable(key)}   
{'_linkedNodes': [<geomapi.nodes.meshnode.MeshNode at 0x24e15ebc6d0>,
  <geomapi.nodes.imagenode.ImageNode at 0x24e15ebc670>,
  <geomapi.nodes.pointcloudnode.PointCloudNode at 0x24e15e7df40>,
  <geomapi.nodes.bimnode.BIMNode at 0x24e175d4fd0>],
 '_linkedSubjects': [rdflib.term.URIRef('file:///Basic_Wall_211_WA_Ff1_Glued_brickwork_sandlime_150mm_1095339'),
  rdflib.term.URIRef('file:///IMG_2174'),
  rdflib.term.URIRef('file:///week22_photogrammetry_-_Cloud'),
  rdflib.term.URIRef('file:///Circular_Mullion_Grouting_pile_680mm_1072728_3M75Mhp4L2Aei641BocJ1v')],
 '_subject': rdflib.term.URIRef('file:///b46569bd-1ef7-11ed-9c4a-c8f75043ce59'),
 '_graph': None,
 '_graphPath': None,
 '_path': None,
 '_name': None,
 '_cartesianBounds': array([-52.61117792, 122.50256846,   9.21354145, 165.88415522,
         -0.7098256 ,  24.0356459 ]),
 '_orientedBounds': array([[-9.66526260e+00,  1.86468386e+02,  3.02287259e+01],
        [ 1.39268037e+02,  1.15739611e+02,  2.71940092e+01],
        [-7.84479065e+01,  4.18226980e+01,  2.58085749e+01],
        [-9.81006206e+00,  1.87462216e+02, -4.02857866e-02],
        [ 7.03405939e+01, -2.79122459e+01, -7.49515344e+00],
        [-7.85927059e+01,  4.28165285e+01, -4.46043679e+00],
        [ 1.39123238e+02,  1.16733442e+02, -3.07500244e+00],
        [ 7.04853934e+01, -2.89060764e+01,  2.27738582e+01]]),
 '_orientedBoundingBox': OrientedBoundingBox: center: (30.3377, 79.2781, 11.3668), extent: 164.903, 160.228, 30.2857),
 '_timestamp': '2022-08-02T08:25:01',
 '_resource': TriangleMesh with 12 points and 20 triangles.,
 '_cartesianTransform': array([[ 1.        ,  0.        ,  0.        , 37.79468033],
        [ 0.        ,  1.        ,  0.        , 71.92163471],
        [ 0.        ,  0.        ,  1.        ,  8.91496436],
        [ 0.        ,  0.        ,  0.        ,  1.        ]])}

NOTE: GetMetadata (bool) by default is True. As such, when data is imported, the cartesianBounds, orientedBounds, cartesianTransform and orientedBoundingBox is automatically extracted.

SessionNode from resources

A similar result is achieved by initialising a SessionNode from a set of geometries. In this case, GetResource (bool) means nothing.

resources=[meshNode.resource,imgNode.resource,pcdNode.resource,bimNode.resource]
node=SessionNode(linkedResources=resources)
{key:value for key, value in node.__dict__.items() if not key.startswith('__') and not callable(key)}            
{'_linkedNodes': [<geomapi.nodes.meshnode.MeshNode at 0x24e15ea1f40>,
  <geomapi.nodes.imagenode.ImageNode at 0x24e15ea1a00>,
  <geomapi.nodes.pointcloudnode.PointCloudNode at 0x24e15ea1070>,
  <geomapi.nodes.meshnode.MeshNode at 0x24e126f28e0>],
 '_linkedSubjects': [rdflib.term.URIRef('file:///b48b8164-1ef7-11ed-9a09-c8f75043ce59'),
  rdflib.term.URIRef('file:///b48b8165-1ef7-11ed-b91e-c8f75043ce59'),
  rdflib.term.URIRef('file:///b48b8166-1ef7-11ed-a400-c8f75043ce59'),
  rdflib.term.URIRef('file:///b49ca47b-1ef7-11ed-a13f-c8f75043ce59')],
 '_subject': rdflib.term.URIRef('file:///b48b8163-1ef7-11ed-884c-c8f75043ce59'),
 '_graph': None,
 '_graphPath': None,
 '_path': None,
 '_name': None,
 '_cartesianBounds': array([-52.61117792, 122.50256846,  12.05279275, 165.88415522,
         -0.7098256 ,  24.0356459 ]),
 '_orientedBounds': array([[ -2.80881716, 195.04328414,  30.48888509],
        [146.03830992, 110.2994234 ,  26.99612434],
        [-76.00408274,  66.6405539 ,  26.60602343],
        [ -2.9516538 , 196.04025482,   0.21255762],
        [ 72.7002077 , -17.10633616,  -7.16306479],
        [-76.14691938,  67.63752458,  -3.67030404],
        [145.89547327, 111.29639407,  -3.28020313],
        [ 72.84304434, -18.10330684,  23.11326268]]),
 '_orientedBoundingBox': OrientedBoundingBox: center: (34.9457, 88.9685, 11.6629), extent: 171.316, 147.851, 30.2931),
 '_timestamp': '2022-08-18T15:14:35',
 '_resource': TriangleMesh with 11 points and 18 triangles.,
 '_cartesianTransform': array([[ 1.        ,  0.        ,  0.        , 39.72931617],
        [ 0.        ,  1.        ,  0.        , 74.53447192],
        [ 0.        ,  0.        ,  1.        ,  9.01263668],
        [ 0.        ,  0.        ,  0.        ,  1.        ]])}

NOTE: The cartesianTransform extracted from paths or resources are void of rotationmatrices as this metadata is not part of the fileformat. The translation thus represents the center of the geometry.

SessionNode from Graph and graphPath

If a session was already serialized, a SessionNode can be initialised from the graph or graphPath.

NOTE: The graphPath is the more complete option as it is used to absolutize the node’s path information. However, it is also the slower option as the entire graph encapsulation the node is parsed multiple times.

USE: linkeddatatools.graph_to_nodes resolves this issue.

It is important to notice that a SessionNode can be initiliased from 3 types of Graphs.

SessionGraph

The sessionGraph is the nodes own metadata and is considered the one true graph that should occupy the sessionnode.graph variable

graphPath = os.path.join(Path(os.getcwd()).parents[2],'test','testfiles','sessionGraph.ttl')
graph=Graph().parse(graphPath)
print(graph.serialize())
@prefix e57: <http://libe57.org#> .
@prefix openlabel: <https://www.asam.net/index.php?eID=dumpFile&t=f&f=3876&token=413e8c85031ae64cc35cf42d0768627514868b2f#> .
@prefix v4d: <https://w3id.org/v4d/core#> .

<file:///17dc31bc-17f2-11ed-bdae-c8f75043ce59> a v4d:SessionNode ;
    e57:cartesianBounds """[-52.61117792 122.50256846   9.21354145 165.88415522  -0.7098256
  24.0356459 ]""" ;
    e57:cartesianTransform """[[ 1.          0.          0.         30.86556763]
 [ 0.          1.          0.         93.16462374]
 [ 0.          0.          1.         10.82216436]
 [ 0.          0.          0.          1.        ]]""" ;
    v4d:linkedSubjects "['file:///Basic_Wall_211_WA_Ff1_Glued_brickwork_sandlime_150mm_1095339', 'file:///IMG_2174', 'file:///week22_photogrammetry_-_Cloud', 'file:///Basic_Wall_211_WA_Ff1_Glued_brickwork_sandlime_150mm_1118860_0KysUSO6T3_gOJKtAiUE7d']" ;
    v4d:name "17dc31bc-17f2-11ed-bdae-c8f75043ce59" ;
    v4d:orientedBounds """[[-3.63772704e+01  1.68281221e+02  2.37857820e+01]
 [ 1.25493633e+02  1.45094167e+02  2.49074918e+01]
 [-5.81517614e+01  1.62450831e+01  2.32386533e+01]
 [-3.61957602e+01  1.68344760e+02 -1.09404387e+00]
 [ 1.03900652e+02 -6.87843298e+00 -5.19462782e-01]
 [-5.79702512e+01  1.63086218e+01 -1.64117262e+00]
 [ 1.25675143e+02  1.45157705e+02  2.76659619e-02]
 [ 1.03719142e+02 -6.94197166e+00  2.43603631e+01]]""" ;
    v4d:path "SESSION\\17dc31bc-17f2-11ed-bdae-c8f75043ce59.ply" ;
    openlabel:timestamp "2022-08-02T08:25:01" .
node=SessionNode(graphPath=graphPath)
print(node.subject)
file:///17dc31bc-17f2-11ed-bdae-c8f75043ce59

calling getResource on this graph will gather the convex hull of all the nodes that comprised the session.

node.get_resource()
print(node.resource)
TriangleMesh with 9 points and 14 triangles.

ResourceGraph

Alternatively, a session can also be initialised from a graph that only contains resources.

NOTE: this graph can consists of any nodetypes

graphPath = os.path.join(Path(os.getcwd()).parents[2],'test','testfiles','resourceGraph.ttl')
graph=Graph().parse(graphPath)

subjects=[s for s in graph.subjects(RDF.type)]
print(subjects)
[rdflib.term.URIRef('file:///Basic_Wall_211_WA_Ff1_Glued_brickwork_sandlime_150mm_1095339'), rdflib.term.URIRef('file:///Basic_Wall_211_WA_Ff1_Glued_brickwork_sandlime_150mm_1118860_0KysUSO6T3_gOJKtAiUE7d'), rdflib.term.URIRef('file:///IMG_2174'), rdflib.term.URIRef('file:///week22_photogrammetry_-_Cloud')]
node=SessionNode(graphPath=graphPath)
{key:value for key, value in node.__dict__.items() if not key.startswith('__') and not callable(key)}            
no sessionSubjects found
{'_linkedNodes': [<geomapi.nodes.meshnode.MeshNode at 0x24e175e1a30>,
  <geomapi.nodes.bimnode.BIMNode at 0x24e182d5190>,
  <geomapi.nodes.imagenode.ImageNode at 0x24e182d5910>,
  <geomapi.nodes.pointcloudnode.PointCloudNode at 0x24e175e1700>],
 '_linkedSubjects': [rdflib.term.URIRef('file:///Basic_Wall_211_WA_Ff1_Glued_brickwork_sandlime_150mm_1095339'),
  rdflib.term.URIRef('file:///Basic_Wall_211_WA_Ff1_Glued_brickwork_sandlime_150mm_1118860_0KysUSO6T3_gOJKtAiUE7d'),
  rdflib.term.URIRef('file:///IMG_2174'),
  rdflib.term.URIRef('file:///week22_photogrammetry_-_Cloud')],
 '_subject': rdflib.term.URIRef('file:///b56f7eaf-1ef7-11ed-8315-c8f75043ce59'),
 '_graph': <Graph identifier=N71506f93b1a24d4a90046464f99554b3 (<class 'rdflib.graph.Graph'>)>,
 '_graphPath': 'd:\\Scan-to-BIM repository\\geomapi\\test\\testfiles\\resourceGraph.ttl',
 '_path': None,
 '_name': None,
 '_cartesianBounds': array([-52.61117797, 122.50256846,   9.21354145, 165.88415534,
         -0.70982566,  24.03564597]),
 '_orientedBounds': array([[-3.63772707e+01,  1.68281221e+02,  2.37857820e+01],
        [ 1.25493633e+02,  1.45094167e+02,  2.49074919e+01],
        [-5.81517614e+01,  1.62450830e+01,  2.32386533e+01],
        [-3.61957605e+01,  1.68344760e+02, -1.09404394e+00],
        [ 1.03900652e+02, -6.87843277e+00, -5.19462770e-01],
        [-5.79702512e+01,  1.63086216e+01, -1.64117265e+00],
        [ 1.25675143e+02,  1.45157706e+02,  2.76659342e-02],
        [ 1.03719142e+02, -6.94197144e+00,  2.43603632e+01]]),
 '_orientedBoundingBox': OrientedBoundingBox: center: (33.7617, 80.7014, 11.6332), extent: 163.527, 153.588, 24.8806),
 '_timestamp': '2022-08-02T08:25:01',
 '_resource': TriangleMesh with 9 points and 14 triangles.,
 '_cartesianTransform': array([[ 1.        ,  0.        ,  0.        , 30.86556763],
        [ 0.        ,  1.        ,  0.        , 93.16462375],
        [ 0.        ,  0.        ,  1.        , 10.82216436],
        [ 0.        ,  0.        ,  0.        ,  1.        ]])}

Combined Graph

A third option is a combined graph of sessionNode(s) and resourceNodes.

import pprint

graphPath = os.path.join(Path(os.getcwd()).parents[2],'test','testfiles','combinedGraph.ttl')
graph=Graph().parse(graphPath)

dict=[{s,o} for s,o in graph.subject_objects(RDF.type)]
pprint.pprint(dict)
[{rdflib.term.URIRef('file:///17dc31bc-17f2-11ed-bdae-c8f75043ce59'),
  rdflib.term.URIRef('https://w3id.org/v4d/core#SessionNode')},
 {rdflib.term.URIRef('file:///Basic_Wall_211_WA_Ff1_Glued_brickwork_sandlime_150mm_1095339'),
  rdflib.term.URIRef('https://w3id.org/v4d/core#MeshNode')},
 {rdflib.term.URIRef('file:///Basic_Wall_211_WA_Ff1_Glued_brickwork_sandlime_150mm_1118860_0KysUSO6T3_gOJKtAiUE7d'),
  rdflib.term.URIRef('https://w3id.org/v4d/core#BIMNode')},
 {rdflib.term.URIRef('file:///IMG_2174'),
  rdflib.term.URIRef('https://w3id.org/v4d/core#ImageNode')},
 {rdflib.term.URIRef('file:///week22_photogrammetry_-_Cloud'),
  rdflib.term.URIRef('https://w3id.org/v4d/core#PointCloudNode')}]
node=SessionNode(graphPath=graphPath)
{key:value for key, value in node.__dict__.items() if not key.startswith('__') and not callable(key)}     
{'_linkedNodes': [<geomapi.nodes.meshnode.MeshNode at 0x24e182d6fa0>,
  <geomapi.nodes.bimnode.BIMNode at 0x24e182d6280>,
  <geomapi.nodes.imagenode.ImageNode at 0x24e182d6af0>,
  <geomapi.nodes.pointcloudnode.PointCloudNode at 0x24e182d6d90>],
 '_linkedSubjects': [rdflib.term.URIRef('file:///Basic_Wall_211_WA_Ff1_Glued_brickwork_sandlime_150mm_1095339'),
  rdflib.term.URIRef('file:///Basic_Wall_211_WA_Ff1_Glued_brickwork_sandlime_150mm_1118860_0KysUSO6T3_gOJKtAiUE7d'),
  rdflib.term.URIRef('file:///IMG_2174'),
  rdflib.term.URIRef('file:///week22_photogrammetry_-_Cloud')],
 '_subject': rdflib.term.URIRef('file:///17dc31bc-17f2-11ed-bdae-c8f75043ce59'),
 '_graph': <Graph identifier=N49190a6bb6d748fba3b8f30b7cb84dc2 (<class 'rdflib.graph.Graph'>)>,
 '_graphPath': 'd:\\Scan-to-BIM repository\\geomapi\\test\\testfiles\\combinedGraph.ttl',
 '_path': 'd:\\Scan-to-BIM repository\\geomapi\\test\\testfiles\\SESSION\\17dc31bc-17f2-11ed-bdae-c8f75043ce59.ply',
 '_name': '17dc31bc-17f2-11ed-bdae-c8f75043ce59',
 '_cartesianBounds': array([-52.61117797, 122.50256846,   9.21354145, 165.88415534,
         -0.70982566,  24.03564597]),
 '_orientedBounds': array([[-3.63772707e+01,  1.68281221e+02,  2.37857820e+01],
        [ 1.25493633e+02,  1.45094167e+02,  2.49074919e+01],
        [-5.81517614e+01,  1.62450830e+01,  2.32386533e+01],
        [-3.61957605e+01,  1.68344760e+02, -1.09404394e+00],
        [ 1.03900652e+02, -6.87843277e+00, -5.19462770e-01],
        [-5.79702512e+01,  1.63086216e+01, -1.64117265e+00],
        [ 1.25675143e+02,  1.45157706e+02,  2.76659342e-02],
        [ 1.03719142e+02, -6.94197144e+00,  2.43603632e+01]]),
 '_orientedBoundingBox': OrientedBoundingBox: center: (33.7617, 80.7014, 11.6332), extent: 163.527, 153.588, 24.8806),
 '_timestamp': '2022-08-02T08:25:01',
 '_resource': TriangleMesh with 9 points and 14 triangles.,
 '_cartesianTransform': array([[ 1.        ,  0.        ,  0.        , 30.86556763],
        [ 0.        ,  1.        ,  0.        , 93.16462375],
        [ 0.        ,  0.        ,  1.        , 10.82216436],
        [ 0.        ,  0.        ,  0.        ,  1.        ]]),
 'type': 'https://w3id.org/v4d/core#SessionNode'}

SessionNode get linked Nodes

It is also possible to add specific Nodes from a graph to an existing session

graphPath = os.path.join(Path(os.getcwd()).parents[2],'test','testfiles','sessionGraph.ttl')
node=SessionNode(graphPath=graphPath)
print(node.linkedNodes)
[]
graphPath = os.path.join(Path(os.getcwd()).parents[2],'test','testfiles','resourceGraph.ttl')
graph=Graph().parse(graphPath)
subjects=[s for s in graph.subjects(RDF.type)]
mysubjects=subjects[0:2]
print(mysubjects)
[rdflib.term.URIRef('file:///Basic_Wall_211_WA_Ff1_Glued_brickwork_sandlime_150mm_1095339'), rdflib.term.URIRef('file:///Basic_Wall_211_WA_Ff1_Glued_brickwork_sandlime_150mm_1118860_0KysUSO6T3_gOJKtAiUE7d')]
node.get_linked_nodes(graph,mysubjects)
print(node.linkedNodes)
[<geomapi.nodes.meshnode.MeshNode object at 0x0000024E182D3670>, <geomapi.nodes.bimnode.BIMNode object at 0x0000024E182D3640>]

SessionNode get linked resources

The resources of the linkedNodes can also be directly called from the sessionNodes. Obviously, this only works if the resources are stored in the directory/subdirectory of the session graphPath

graphPath = os.path.join(Path(os.getcwd()).parents[2],'test','testfiles','combinedGraph.ttl')
node=SessionNode(graphPath=graphPath)
print(node.linkedNodes)
[<geomapi.nodes.meshnode.MeshNode object at 0x0000024E182BF460>, <geomapi.nodes.bimnode.BIMNode object at 0x0000024E182BF250>, <geomapi.nodes.imagenode.ImageNode object at 0x0000024E182D0370>, <geomapi.nodes.pointcloudnode.PointCloudNode object at 0x0000024E182BFAF0>]
resources=node.get_linked_resources()
print([type(r) for r in resources])
[<class 'open3d.cpu.pybind.geometry.TriangleMesh'>, <class 'open3d.cpu.pybind.geometry.TriangleMesh'>, <class 'numpy.ndarray'>, <class 'open3d.cpu.pybind.geometry.PointCloud'>]

SessionNode save linked resources

We can also save these resources to a new directory. Default file types will be used for each nodetype.

directory=os.path.join(Path(os.getcwd()).parents[2],'test','testfiles','resources')
node.save_linked_resources(directory)
print(ut.get_list_of_files(directory))
['d:\\Scan-to-BIM repository\\geomapi\\test\\testfiles\\resources\\17dc31bc-17f2-11ed-bdae-c8f75043ce59.ply', 'd:\\Scan-to-BIM repository\\geomapi\\test\\testfiles\\resources\\Basic_Wall_211_WA_Ff1_Glued_brickwork_sandlime_150mm_1095339.ply', 'd:\\Scan-to-BIM repository\\geomapi\\test\\testfiles\\resources\\Basic_Wall_211_WA_Ff1_Glued_brickwork_sandlime_150mm_1118860_0KysUSO6T3_gOJKtAiUE7d.ply', 'd:\\Scan-to-BIM repository\\geomapi\\test\\testfiles\\resources\\IMG_2174.png', 'd:\\Scan-to-BIM repository\\geomapi\\test\\testfiles\\resources\\week22_photogrammetry_-_Cloud.pcd']

SessionNode save resource

It is interesting to buffer the geometries on drive to speed up future analyses. Set save=True on node.to_graph with an optional filepath to store the geometry as .ply or .obj.

node.save_resource(directory)
print(node.path)
d:\Scan-to-BIM repository\geomapi\test\testfiles\SESSION\17dc31bc-17f2-11ed-bdae-c8f75043ce59.ply

SessionNode to Graph

The Graph serialisation is inherited from Node functionality.

graphPath= os.path.join(Path(os.getcwd()).parents[2],'test','testfiles','combinedGraph.ttl')

node=SessionNode(graphPath=graphPath)

newGraphPath = os.path.join(os.getcwd(),'myGraph.ttl')
node.to_graph(newGraphPath)

newNode=Node(graphPath=newGraphPath)
print(node.graph.serialize())
@prefix e57: <http://libe57.org#> .
@prefix openlabel: <https://www.asam.net/index.php?eID=dumpFile&t=f&f=3876&token=413e8c85031ae64cc35cf42d0768627514868b2f#> .
@prefix v4d: <https://w3id.org/v4d/core#> .

<file:///17dc31bc-17f2-11ed-bdae-c8f75043ce59> a v4d:SessionNode ;
    e57:cartesianBounds """[-52.61117797 122.50256846   9.21354145 165.88415534  -0.70982566
  24.03564597]""" ;
    e57:cartesianTransform """[[ 1.          0.          0.         30.86556763]
 [ 0.          1.          0.         93.16462375]
 [ 0.          0.          1.         10.82216436]
 [ 0.          0.          0.          1.        ]]""" ;
    v4d:linkedSubjects "['file:///Basic_Wall_211_WA_Ff1_Glued_brickwork_sandlime_150mm_1095339', 'file:///Basic_Wall_211_WA_Ff1_Glued_brickwork_sandlime_150mm_1118860_0KysUSO6T3_gOJKtAiUE7d', 'file:///IMG_2174', 'file:///week22_photogrammetry_-_Cloud']" ;
    v4d:name "17dc31bc-17f2-11ed-bdae-c8f75043ce59" ;
    v4d:orientedBounds """[[-3.63772707e+01  1.68281221e+02  2.37857820e+01]
 [ 1.25493633e+02  1.45094167e+02  2.49074919e+01]
 [-5.81517614e+01  1.62450830e+01  2.32386533e+01]
 [-3.61957605e+01  1.68344760e+02 -1.09404394e+00]
 [ 1.03900652e+02 -6.87843277e+00 -5.19462770e-01]
 [-5.79702512e+01  1.63086216e+01 -1.64117265e+00]
 [ 1.25675143e+02  1.45157706e+02  2.76659342e-02]
 [ 1.03719142e+02 -6.94197144e+00  2.43603632e+01]]""" ;
    v4d:path "..\\..\\..\\test\\testfiles\\SESSION\\17dc31bc-17f2-11ed-bdae-c8f75043ce59.ply" ;
    openlabel:timestamp "2022-08-02T08:25:01" .

LinkedNodes linked nodes to Graph

Additionally, sessionNodes have the functionality to serialize all of their linkedNodes aswell. This effectively creates a new resourceGraph in the target directory

node.linked_nodes_to_graph(newGraphPath,save=True)

newGraph=Graph().parse(newGraphPath)
subjects=[s for s in newGraph.subjects(RDF.type)]
print(subjects)
[rdflib.term.URIRef('file:///Basic_Wall_211_WA_Ff1_Glued_brickwork_sandlime_150mm_1095339'), rdflib.term.URIRef('file:///Basic_Wall_211_WA_Ff1_Glued_brickwork_sandlime_150mm_1118860_0KysUSO6T3_gOJKtAiUE7d'), rdflib.term.URIRef('file:///IMG_2174'), rdflib.term.URIRef('file:///week22_photogrammetry_-_Cloud')]

LinkedNodes session to Graph

Finally, a combined Graph can also be serialized consisting of both the sessionNode and each of the LinkedNodes.

node.session_to_graph(newGraphPath,save=True)

newGraph=Graph().parse(newGraphPath)
dict=[{s,o} for s,o in newGraph.subject_objects(RDF.type)]
pprint.pprint(dict)
[{rdflib.term.URIRef('file:///17dc31bc-17f2-11ed-bdae-c8f75043ce59'),
  rdflib.term.URIRef('https://w3id.org/v4d/core#SessionNode')},
 {rdflib.term.URIRef('file:///Basic_Wall_211_WA_Ff1_Glued_brickwork_sandlime_150mm_1095339'),
  rdflib.term.URIRef('https://w3id.org/v4d/core#MeshNode')},
 {rdflib.term.URIRef('file:///Basic_Wall_211_WA_Ff1_Glued_brickwork_sandlime_150mm_1118860_0KysUSO6T3_gOJKtAiUE7d'),
  rdflib.term.URIRef('https://w3id.org/v4d/core#BIMNode')},
 {rdflib.term.URIRef('file:///IMG_2174'),
  rdflib.term.URIRef('https://w3id.org/v4d/core#ImageNode')},
 {rdflib.term.URIRef('file:///week22_photogrammetry_-_Cloud'),
  rdflib.term.URIRef('https://w3id.org/v4d/core#PointCloudNode')}]

SessionNode Analysis

SessionNodes are usefull for a range of analyses. Most notably, their metadata allows for fast selection of which resources are to be used in any given analysis.

E.g. Image we want to asses which objects of a BIM model are potentially observed in an epochs.

Consider the following resource (convex hull) of the following sessionNode:

imgNode=ImageNode(xmpPath=os.path.join(Path(os.getcwd()).parents[2],'test','testfiles','IMG','IMG_2174.xmp'),getResource=True)
pcdNode=PointCloudNode(path=os.path.join(Path(os.getcwd()).parents[2],'test','testfiles','PCD','academiestraat week 22 a 20.pcd'),getResource=True)
bimNode=BIMNode(ifcPath=os.path.join(Path(os.getcwd()).parents[2],'test','testfiles','IFC','Academiestraat_parking.ifc'),getResource=True)
nodeList=[imgNode,pcdNode,bimNode]     
week1=SessionNode(linkedNodes=nodeList)
print(week1.resource)
TriangleMesh with 12 points and 20 triangles.

The already serialized ifc model and its geometry can be efficiently retrieved from the graphPath

bimGraphPath= os.path.join(Path(os.getcwd()).parents[2],'test','testfiles','bimGraph1.ttl')
bimGraph=Graph().parse(bimGraphPath)
bimNodes=[]
for s in bimGraph.subjects(RDF.type):
    bimNodes.append(BIMNode(subject=s,graphPath=bimGraphPath,getResource=True))
[Open3D WARNING] Unable to load file d:\Scan-to-BIM repository\geomapi\test\testfiles\282_SC_f2_Round:Ø30:883870.obj with ASSIMP
[Open3D WARNING] Unable to load file d:\Scan-to-BIM repository\geomapi\test\testfiles\B1_CL.obj with ASSIMP
[Open3D WARNING] Unable to load file d:\Scan-to-BIM repository\geomapi\test\testfiles\Default Grid.obj with ASSIMP
[Open3D WARNING] Unable to load file d:\Scan-to-BIM repository\geomapi\test\testfiles\Precast Stair:Stair:1194239.obj with ASSIMP
[Open3D WARNING] Unable to load file d:\Scan-to-BIM repository\geomapi\test\testfiles\Precast Stair:Stair:1195754.obj with ASSIMP

next, we can easily asses whether the BIM geometries are contained within the convex hull of the session

BIMgeometries=[n.resource for n in bimNodes]
print(len(BIMgeometries))
100
inliers=gmu.get_mesh_inliers(sources=BIMgeometries, reference=week1.resource)
print(inliers)
[False, False, False, True, True, True, None, False, False, False, False, True, False, True, False, False, False, False, True, False, False, False, False, False, True, False, True, True, None, True, True, False, True, True, False, True, True, False, False, False, False, False, True, True, False, False, False, True, True, True, True, False, False, True, True, True, True, True, True, True, False, True, True, True, True, True, True, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, True, None, False, False, False, True, False, False, True, False, False, False, None, True, True, None, False]
inside=[g for i,g in enumerate(BIMgeometries) if inliers[i]==True]
outside=[g for i,g in enumerate(BIMgeometries) if inliers[i]!=True]
lineset1=o3d.geometry.LineSet.create_from_triangle_mesh(week1.resource)
lineset1.paint_uniform_color([1,0,0])
[g.paint_uniform_color([0,1,0]) for g in inside]
o3d.visualization.draw_geometries(inside + outside + [lineset1,pcdNode.resource])
LineSet with 30 lines.

rendering

subjects=[node.subject for i,node in enumerate(bimNodes) if inliers[i]==True]
print(len(subjects))
39
analysisNode1=SessionNode(subject=week1.subject,
                observableBIMNodes=subjects,
                isDerivedFromGeometry=week1.resource,
                analysisTimestamp=week1.timestamp)
analysisNode1.to_graph()
print(analysisNode1.graph.serialize())
@prefix omg: <https://w3id.org/omg#> .
@prefix v4d: <https://w3id.org/v4d/core#> .

<file:///c23e6f1b-1ef7-11ed-9a6f-c8f75043ce59> a v4d:SessionNode ;
    omg:isDerivedFromGeometry "TriangleMesh with 12 points and 20 triangles." ;
    v4d:analysisTimestamp "2022-03-13T13:55:30" ;
    v4d:observableBIMNodes "[rdflib.term.URIRef('file:///20946a35-1493-11ed-a3e0-c8f75043ce59'), rdflib.term.URIRef('file:///282_SC_f2_Rectangular_20_20_902338_0WczkhEJb5j9vKUsZ2C8Xb'), rdflib.term.URIRef('file:///282_SC_f2_Rectangular_50_20_1128324_3fzMBiyqn35x9PKxUuI9k5'), rdflib.term.URIRef('file:///282_SC_h2_Circular_Hollow_Sections_CHS219x8_874044_2VkCuCs_XE6Oa0y_VskRw_'), rdflib.term.URIRef('file:///282_SF_f2_Rectangular_20_45_883681_3vj_SCnYn1ygZxCAXgDJ8E'), rdflib.term.URIRef('file:///282_SF_f2_Rectangular_20_50_882150_1uRDEANfn5uQudsbYskAIQ'), rdflib.term.URIRef('file:///282_SF_f2_Rectangular_precast__20_20_910622_0xd_OlqWn3CPWz5_iJaRR3'), rdflib.term.URIRef('file:///282_SF_h2_H-Wide_Flange_HEM180_1087611_3X7b5jdTP3qPcQ8PFZIQyv'), rdflib.term.URIRef('file:///282_SF_h2_UPN_UPN240_1204348_3UjGj1LDDF4OdGVnMEh_PL'), rdflib.term.URIRef('file:///Basic_Wall_211_WA_Ff1_Glued_brickwork_sandlime_150mm_1118860_0KysUSO6T3_gOJKtAiUE7d'), rdflib.term.URIRef('file:///Basic_Wall_211_WA_Ff1_Glued_brickwork_sandlime_150mm_1119623_2KGJL8gr1CSxNi9E5OsTC5'), rdflib.term.URIRef('file:///Basic_Wall_211_WA_Ff1_Glued_brickwork_sandlime_214mm_871840_0xEj5E_xb0xuG9m0PNp3LQ'), rdflib.term.URIRef('file:///Basic_Wall_211_WA_Ff1_Glued_brickwork_sandlime_214mm_875067_0tYm_oWnH4EO9KOnrQ_LTZ'), rdflib.term.URIRef('file:///Basic_Wall_211_WA_Ff1_Glued_brickwork_sandlime_214mm_909296_1_1LnVDAG8H2GhtSGFUjv0cw'), rdflib.term.URIRef('file:///Basic_Wall_211_WA_Ff1_Glued_brickwork_sandlime_214mm_915343_0igX5ELYf27gUEXdl0c9BO'), rdflib.term.URIRef('file:///Basic_Wall_211_WA_Ff1_Glued_brickwork_sandlime_214mm_957051_1r0ua9H6vBcgWG64YK_7sP'), rdflib.term.URIRef('file:///Basic_Wall_211_WA_Ff1_Glued_brickwork_sandlime_214mm_957053_1r0ua9H6vBcgWG64YK_7sV'), rdflib.term.URIRef('file:///Basic_Wall_211_WA_Fg2_Masonry_perforated_brick_420mm_890813_1_1viIg84R51EQ3AC_ORnWPI'), rdflib.term.URIRef('file:///Basic_Wall_211_WA_Fg2_Masonry_perforated_brick_420mm_890813_35DaG1pibDM8EXj9_uGbw5'), rdflib.term.URIRef('file:///Basic_Wall_211_WA_f2_Concrete_interior_150mm_871551_27Y0jzhqDDngyHLzsbzFWM'), rdflib.term.URIRef('file:///Basic_Wall_211_WA_f2_Concrete_interior_200mm_1094351_3dwB2NAe1Bwg5iNk81I6l1'), rdflib.term.URIRef('file:///Basic_Wall_211_WA_f2_Concrete_interior_200mm_871479_14ggU2wNT39gT0cJEPcQ2T'), rdflib.term.URIRef('file:///Basic_Wall_211_WA_f2_Concrete_interior_200mm_871524_1MQAV8CnbF1gfS2oQu3BTB'), rdflib.term.URIRef('file:///Basic_Wall_211_WA_f2_Concrete_interior_200mm_871539_1naLsFzZjExgYlxSPIk5Fv'), rdflib.term.URIRef('file:///Basic_Wall_211_WA_f2_Concrete_interior_200mm_871546_27Y0jzhqDDngyHLzsbzFWJ'), rdflib.term.URIRef('file:///Basic_Wall_211_WA_f2_Concrete_interior_200mm_871605_36EK3dgtj5kvzLoe__DpK_'), rdflib.term.URIRef('file:///Basic_Wall_211_WA_f2_Concrete_interior_200mm_871610_36EK3dgtj5kvzLoe__DpKn'), rdflib.term.URIRef('file:///Basic_Wall_211_WA_f2_Concrete_interior_200mm_871615_36EK3dgtj5kvzLoe__DpKq'), rdflib.term.URIRef('file:///Basic_Wall_211_WA_f2_Concrete_interior_200mm_871640_3m98g9juf6duIoC__K0IiP'), rdflib.term.URIRef('file:///Basic_Wall_211_WA_f2_Concrete_interior_200mm_871749_24_a_Hzkf0MQ_ZwECgKMG1'), rdflib.term.URIRef('file:///Basic_Wall_211_WA_f2_Concrete_interior_200mm_872964_21RVJrNGr989BN7TXOhOb6'), rdflib.term.URIRef('file:///Basic_Wall_211_WA_f2_Concrete_interior_200mm_872969_21RVJrNGr989BN7TXOhObB'), rdflib.term.URIRef('file:///Basic_Wall_211_WA_f2_Concrete_interior_200mm_873082_1naLsFzZjExgYlxSPIk59_'), rdflib.term.URIRef('file:///Basic_Wall_211_WA_f2_Concrete_interior_200mm_892869_3_XZi7pMP9Ng_MI42c_pXP'), rdflib.term.URIRef('file:///Basic_Wall_211_WA_f2_Concrete_interior_250mm_881587_1i_PelBQv4sR9_JJI0Ie0o'), rdflib.term.URIRef('file:///Floor_232_FL_Compression_slab_150_CIP__200mm__957651_2IS4Dw5in8hOx8w270ibl_'), rdflib.term.URIRef('file:///Floor_232_FL_Wide_slab_50mm_1012024_37fvQW5Ab3jRe12QYYQFCn'), rdflib.term.URIRef('file:///Precast_Stair_Stair_1194239_Landing_1_0zVTcSmQj60w7eOLIJ1eQZ'), rdflib.term.URIRef('file:///Precast_Stair_Stair_1194239_Run_2_0zVTcSmQj60w7eOLIJ1eQX')]" .

These analyses nodes can be stored in seperate graphs and combined at later stages to be used in future analyses.