From 4f9a2a5816134f417f15a6bde01c32a432e978d1 Mon Sep 17 00:00:00 2001 From: George Marchment <georgemarchment@yahoo.fr> Date: Wed, 18 Jun 2025 15:28:14 +0200 Subject: [PATCH] Added Ro-crate generation --- src/code_.py | 3 + src/include.py | 3 + src/main.py | 55 +++++- src/nextflow_building_blocks.py | 14 +- src/nextflow_file.py | 19 ++ src/process.py | 56 ++++++ src/ro_crate.py | 297 +++++++++++++++++++++++++++----- src/subworkflow.py | 62 ++++++- src/workflow.py | 15 ++ 9 files changed, 475 insertions(+), 49 deletions(-) diff --git a/src/code_.py b/src/code_.py index 3b2ead5..5a9ee7e 100644 --- a/src/code_.py +++ b/src/code_.py @@ -476,4 +476,7 @@ class Code: def get_file_address(self): return self.origin.get_file_address() + + def get_nextflow_file(self): + return self.origin.get_nextflow_file() \ No newline at end of file diff --git a/src/include.py b/src/include.py index a86fdd9..ce217e8 100644 --- a/src/include.py +++ b/src/include.py @@ -38,6 +38,9 @@ class Include(Nextflow_Building_Blocks): def get_duplicate_status(self): return self.nextflow_file_origin.get_duplicate_status() + def get_nextflow_file(self): + return self.nextflow_file + #def get_list_name_includes(self): # if(self.get_duplicate_status()): diff --git a/src/main.py b/src/main.py index b6514af..e12a175 100644 --- a/src/main.py +++ b/src/main.py @@ -98,6 +98,9 @@ class Main(Nextflow_Building_Blocks): def get_file_address(self): return self.nextflow_file.get_file_address() + + def get_nextflow_file(self): + return self.nextflow_file def get_output_dir(self): @@ -254,4 +257,54 @@ class Main(Nextflow_Building_Blocks): def check_that_a_channel_is_not_defined_used_and_redefined_used_in_another_block(self): - return self.root.check_that_a_channel_is_not_defined_used_and_redefined_used_in_another_block() \ No newline at end of file + return self.root.check_that_a_channel_is_not_defined_used_and_redefined_used_in_another_block() + + + + #========================================================= + #-----------------------RO-CRATE-------------------------- + #========================================================= + + + def add_2_rocrate(self, dico): + #By definition we add this info + dico["@graph"].append({ "@id": "#nextflow", "@type": "ComputerLanguage", "name": "Nextflow", "identifier": { "@id": "https://www.nextflow.io/" }, "url": { "@id": "https://www.nextflow.io/" }}) + parent_key = self.nextflow_file.get_file_rocrate_key(dico) + self.nextflow_file.add_computational_workflow_to_types(dico) + main_key = f"{parent_key}#MAIN" + dico_main = get_dico_from_tab_from_id(dico, main_key) + if(dico_main==None): + dico_main = {} + dico_main["@id"] = main_key + dico_main["name"] = "Main Workflow" + dico_main["@type"] = ["SoftwareSourceCode", "ComputationalWorkflow"] + #TODO -> check if this remains true + #dico_main["conformsTo"] = {"@id": "https://bioschemas.org/profiles/ComputationalWorkflow/0.5-DRAFT-2020_07_21"} + #dico_main["dct:conformsTo"]= "https://bioschemas.org/profiles/ComputationalWorkflow/1.0-RELEASE/" + dico_main["input"] = [] + dico_main["output"] = [] + dico_main["isPartOf"] = [{"@id": parent_key}] + dico_main["hasPart"] = [] + called = [] + for call in self.root.get_all_calls_from_root(): + called.append(call.get_first_element_called()) + for c in called: + c.add_2_rocrate(dico, main_key) + dico_main["hasPart"].append({"@id":c.get_rocrate_key(dico)}) + + dico["@graph"].append(dico_main) + self.nextflow_file.add_to_has_part(dico, main_key) + + #Remove duplicates from dico["@graph"] + duplicates = [] + seen = [] + for ele in dico["@graph"]: + if(ele in seen): + duplicates.append(ele) + else: + seen.append(ele) + for ele in duplicates: + dico["@graph"].remove(ele) + + + \ No newline at end of file diff --git a/src/nextflow_building_blocks.py b/src/nextflow_building_blocks.py index aba604f..c27c2d0 100644 --- a/src/nextflow_building_blocks.py +++ b/src/nextflow_building_blocks.py @@ -51,6 +51,13 @@ class Nextflow_Building_Blocks: def get_file_address(self): return self.origin.get_file_address() + def get_nextflow_file(self): + try: + return self.origin.get_file_address() + except: + return self.nextflow_file + + def get_display_info(self): return self.origin.get_display_info() @@ -91,10 +98,13 @@ class Nextflow_Building_Blocks: def get_rocrate_key(self, dico): - return f"{self.get_file_address()[len(dico['temp_directory'])+1:]}#{self.get_name()}" + return f"{str(self.get_file_address())[len(dico['temp_directory']):]}#{self.get_name()}" def get_file_address(self): - return self.origin.get_file_address() + try: + return self.origin.get_file_address() + except: + return self.nextflow_file.get_file_address() def get_workflow_address(self): return self.origin.get_workflow_address() diff --git a/src/nextflow_file.py b/src/nextflow_file.py index 14c6fa2..f4aef0e 100644 --- a/src/nextflow_file.py +++ b/src/nextflow_file.py @@ -29,6 +29,7 @@ class Nextflow_File(Nextflow_Building_Blocks): self.subworkflows = [] self.functions = [] self.initialised = False + self.added_2_rocrate = False contents = check_file_exists(self.get_file_address(), self) Nextflow_Building_Blocks.__init__(self, contents, initialise_code=True) self.check_file_correctness() @@ -59,6 +60,9 @@ class Nextflow_File(Nextflow_Building_Blocks): def get_file_address(self): return Path(os.path.normpath(self.address)) + def get_nextflow_file(self): + return self + def get_DSL(self): return self.workflow.get_DSL() @@ -395,3 +399,18 @@ class Nextflow_File(Nextflow_Building_Blocks): else: raise Exception("This shouldn't happen") + + def add_to_has_part(self, dico, to_add_key): + file_name = str(self.get_file_address())[len(dico["temp_directory"]):] + file_dico = get_dico_from_tab_from_id(dico, file_name) + file_dico["hasPart"].append({"@id":to_add_key}) + + + def add_computational_workflow_to_types(self, dico): + file_name = str(self.get_file_address())[len(dico["temp_directory"]):] + file_dico = get_dico_from_tab_from_id(dico, file_name) + file_dico["@type"].append("ComputationalWorkflow") + + def get_file_rocrate_key(self, dico): + file_name = str(self.get_file_address())[len(dico["temp_directory"]):] + return file_name diff --git a/src/process.py b/src/process.py index e887430..3ffb410 100644 --- a/src/process.py +++ b/src/process.py @@ -593,5 +593,61 @@ class Process(Nextflow_Building_Blocks): return self.origin.get_all_conditions(conditions) + def add_2_rocrate(self, dico, parent_key): + process_key = self.get_rocrate_key(dico) + dico_process = get_dico_from_tab_from_id(dico, process_key) + if(dico_process==None): + dico_process = {} + dico_process["@id"] = process_key + dico_process["name"] = f"Process#{self.get_alias()}" + dico_process["@type"] = ["SoftwareSourceCode"] + #ADD INPUTS + dico_process["input"] = [] + for input in self.get_inputs(): + if(type(input)==str): + name_input = input + else: + name_input = input.get_code() + dico_input = get_dico_from_tab_from_id(dico, name_input) + if(dico_input==None): + dico_input = {"@id":f"#{name_input}", "name": name_input, "@type": "FormalParameter"} + dico["@graph"].append(dico_input) + dico_process["input"].append({"@id":dico_input["@id"]}) + #ADD OUTPUTS + dico_process["output"] = [] + for output in self.get_outputs(): + if(type(output)==str): + name_output = output + else: + name_output = output.get_code() + dico_output = get_dico_from_tab_from_id(dico, name_output) + if(dico_output==None): + dico_output = {"@id":f"#{name_output}", "name": name_output, "@type": "FormalParameter"} + dico["@graph"].append(dico_output) + dico_process["output"].append({"@id":dico_output["@id"]}) + #ADD isPartOf + dico_process["isPartOf"] = [] + dico_process["isPartOf"].append({"@id":parent_key}) + #ADD hasPart + dico_process["hasPart"] = [] + for tool in self.get_tools(): + dico_tool = get_dico_from_tab_from_id(dico, tool) + if(dico_tool==None): + dico_tool = {"@id":tool, + "name": tool, + "@type": "Tool" + #TODO in later versions + , "url": f"https://bio.tools/t?page=1&q={tool}&sort=score" + #, "identifier": "tool_identifier" + } + dico["@graph"].append(dico_tool) + dico_process["hasPart"].append({"@id":dico_tool["@id"]}) + + dico["@graph"].append(dico_process) + else: + if(not check_if_element_in_tab_rocrate(dico_process["isPartOf"], parent_key)): + dico_process["isPartOf"].append({"@id":parent_key}) + self.get_nextflow_file().add_to_has_part(dico, process_key) + diff --git a/src/ro_crate.py b/src/ro_crate.py index 186c53f..935f541 100644 --- a/src/ro_crate.py +++ b/src/ro_crate.py @@ -19,19 +19,228 @@ from . import constant class RO_Crate: - def __init__(self, workflow): + def __init__(self, workflow, personnal_acces_token = None, + display_info=False, + datePublished=None, description=None, + license=None, authors = None, + publisher = None, keywords = None, + producer = None): self.workflow = workflow - self.directory = '/'.join(workflow.get_file_address().split('/')[:-1]) + self.directory = workflow.get_root_directory() + self.personnal_acces_token = personnal_acces_token + self.display_info = display_info self.files = [] self.dico = {} + self.info_dico_workflow = {} + self.log = "" self.dico["temp_directory"] = self.directory + self.datePublished = datePublished + self.description = description + self.license = license + self.authors = authors + self.publisher = publisher + self.keywords = keywords + self.producer = producer + + self.fill_log() + self.workflow_git_name = self.set_address() + self.fill_info_dico_workflow() + + def set_address(self): + address = "" + current_directory = os.getcwd() + os.chdir(self.directory) + try: + os.system(f"git ls-remote --get-url origin > temp_address_{id(self)}.txt") + with open(f'temp_address_{id(self)}.txt') as f: + address = f.read() + os.system(f"rm temp_address_{id(self)}.txt") + except: + None + os.chdir(current_directory) + for match in re.finditer(r"https:\/\/github\.com\/([^\.]+)\.git", address): + address = match.group(1) + return address + return "" + + def fill_info_dico_workflow(self): + current_directory = os.getcwd() + os.chdir(self.directory) + try: + if(self.personnal_acces_token!=None): + command = f'curl --silent --request GET --url "https://api.github.com/repos/{self.workflow_git_name}" --header "Authorization: Bearer {self.personnal_acces_token}" --header "X-GitHub-Api-Version: 2022-11-28" > temp_dico_{id(self)}.json' + else: + command = f'curl --silent --request GET --url "https://api.github.com/repos/{self.workflow_git_name}" > temp_dico_{id(self)}.json' + _ = os.system(command) + with open(f'temp_dico_{id(self)}.json') as json_file: + self.info_dico_workflow = json.load(json_file) + os.system(f"rm temp_dico_{id(self)}.json") + + except: + _ = os.system(f"rm temp_dico_{id(self)}.json") + if(self.display_info): + if(self.info_dico_workflow=={}): + print("Unable to retrieve information regarding the commits") + else: + print("Successfully retrieved information regarding the commits") + os.chdir(current_directory) + + + def fill_log(self): + """Method that reads the git log and saves it + + Keyword arguments: + + """ + current_directory = os.getcwd() + os.chdir(self.directory) + try: + + os.system(f"git log --reverse > temp_{id(self)}.txt") + with open(f'temp_{id(self)}.txt') as f: + self.log = f.read() + os.system(f"rm temp_{id(self)}.txt") + except: + None + if(self.display_info): + if(self.log==""): + print("Unable to retrieve the git log") + else: + print("Successfully retrieved the git log") + os.chdir(current_directory) def get_files(self): self.files = glob.glob(f'{self.directory}/**/*.*', recursive=True) tab_files = [] for file in self.files: - tab_files.append({"@id":file[len(self.directory)+1:]}) + tab_files.append({"@id":file[len(self.directory):]}) return tab_files + + #Format yyyy-mm-dd + #Here i return the first commit date + def get_datePublished(self): + """Method that returns the date of publication + + Keyword arguments: + + """ + if(self.datePublished==None): + for match in re.finditer(r"Date: +\w+ +(\w+) +(\d+) +\d+:\d+:\d+ +(\d+)",self.log): + month = constant.month_mapping[match.group(1)] + day = match.group(2) + year = match.group(3) + if(int(month)<10 and len(month)==1): + month = f"0{month}" + if(int(day)<10): + day = f"0{day}" + return f"{year}-{month}-{day}" + else: + return self.datePublished + + def get_description(self): + """Method that returns the description + + Keyword arguments: + + """ + if(self.description==None): + try: + res = self.info_dico_workflow["description"] + except: + res = None + return res + else: + return self.description + + def get_license(self): + """Method that returns the license + + Keyword arguments: + + """ + if(self.license==None): + try: + res = self.info_dico_workflow["license"]["key"] + except: + res = None + return res + else: + return self.license + + def get_authors(self): + """Method that returns a list of the authors + + Keyword arguments: + + """ + if(self.authors==None): + authors = {} + for match in re.finditer(r"Author: ([^>]+)<([^>]+)>",self.log): + authors[match.group(2)] = match.group(1).strip() + tab = [] + for author in authors: + #tab.append({"@id":author, "name":authors[author]}) + tab.append({"@id":authors[author], "email":author}) + return tab + else: + authors = self.authors.split(',') + tab = [] + for a in authors: + tab.append({"@id":a.strip()}) + return tab + + def get_publisher(self): + """Method that returns the publisher + + Keyword arguments: + + """ + if(self.publisher==None): + if(self.info_dico_workflow!={}): + return "https://github.com/" + else: + return None + else: + self.publisher + + #TODO + def get_creativeWorkStatus(self): + return "TODO" + + #TODO + def get_version(self): + return "TODO" + + #Need to follow this format : "rna-seq, nextflow, bioinformatics, reproducibility, workflow, reproducible-research, bioinformatics-pipeline" + def get_keywords(self): + """Method that returns the keywords + + Keyword arguments: + + """ + if(self.keywords==None): + try: + res = ", ".join(self.info_dico_workflow["topics"]) + except: + res = None + return res + else: + return self.keywords + + def get_producer(self): + """Method that returns the producer + + Keyword arguments: + + """ + if(self.producer==None): + try: + res = {"@id": str(self.dico["owner"]["login"])} + except: + res = None + return res + else: + return self.producer def initialise_dico(self): self.dico["@context"] = "https://w3id.org/ro/crate/1.1/context" @@ -51,88 +260,89 @@ class RO_Crate: root["@id"] = "./" root["@type"] = "Dataset" root["name"] = self.workflow.get_name() - root["datePublished"] = self.workflow.get_datePublished() - root["description"] = self.workflow.get_description() - root["mainEntity"] = {"@id": self.workflow.get_main_file()} + root["datePublished"] = self.get_datePublished() + root["description"] = str(self.get_description()) + root["mainEntity"] = {"@id": str(self.workflow.get_first_file().get_file_address()).split("/")[-1]} #, "@type":["File", "SoftwareSourceCode"]} #We do not consider a File as a "ComputationalWorkflow" since multiple (sub)workflows can be defined in a same file - root["license"] = {"@id":self.workflow.get_license()} - authors = self.workflow.get_authors() - tab_authors = [] + root["license"] = {"@id":str(self.get_license())} + authors = self.get_authors() + tab_authors, tab_authors_ids= [], [] for author in authors: + id_author = f'#{"_".join(author["@id"].split())}' + tab_authors_ids.append({"@id":id_author}) try: #tab_authors.append({"@id":author["@id"], "email":author["email"]}) - tab_authors.append({"@id":f'#{"_".join(author["@id"].split())}', "@name":author["@id"],"email":author["email"]}) + tab_authors.append({"@id":id_author, "@type": ["Person"], "name":author["@id"],"email":author["email"]}) except: #tab_authors.append({"@id":author["@id"]}) - tab_authors.append({"@id":f'#{"_".join(author["@id"].split())}', "@name":author["@id"]}) - root["author"] = tab_authors - root["maintainer"] = tab_authors #Right now i'm assuming that all the authors are maintainers + tab_authors.append({"@id":id_author, "@type": ["Person"], "name":author["@id"]}) + self.dico["@graph"]+=tab_authors + root["author"] = tab_authors_ids + root["maintainer"] = tab_authors_ids #Right now i'm assuming that all the authors are maintainers files = self.get_files() tab_files = [] for file in files: tab_files.append({"@id":file["@id"]}) root["hasPart"] = tab_files - root["publisher"] = {"@id":self.workflow.get_publisher()} + publisher = str(self.get_publisher()) + root["publisher"] = {"@id":publisher} + self.dico["@graph"].append({"@id":publisher, "@type":["Organization"]}) #subjectOf TODO root["subjectOf"] = None - root["creativeWorkStatus"] = self.workflow.get_creativeWorkStatus() - root["@version"] = self.workflow.get_version() - root["keywords"] = self.workflow.get_keywords() - root["producer"] = self.workflow.get_producer() + root["creativeWorkStatus"] = self.get_creativeWorkStatus() + root["version"] = self.get_version() + root["keywords"] = self.get_keywords() + root["producer"] = self.get_producer() self.dico["@graph"].append(root) #TODO def get_programming_language(self, file): if(file[-3:]==".nf"): - return "https://w3id.org/workflowhub/workflow-ro-crate#nextflow" + #return "https://w3id.org/workflowhub/workflow-ro-crate#nextflow" + return "#nextflow" return None def get_contentSize(self, file): file_stats = os.stat(file) return file_stats.st_size/1e3 - def fill_log_file(self, file, reverse = True): - info = "" - current_directory = os.getcwd() - os.chdir("/".join(self.workflow.nextflow_file.get_file_address().split("/")[:-1])) - try: - os.system(f"git log {'--reverse'*reverse} \"{file}\" > temp_{id(self)}.txt") - with open(f'temp_{id(self)}.txt') as f: - info = f.read() - os.system(f"rm temp_{id(self)}.txt") - except: - None - os.chdir(current_directory) - return info + def get_dateCreated(self, file): - info = self.fill_log_file(file, reverse = True) + info = self.log for match in re.finditer(r"Date: +\w+ +(\w+) +(\d+) +\d+:\d+:\d+ +(\d+)", info): month = constant.month_mapping[match.group(1)] day = match.group(2) year = match.group(3) + if(int(month)<10 and len(month)==1): + month = f"0{month}" + if(int(day)<10): + day = f"0{day}" return f"{year}-{month}-{day}" return None def get_dateModified(self, file): - info = self.fill_log_file(file, reverse = False) + info = self.log for match in re.finditer(r"Date: +\w+ +(\w+) +(\d+) +\d+:\d+:\d+ +(\d+)", info): month = constant.month_mapping[match.group(1)] day = match.group(2) year = match.group(3) - return f"{year}-{month}-{day}" - return None + if(int(month)<10 and len(month)==1): + month = f"0{month}" + if(int(day)<10): + day = f"0{day}" + return f"{year}-{month}-{day}" + - #TODO -> update this -> it's incomplet def get_url(self, file): - if(self.workflow.dico!={}): - return f"https://github.com/{self.workflow.get_file_address()}/blob/main/{file}" + if(self.workflow_git_name!=""): + return f"https://github.com/{self.workflow_git_name}/blob/main/{file}" return None def get_creators(self, file): - info = self.fill_log_file(file, reverse = True) + info = self.log for match in re.finditer(r"Author: ([^>]+)<([^>]+)>",info): return [{"@id": match.group(1).strip()}] return [] @@ -146,12 +356,12 @@ class RO_Crate: def initialise_file(self, file): - key = file[len(self.directory)+1:] + key = file[len(self.directory):] dico = {} dico["@id"] = key dico["name"] = key dico["@type"] = self.get_types(file) - dico["programmingLanguage"] = {"@id":self.get_programming_language(file)} + dico["programmingLanguage"] = {"@id":str(self.get_programming_language(file))} dico["contentSize"] = self.get_contentSize(file) dico["dateCreated"] = self.get_dateCreated(key) dico["dateModified"] = self.get_dateModified(key) @@ -174,9 +384,6 @@ class RO_Crate: self.fill_from_workflow() self.dico.pop("temp_directory") - name = self.workflow.get_name() - name = name.replace('github.com/', '') - name = re.sub(r"^[ .]|[/<>:\"\\|?*]+|[ .]$", "-", name) #with open(f"{self.workflow.get_output_dir()}/ro-crate-metadata-{name}.json", 'w') as output_file : with open(f"{self.workflow.get_output_dir()}/ro-crate-metadata.json", 'w') as output_file : diff --git a/src/subworkflow.py b/src/subworkflow.py index 4749628..fc16dd2 100644 --- a/src/subworkflow.py +++ b/src/subworkflow.py @@ -464,4 +464,64 @@ class Subworkflow(Main): #ope.set_operation_type("Branch") ope.get_structure(dico) - \ No newline at end of file + + def add_2_rocrate(self, dico, parent_key): + sub_key = self.get_rocrate_key(dico) + dico_sub = get_dico_from_tab_from_id(dico, sub_key) + if(dico_sub==None): + dico_sub = {} + dico_sub["@id"] = sub_key + dico_sub["name"] = f"Subworkflow#{self.get_alias()}" + dico_sub["@type"] = ["SoftwareSourceCode", "ComputationalWorkflow"] + #TODO -> check if this remains true + #dico_main["conformsTo"] = {"@id": "https://bioschemas.org/profiles/ComputationalWorkflow/0.5-DRAFT-2020_07_21"} + #dico_main["dct:conformsTo"]= "https://bioschemas.org/profiles/ComputationalWorkflow/1.0-RELEASE/" + + + #ADD INPUTS + dico_sub["input"] = [] + for input in self.get_takes(): + if(type(input)==str): + name_input = input + else: + name_input = input.get_code(get_OG = True) + dico_input = get_dico_from_tab_from_id(dico, name_input) + if(dico_input==None): + dico_input = {"@id":f"#{name_input}", "name": name_input, "@type": "FormalParameter"} + dico["@graph"].append(dico_input) + dico_sub["input"].append({"@id":dico_input["@id"]}) + #ADD OUTPUTS + dico_sub["output"] = [] + for output in self.get_emit(): + if(type(output)==str): + name_output = output + else: + name_output = output.get_code(get_OG = True) + dico_output = get_dico_from_tab_from_id(dico, name_output) + if(dico_output==None): + dico_output = {"@id":f"#{name_output}", "name": name_output, "@type": "FormalParameter"} + dico["@graph"].append(dico_output) + dico_sub["output"].append({"@id":dico_output["@id"]}) + + + dico_sub["isPartOf"] = [{"@id": parent_key}] + dico_sub["hasPart"] = [] + + + called = [] + for call in self.root.get_all_calls_from_root(): + called.append(call.get_first_element_called()) + + for c in called: + if(c==self): + raise Exception("This shoudn't happen!") + c.add_2_rocrate(dico, sub_key) + dico_sub["hasPart"].append({"@id":c.get_rocrate_key(dico)}) + + dico["@graph"].append(dico_sub) + else: + if(not check_if_element_in_tab_rocrate(dico_sub["isPartOf"], parent_key)): + dico_sub["isPartOf"].append({"@id":parent_key}) + + self.get_nextflow_file().add_to_has_part(dico, sub_key) + \ No newline at end of file diff --git a/src/workflow.py b/src/workflow.py index e21542b..7f66578 100644 --- a/src/workflow.py +++ b/src/workflow.py @@ -91,6 +91,12 @@ class Workflow: self.alias_2_tools = {} self.scripts_2_tools = {} + def get_name(self): + if(self.name!=None): + return self.name + else: + return self.get_root_directory().split('/')[-2] + def create_empty_results(self): os.makedirs(self.output_dir, exist_ok=True) @@ -129,6 +135,9 @@ class Workflow: def set_DSL(self, DSL): self.DSL = DSL + def add_2_rocrate(self, dico): + self.get_workflow_main().add_2_rocrate(dico) + def get_first_file(self): for file in self.nextflow_files: if(file.first_file): @@ -291,6 +300,12 @@ George Marchment, Bryan Brancotte, Marie Schmit, Frédéric Lemoine, Sarah Cohen self.generate_executors_per_subworkflows() + #The generation of the Ro-Crate has been valid from + # - https://ro-crate.ldaca.edu.au/explorer, and + # - https://github.com/crs4/rocrate-validator (with the workflow-ro-crate-1.0 profile) + def get_rocrate(self, display_info=False): + self.rocrate = RO_Crate(self, display_info=display_info) + self.rocrate.initialise() #Returns a dico of number of processes called per each condition -- GitLab