From 1eeee1b0a60462f1630362b5285364f808d45b1b Mon Sep 17 00:00:00 2001 From: LAIRNI Date: Wed, 5 Nov 2025 16:46:32 +0100 Subject: [PATCH 1/3] matching outated import --- src/openalea/rsml/matching.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/openalea/rsml/matching.py b/src/openalea/rsml/matching.py index 9848045..829eb20 100644 --- a/src/openalea/rsml/matching.py +++ b/src/openalea/rsml/matching.py @@ -20,7 +20,7 @@ def match_plants(t1,t2, max_distance=None): The matching is done usinf `one_to_one_match` """ - from operator import div + from operator import truediv as div # compute seed position of plants in t # ------------------------------------ From f7ce1fc76026ed46668380c2060bade605908729 Mon Sep 17 00:00:00 2001 From: LAIRNI Date: Wed, 12 Nov 2025 18:26:47 +0100 Subject: [PATCH 2/3] Parsing RootSystemTracker --- src/openalea/rsml/io.py | 75 ++++++++++++++++++++++++----------- src/openalea/rsml/metadata.py | 13 ++++-- 2 files changed, 62 insertions(+), 26 deletions(-) diff --git a/src/openalea/rsml/io.py b/src/openalea/rsml/io.py index 2bc1d8c..28c3aef 100644 --- a/src/openalea/rsml/io.py +++ b/src/openalea/rsml/io.py @@ -1,4 +1,4 @@ -""" XML SmartRoot / RootNav reader and writer +""" XML SmartRoot / RootNav / RootSystemTracker reader and writer TODO: * Manage metadata @@ -18,7 +18,7 @@ """ ############################################################################## -# XML SmartRoot / RootNav reader and writer +# XML SmartRoot / RootNav / RootSystemTracker reader and writer ############################################################################## from ast import literal_eval @@ -48,7 +48,17 @@ def parse(self, filename, debug=False): root = doc.getroot() # recursive call of the functions to add neww plants/root axis to the MTG self.dispatch(root) - + + graph = self._g + if graph.graph_properties().get('metadata', {}).get('functions') is None: + graph.graph_properties()['metadata']['functions'] = [] + if graph.properties().get('time'): + graph.graph_properties()['metadata']['functions'].append('time') + if graph.properties().get('time_hours'): + graph.graph_properties()['metadata']['functions'].append('time_hours') + if graph.properties().get('diameter'): + graph.graph_properties()['metadata']['functions'].append('diameter') + g = fat_mtg(self._g) # Add metadata as property of the graph @@ -87,6 +97,7 @@ def metadata(self, elts, **properties): meta = self._metadata = dict() gprop = self._g.graph_properties() #print([elt.tag for elt in elts]) + pixel_size = None for elt in elts: elt_tag = elt.tag #print(elt_tag) @@ -97,12 +108,20 @@ def metadata(self, elts, **properties): elif elt_tag in ['user','file-key','software','unit']: meta[elt_tag] = elt.text elif elt_tag in ["property-definitions","time-sequence","image",'private']: - #print(elt_tag) self.dispatch(elt) + elif elt_tag == "observation-hours": + elt_text = elt.text + meta[elt_tag] = [literal_eval(v) for v in elt_text.split(',') if v] + elif elt_tag in ['size', 'pixel_size']: + pixel_size = float(elt.text) # RootSystemTracker use size for pixel_size before image element D: elif elt_tag=='mtg_graph_properties': gprop.update(read_xml_tree(elt)) else: meta[elt_tag] = read_xml_tree(elt) + + if pixel_size: + meta['resolution'] = meta.get('image',{}) + meta['image']['resolution'] = pixel_size gprop['metadata'] = meta @@ -134,7 +153,7 @@ def function_definition(self, elts, **properties): label = prop.pop('label') if label: self._propdef[label]=prop - + def time_sequence(self, elts, **properties): """ A plant with parameters and a recursive structure. @@ -218,6 +237,7 @@ def polyline(self, elts, **properties): self._polyline = [] # will store all points in `elts` self._time = [] self._time_hours = [] + # self._diameter = [] for elt in elts: self.dispatch(elt) @@ -230,13 +250,16 @@ def polyline(self, elts, **properties): if self._time_hours : self._node.time_hours = self._time_hours self._time_hours = None - + # if self._diameter : + # self._node.diameter = self._diameter + # self._diameter = None def point(self, elts, **properties): poly = self._polyline point = [] times = self._time times_hours = self._time_hours + # diameters = self._diameter if properties: if 'x' in properties or 'coord_x' in properties: coords = ['x', 'y', 'z'] @@ -250,13 +273,14 @@ def point(self, elts, **properties): coords = ['th', 'coord_th'] time_hours = [float(properties[c]) for c in coords if c in properties] times_hours.append(time_hours[0]) + # if 'diameter' in properties: + # diameter = float(properties['diameter']) + # diameters.append(diameter) else: point = [float(elt.text) for elt in elts] poly.append(point) - #print('point', point) - - + #print('point', point) def functions(self, elts, **properties): """ A root axis with geometry, functions, properties. @@ -412,18 +436,26 @@ def mtg(self): def metadata(self): g = self._g self.xml_meta = xml.SubElement(self.xml_root,'metadata') - gmetadata = metadata.set_metadata(g) for tag in metadata.flat_metadata: self.SubElement(self.xml_meta, tag=tag, text=str(gmetadata[tag])) - + # image metadata - + self.observation_hours(gmetadata) self.image(gmetadata) self.property_definitions(gmetadata) # print('TODO: time-sequence') + def observation_hours(self,metadata): + """ dump observation-hours element of metadata """ + obs = metadata.get('observation-hours') # List of observation hours + if obs is None: return + + obs_elt = self.SubElement(self.xml_meta, 'observation-hours') + txt = ','.join(str(hour) for hour in obs) + obs_elt.text = txt + def image(self,metadata): """ dump image element of metadata """ image = metadata.get('image') @@ -485,8 +517,6 @@ def scene(self): # self.process_vertex(vid) - - def plant(self, vid): g = self._g @@ -512,16 +542,17 @@ def root(self, xml_parent, mtg_vid): self.xml_nodes[vid] = axis = self.SubElement(xml_parent, 'root') # set xml attributes - props = g[vid] + props = g[vid] axis.attrib['id'] = str(props.pop('id', vid)) axis.attrib['label'] = str(props.pop('label', g.label(vid))) if 'po:accession' in props: axis.attrib['po:accession'] = str(props.pop('po:accession')) # set xml axis element - self.properties(vid, axis) - ##self.functions(axis,**props) self.geometry(axis,**props) + self.functions(axis,**props) + self.properties(vid, axis) + # process children root axis # -------------------------- @@ -583,12 +614,12 @@ def functions(self, axis, **props): for tag in pname: if tag in props: if functions_elt is None: - functions_elt = self.SubElement(xml_elt, 'functions') + functions_elt = self.SubElement(axis, 'functions') function_elt = self.SubElement(functions_elt, 'function') function_elt.attrib['domain'] = 'polyline' - function_elt.attrib['name'] = tag + function_elt.attrib['name'] = tag - for sample in attrib[tag]: + for sample in props[tag]: sample_elt = self.SubElement(function_elt, 'sample') if isinstance(sample, (tuple, list)) and len(sample) == 2: sample_elt.attrib['position'] = str(sample[0]) @@ -596,8 +627,6 @@ def functions(self, axis, **props): else: sample_elt.attrib['value'] = str(sample) - - ########################################################################## # Wrapper functions for OpenAlea usage. @@ -621,4 +650,4 @@ def mtg2rsml(g, rsml_file): with open(rsml_file, 'wb') as f: # F. Bauget 2022-04-11: with python 3 xml.tostring(self.xml_root, encoding='UTF-8') gives bytes so I open in binary mode f.write(s) else: - rsml_file.write(s) + rsml_file.write(s) \ No newline at end of file diff --git a/src/openalea/rsml/metadata.py b/src/openalea/rsml/metadata.py index 6341c8f..e3c2791 100644 --- a/src/openalea/rsml/metadata.py +++ b/src/openalea/rsml/metadata.py @@ -25,14 +25,13 @@ function also fill missing items, folowing the specified behavior describe in the function documentation. """ -import xml.etree.ElementTree as xml - # ordered list of metadata attribute name flat_metadata = ['version','unit','resolution','software','user', 'last-modified','file-key'] metadata_names = flat_metadata + ['image', 'property-definitions', 'function-definitions', 'time-sequence', + 'observation-hours', 'private'] # default values @@ -91,9 +90,17 @@ def set_metadata(g): from os.path import getctime creation = getctime(image['name']) image['captured'] = datetime.fromtimestamp(creation).isoformat() + except KeyError: # not defined + pass except OSError: # no such file pass - + + if 'observation-hours' in metadata: + # table of observation times + obs = metadata['observation-hours'] + if isinstance(obs, str): + import ast + metadata['observation-hours'] = list(ast.literal_eval(obs)) # convert string to list if metadata['file-key']!=default['file-key']: import uuid From 5562db3a6b91a7f0ad1e9f3a01fa0eb6a8ba6f0a Mon Sep 17 00:00:00 2001 From: LAIRNI Date: Wed, 12 Nov 2025 18:34:02 +0100 Subject: [PATCH 3/3] Parsing RootSystemTracker* --- src/openalea/rsml/io.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/openalea/rsml/io.py b/src/openalea/rsml/io.py index 28c3aef..f559cb2 100644 --- a/src/openalea/rsml/io.py +++ b/src/openalea/rsml/io.py @@ -49,6 +49,7 @@ def parse(self, filename, debug=False): # recursive call of the functions to add neww plants/root axis to the MTG self.dispatch(root) + # if some functions are defined in the MTG properties but not in metadata, add them graph = self._g if graph.graph_properties().get('metadata', {}).get('functions') is None: graph.graph_properties()['metadata']['functions'] = []