From 1c376855f58ddb656caed60cd7e9a4376dc0efb4 Mon Sep 17 00:00:00 2001 From: pradal Date: Wed, 13 Mar 2024 14:33:13 +0100 Subject: [PATCH 01/14] Add time in bold and geenration of xlsx for both expertised data and normal one --- src/rsml/hirros.py | 37 ++++++++++++++++++++++++++++--------- 1 file changed, 28 insertions(+), 9 deletions(-) diff --git a/src/rsml/hirros.py b/src/rsml/hirros.py index 3b5b04d..1afce9c 100644 --- a/src/rsml/hirros.py +++ b/src/rsml/hirros.py @@ -14,16 +14,21 @@ def walk(dir: str, recursive=True): - """ + """ Traverse a set of directories and return rsml files. + + Notes : rsml files are defined by 61_graph*.rsml """ dir = Path(dir) - fns = None + fns = [] for rsml_file in ['61_graph_expertized.rsml', '61_graph.rsml']: - fns = dir.glob(rsml_file) - if not fns and recursive: - fns = dir.glob('*/%s'%rsml_file) - if fns: - break + rsmls = dir.glob(rsml_file) + if rsmls : + fns.extend(rsmls) + if recursive: + rsmls = dir.glob('*/%s'%rsml_file) + if rsmls : + fns.extend(rsmls) + if not fns: print("ERROR: no rsml files found") @@ -31,6 +36,8 @@ def walk(dir: str, recursive=True): for fn in fns: if (fn.parent/'80_graph_analysis.xlsx').exists(): continue + elif (fn.parent/'80_graph_expertized_analysis.xlsx').exists(): + continue else: final_fns.append(fn) return final_fns @@ -152,11 +159,19 @@ def write_xls(xls_file, obs, primaries, secondaries): sheet_name='Prim', float_format="%.2f", header = False) + workbook = writer.book + bold_format = workbook.add_format({'bold': True}) + + worksheet = writer.sheets['Prim'] + worksheet.set_row(0, None, bold_format) + for i, df in enumerate(dfs): df.to_excel(writer, sheet_name='RP%d'%(i+1), float_format="%.2f", header = False) + worksheet = writer.sheets['RP%d'%(i+1)] + worksheet.set_row(0, None, bold_format) def run(fn): @@ -174,7 +189,10 @@ def run(fn): secondaries.append(s2) # Write in xlsx - xlsx_file = fn.parent/'80_graph_analysis.xlsx' + if 'expertized' in fn: + xlsx_file = fn.parent/'80_graph_expertized_analysis.xlsx' + else: + xlsx_file = fn.parent/'80_graph_analysis.xlsx' write_xls(xlsx_file, obs, primaries=prims, secondaries=secondaries) @@ -208,7 +226,8 @@ def main(): if __name__=='__main__': - g = read('set_de_5/230403VS004') + fns = walk('set_de_5/230403VS004') + g = read(fns[0]) obs = times(g) plant_ids = g.vertices(scale=1) From 2dddaef021a1435ecf2356d05632113cfe515ede Mon Sep 17 00:00:00 2001 From: pradal Date: Wed, 20 Mar 2024 10:52:07 +0100 Subject: [PATCH 02/14] Add a new option for processing a set of boxes (GxE) and pulling all the data in a xlsx file --- src/rsml/hirros.py | 107 +++++++++++++++++++++++++++++++++++++-------- 1 file changed, 88 insertions(+), 19 deletions(-) diff --git a/src/rsml/hirros.py b/src/rsml/hirros.py index 1afce9c..e62567c 100644 --- a/src/rsml/hirros.py +++ b/src/rsml/hirros.py @@ -107,8 +107,7 @@ def length_and_number(secondary): return total_length.tolist(), total_number.tolist() -def write_xls(xls_file, obs, primaries, secondaries): - +def dataframes(obs, primaries, secondaries): data = [obs] index=['time(h)'] for i, p in enumerate(primaries): @@ -127,8 +126,6 @@ def write_xls(xls_file, obs, primaries, secondaries): dfs= [] - - for sec in secondaries: data = [obs] @@ -151,7 +148,10 @@ def write_xls(xls_file, obs, primaries, secondaries): df = pd.DataFrame(data, index = index) dfs.append(df) - #print(dfp) + return dfp, dfs + +def write_xls(xls_file, dfp, dfs): + if xls_file: with pd.ExcelWriter(xls_file, engine="xlsxwriter") as writer: @@ -173,9 +173,39 @@ def write_xls(xls_file, obs, primaries, secondaries): worksheet = writer.sheets['RP%d'%(i+1)] worksheet.set_row(0, None, bold_format) +def write_xls_all(xls_file, primaries, secondaries): -def run(fn): - g = read(fn) + with pd.ExcelWriter(xls_file, engine="xlsxwriter") as writer: + startrow = 0 + for dfp in primaries: + + dfp.to_excel(writer, + sheet_name='Prim', + float_format="%.2f", + header = False, + startrow=startrow) + workbook = writer.book + bold_format = workbook.add_format({'bold': True}) + + worksheet = writer.sheets['Prim'] + worksheet.set_row(startrow, None, bold_format) + + startrow += len(dfp)+2 + + for i in secondaries: + dfs = secondaries[i] + startrow = 0 + for df in dfs: + df.to_excel(writer, + sheet_name='RP%d'%(i+1), + float_format="%.2f", + header = False, + startrow=startrow) + worksheet = writer.sheets['RP%d'%(i+1)] + worksheet.set_row(startrow, None, bold_format) + startrow += len(df)+2 + +def process(g): obs = times(g) plant_ids = g.vertices(scale=1) @@ -188,41 +218,80 @@ def run(fn): s2 = secondary(g, pid, obs) secondaries.append(s2) + df_primary, df_secondaries = dataframes(obs, prims, secondaries) + return df_primary, df_secondaries + +def run(fn): + g = read(fn) + dfp, dfs = process(g) + # Write in xlsx if 'expertized' in fn: xlsx_file = fn.parent/'80_graph_expertized_analysis.xlsx' else: xlsx_file = fn.parent/'80_graph_analysis.xlsx' - write_xls(xlsx_file, - obs, primaries=prims, secondaries=secondaries) + write_xls(xlsx_file, dfp, dfs) print('WRITE %s'%xlsx_file) +def run_all(fns): + + prims = [] + secondaries = {} + + for fn in fns: + g = read(fn) + dfp, dfs = process(g) + prims.append(dfp) + for i, df in enumerate(dfs): + secondaries.setdefault(i, []).append(df) + + write_xls_all('gxe_results.xlsx', prims, secondaries) + print('WRITE gxe_results.xlsx') + def main(): parser = argparse.ArgumentParser() parser.add_argument('-d', default='.', help='directory to process') parser.add_argument('-r', default=True, type=bool, help='traverse the directories recursively') + parser.add_argument('-f', help='text file containing the list of directories to process') args = parser.parse_args() dir = args.d recursive = args.r + gxe_file = args.f - fns = walk(dir=dir, recursive=recursive) - if not fns: - return - - if len(fns) == 1: - fn = fns[0] - print('Process file %s'%(fns[0])) - run(fn) - + fns = [] + if gxe_file: + print('GxE file to process', gxe_file) + dirs = [] + with open('gxe.txt', 'r') as gxe: + dirs = [d.strip() for d in gxe] + dirs = [d for d in dirs if Path(d).exists()] + + for d in dirs: + rsml_files = walk(dir=d, recursive=recursive) + fns.extend(rsml_files) + + print('Process files %s'%(' '.join(fns))) + run_all(fns) else: - for fn in fns: + fns = walk(dir=dir, recursive=recursive) + + if not fns: + return + + if len(fns) == 1: + fn = fns[0] print('Process file %s'%(fns[0])) run(fn) + else: + for fn in fns: + print('Process file %s'%(fns[0])) + run(fn) + if __name__=='__main__': From e5cccd25f66061b21e05d4746efd6048e9d537cc Mon Sep 17 00:00:00 2001 From: pradal Date: Wed, 20 Mar 2024 11:15:44 +0100 Subject: [PATCH 03/14] fix bug --- src/rsml/hirros.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/rsml/hirros.py b/src/rsml/hirros.py index e62567c..9d2e4bd 100644 --- a/src/rsml/hirros.py +++ b/src/rsml/hirros.py @@ -266,7 +266,7 @@ def main(): if gxe_file: print('GxE file to process', gxe_file) dirs = [] - with open('gxe.txt', 'r') as gxe: + with open(gxe_file, 'r') as gxe: dirs = [d.strip() for d in gxe] dirs = [d for d in dirs if Path(d).exists()] From 841cf90823df3601d00328627ec2983618ae539b Mon Sep 17 00:00:00 2001 From: pradal Date: Wed, 20 Mar 2024 11:30:05 +0100 Subject: [PATCH 04/14] Update hirros --- src/rsml/hirros.py | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/src/rsml/hirros.py b/src/rsml/hirros.py index 9d2e4bd..52bb18d 100644 --- a/src/rsml/hirros.py +++ b/src/rsml/hirros.py @@ -13,7 +13,7 @@ import rsml -def walk(dir: str, recursive=True): +def walk(dir: str, recursive=True, force=False): """ Traverse a set of directories and return rsml files. Notes : rsml files are defined by 61_graph*.rsml @@ -21,6 +21,8 @@ def walk(dir: str, recursive=True): dir = Path(dir) fns = [] for rsml_file in ['61_graph_expertized.rsml', '61_graph.rsml']: + if fns and force: + continue rsmls = dir.glob(rsml_file) if rsmls : fns.extend(rsmls) @@ -34,12 +36,13 @@ def walk(dir: str, recursive=True): final_fns = [] for fn in fns: - if (fn.parent/'80_graph_analysis.xlsx').exists(): - continue - elif (fn.parent/'80_graph_expertized_analysis.xlsx').exists(): - continue - else: - final_fns.append(fn) + if not force: + if (fn.parent/'80_graph_analysis.xlsx').exists(): + continue + elif (fn.parent/'80_graph_expertized_analysis.xlsx').exists(): + continue + final_fns.append(fn) + return final_fns def read(fn): @@ -271,7 +274,7 @@ def main(): dirs = [d for d in dirs if Path(d).exists()] for d in dirs: - rsml_files = walk(dir=d, recursive=recursive) + rsml_files = walk(dir=d, recursive=recursive, force=True) fns.extend(rsml_files) print('Process files %s'%(' '.join(fns))) From 39dbbcbcf364d4b1683b8d0b8842667bd65c5d1a Mon Sep 17 00:00:00 2001 From: pradal Date: Wed, 20 Mar 2024 11:52:47 +0100 Subject: [PATCH 05/14] add plant name and file name --- src/rsml/hirros.py | 43 +++++++++++++++++++++++-------------------- 1 file changed, 23 insertions(+), 20 deletions(-) diff --git a/src/rsml/hirros.py b/src/rsml/hirros.py index 52bb18d..74a3423 100644 --- a/src/rsml/hirros.py +++ b/src/rsml/hirros.py @@ -42,7 +42,7 @@ def walk(dir: str, recursive=True, force=False): elif (fn.parent/'80_graph_expertized_analysis.xlsx').exists(): continue final_fns.append(fn) - + return final_fns def read(fn): @@ -176,37 +176,41 @@ def write_xls(xls_file, dfp, dfs): worksheet = writer.sheets['RP%d'%(i+1)] worksheet.set_row(0, None, bold_format) -def write_xls_all(xls_file, primaries, secondaries): +def write_xls_all(xls_file, fns, primaries, secondaries): + with pd.ExcelWriter(xls_file, engine="xlsxwriter") as writer: startrow = 0 - for dfp in primaries: + for i, dfp in enumerate(primaries): dfp.to_excel(writer, sheet_name='Prim', float_format="%.2f", header = False, - startrow=startrow) + startrow=startrow+1) workbook = writer.book bold_format = workbook.add_format({'bold': True}) worksheet = writer.sheets['Prim'] - worksheet.set_row(startrow, None, bold_format) - - startrow += len(dfp)+2 + worksheet.set_row(startrow+1, None, bold_format) + worksheet.write(startrow, 0, fns[i]) + startrow += len(dfp)+3 - for i in secondaries: - dfs = secondaries[i] - startrow = 0 - for df in dfs: + startrow = 0 + for i, dfs in enumerate(secondaries): + name = fns[i] + for j, df in enumerate(dfs): df.to_excel(writer, - sheet_name='RP%d'%(i+1), + sheet_name='Lateral', float_format="%.2f", header = False, - startrow=startrow) - worksheet = writer.sheets['RP%d'%(i+1)] - worksheet.set_row(startrow, None, bold_format) - startrow += len(df)+2 + startrow=startrow+1) + worksheet = writer.sheets['Lateral'] + worksheet.set_row(startrow+1, None, bold_format) + worksheet.write(startrow, 0, name) + worksheet.write(startrow, 1, 'Plant %d'%j) + + startrow += len(df)+3 def process(g): obs = times(g) @@ -240,16 +244,15 @@ def run(fn): def run_all(fns): prims = [] - secondaries = {} + secondaries = [] for fn in fns: g = read(fn) dfp, dfs = process(g) prims.append(dfp) - for i, df in enumerate(dfs): - secondaries.setdefault(i, []).append(df) + secondaries.append(dfs) - write_xls_all('gxe_results.xlsx', prims, secondaries) + write_xls_all('gxe_results.xlsx', fns, prims, secondaries) print('WRITE gxe_results.xlsx') def main(): From 4feed19f5301f183af39810ca794d45108f3c3cf Mon Sep 17 00:00:00 2001 From: pradal Date: Wed, 20 Mar 2024 11:55:24 +0100 Subject: [PATCH 06/14] add plant name and file name --- src/rsml/hirros.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/rsml/hirros.py b/src/rsml/hirros.py index 74a3423..415bc97 100644 --- a/src/rsml/hirros.py +++ b/src/rsml/hirros.py @@ -208,7 +208,7 @@ def write_xls_all(xls_file, fns, primaries, secondaries): worksheet = writer.sheets['Lateral'] worksheet.set_row(startrow+1, None, bold_format) worksheet.write(startrow, 0, name) - worksheet.write(startrow, 1, 'Plant %d'%j) + worksheet.write(startrow, 1, 'Plant %d'%(j+1)) startrow += len(df)+3 From 7ae26d4cad06e1f452b52ca997ce7c6d98837d17 Mon Sep 17 00:00:00 2001 From: pradal Date: Tue, 10 Jun 2025 13:27:28 +0200 Subject: [PATCH 07/14] Up --- src/rsml/hirros.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/rsml/hirros.py b/src/rsml/hirros.py index 415bc97..b033540 100644 --- a/src/rsml/hirros.py +++ b/src/rsml/hirros.py @@ -255,6 +255,16 @@ def run_all(fns): write_xls_all('gxe_results.xlsx', fns, prims, secondaries) print('WRITE gxe_results.xlsx') +def merge_and_compute_primaries(dataframes): + """Mean of Mean + or mean of total Length and total number""" + pass + +def merge_and_compute_secondaries(dataframes): + """Mean of Mean + or mean of total Length and total number""" + pass + def main(): parser = argparse.ArgumentParser() From 9425d09ef87586dff49a1b95c89f63c237fb7273 Mon Sep 17 00:00:00 2001 From: LAIRNI Date: Thu, 24 Jul 2025 13:50:33 +0200 Subject: [PATCH 08/14] Matching outdated --- src/rsml/matching.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/rsml/matching.py b/src/rsml/matching.py index 2ae855d..489f454 100644 --- a/src/rsml/matching.py +++ b/src/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 1eeee1b0a60462f1630362b5285364f808d45b1b Mon Sep 17 00:00:00 2001 From: LAIRNI Date: Wed, 5 Nov 2025 16:46:32 +0100 Subject: [PATCH 09/14] 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 10/14] 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 11/14] 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'] = [] From c8a35fd07b4ea4b5da8f232ab6064a77f8217e04 Mon Sep 17 00:00:00 2001 From: pradal Date: Wed, 10 Dec 2025 17:51:31 +0100 Subject: [PATCH 12/14] Now observation time is converted into values. Change the code accordingly --- src/openalea/rsml/hirros.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/openalea/rsml/hirros.py b/src/openalea/rsml/hirros.py index 21edf44..5301cf1 100644 --- a/src/openalea/rsml/hirros.py +++ b/src/openalea/rsml/hirros.py @@ -51,7 +51,7 @@ def read(fn): def times(g): """Return Observation dates in hours.""" - obs_t = g._graph_properties['metadata']['observation-hours'].split(',') + obs_t = g._graph_properties['metadata']['observation-hours'] observations = [float(t) for t in obs_t] return observations From 89011631c844b13752cf0b2032c5108260c62172 Mon Sep 17 00:00:00 2001 From: pradal Date: Wed, 10 Dec 2025 17:58:54 +0100 Subject: [PATCH 13/14] Fix bug in metadata parsing --- src/openalea/rsml/io.py | 5 +++-- test/test_hirros.py | 6 +++++- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/src/openalea/rsml/io.py b/src/openalea/rsml/io.py index f559cb2..d6adb3c 100644 --- a/src/openalea/rsml/io.py +++ b/src/openalea/rsml/io.py @@ -121,8 +121,9 @@ def metadata(self, elts, **properties): meta[elt_tag] = read_xml_tree(elt) if pixel_size: - meta['resolution'] = meta.get('image',{}) - meta['image']['resolution'] = pixel_size + # BUG : resolution now is similar to image + meta['resolution'] = pixel_size + meta.setdefault('image',{})['resolution'] = pixel_size gprop['metadata'] = meta diff --git a/test/test_hirros.py b/test/test_hirros.py index a3afbe8..2ca92bc 100644 --- a/test/test_hirros.py +++ b/test/test_hirros.py @@ -26,7 +26,11 @@ def test_observations(): plant_ids = g.vertices(scale=1) prims = hirros.primaries(g, plant_ids, obs) - +def test_fix_bug_resolution(): + g = get_mtg() + meta = g._graph_properties['metadata'] + assert meta['resolution'] == 76. + assert meta['image'] != meta['resolution'] def test1(): "Returns secondary dataframe" From 78e49cf181b3880119301f15d5cdcb9aa708ec03 Mon Sep 17 00:00:00 2001 From: pradal Date: Wed, 10 Dec 2025 18:34:55 +0100 Subject: [PATCH 14/14] Add this dataset for old format --- test/data/UC1_1PN001_old_format.rsml | 2112 ++++++++++++++++++++++++++ 1 file changed, 2112 insertions(+) create mode 100644 test/data/UC1_1PN001_old_format.rsml diff --git a/test/data/UC1_1PN001_old_format.rsml b/test/data/UC1_1PN001_old_format.rsml new file mode 100644 index 0000000..688ec9f --- /dev/null +++ b/test/data/UC1_1PN001_old_format.rsml @@ -0,0 +1,2112 @@ + + +1.4 +pixel(um) +76.0 +18-07-2023 11:22:19 +RootSystemTracker +Unknown +61_graph_expertized +0.0,13.6589,19.6578,25.6575,31.6572,37.6574,43.6565,49.6553,55.6572,61.6557,67.6562,73.6558,79.6562,85.6552,91.6561,95.4315,101.4321,107.433,113.432,119.4305,125.4316,131.4299,137.43,143.4312,158.606,164.601,170.6014,176.6003,182.5998 + + +Nothing there + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +