Mercurial > repos > bimib > cobraxy
comparison COBRAxy/marea.py @ 456:a6e45049c1b9 draft
Uploaded
author | francesco_lapi |
---|---|
date | Fri, 12 Sep 2025 17:28:45 +0000 |
parents | f62b8625f6f1 |
children | df90f40a156c |
comparison
equal
deleted
inserted
replaced
455:4e2bc80764b6 | 456:a6e45049c1b9 |
---|---|
1 """ | |
2 MAREA: Enrichment and map styling for RAS/RPS data. | |
3 | |
4 This module compares groups of samples using RAS (Reaction Activity Scores) and/or | |
5 RPS (Reaction Propensity Scores), computes statistics (p-values, z-scores, fold change), | |
6 and applies visual styling to an SVG metabolic map (with optional PDF/PNG export). | |
7 """ | |
1 from __future__ import division | 8 from __future__ import division |
2 import csv | 9 import csv |
3 from enum import Enum | 10 from enum import Enum |
4 import re | 11 import re |
5 import sys | 12 import sys |
24 ERRORS = [] | 31 ERRORS = [] |
25 ########################## argparse ########################################## | 32 ########################## argparse ########################################## |
26 ARGS :argparse.Namespace | 33 ARGS :argparse.Namespace |
27 def process_args(args:List[str] = None) -> argparse.Namespace: | 34 def process_args(args:List[str] = None) -> argparse.Namespace: |
28 """ | 35 """ |
29 Interfaces the script of a module with its frontend, making the user's choices for various parameters available as values in code. | 36 Parse command-line arguments exposed by the Galaxy frontend for this module. |
30 | 37 |
31 Args: | 38 Args: |
32 args : Always obtained (in file) from sys.argv | 39 args: Optional list of arguments, defaults to sys.argv when None. |
33 | 40 |
34 Returns: | 41 Returns: |
35 Namespace : An object containing the parsed arguments | 42 Namespace: Parsed arguments. |
36 """ | 43 """ |
37 parser = argparse.ArgumentParser( | 44 parser = argparse.ArgumentParser( |
38 usage = "%(prog)s [options]", | 45 usage = "%(prog)s [options]", |
39 description = "process some value's genes to create a comparison's map.") | 46 description = "process some value's genes to create a comparison's map.") |
40 | 47 |
263 tmp.append('stroke-width:' + width) | 270 tmp.append('stroke-width:' + width) |
264 if not flag_dash: | 271 if not flag_dash: |
265 tmp.append('stroke-dasharray:' + dash) | 272 tmp.append('stroke-dasharray:' + dash) |
266 return ';'.join(tmp) | 273 return ';'.join(tmp) |
267 | 274 |
268 # TODO: remove, there's applyRPS whatever | |
269 # The type of d values is collapsed, losing precision, because the dict containst lists instead of tuples, please fix! | |
270 def fix_map(d :Dict[str, List[Union[float, FoldChange]]], core_map :ET.ElementTree, threshold_P_V :float, threshold_F_C :float, max_z_score :float) -> ET.ElementTree: | 275 def fix_map(d :Dict[str, List[Union[float, FoldChange]]], core_map :ET.ElementTree, threshold_P_V :float, threshold_F_C :float, max_z_score :float) -> ET.ElementTree: |
271 """ | 276 """ |
272 Edits the selected SVG map based on the p-value and fold change data (d) and some significance thresholds also passed as inputs. | 277 Edits the selected SVG map based on the p-value and fold change data (d) and some significance thresholds also passed as inputs. |
273 | 278 |
274 Args: | 279 Args: |
341 """ | 346 """ |
342 return utils.Result.Ok( | 347 return utils.Result.Ok( |
343 f"//*[@id=\"{reactionId}\"]").map( | 348 f"//*[@id=\"{reactionId}\"]").map( |
344 lambda xPath : metabMap.xpath(xPath)[0]).mapErr( | 349 lambda xPath : metabMap.xpath(xPath)[0]).mapErr( |
345 lambda _ : utils.Result.ResultErr(f"No elements with ID \"{reactionId}\" found in map")) | 350 lambda _ : utils.Result.ResultErr(f"No elements with ID \"{reactionId}\" found in map")) |
346 # ^^^ we shamelessly ignore the contents of the IndexError, it offers nothing to the user. | |
347 | 351 |
348 def styleMapElement(element :ET.Element, styleStr :str) -> None: | 352 def styleMapElement(element :ET.Element, styleStr :str) -> None: |
353 """Append/override stroke-related styles on a given SVG element.""" | |
349 currentStyles :str = element.get("style", "") | 354 currentStyles :str = element.get("style", "") |
350 if re.search(r";stroke:[^;]+;stroke-width:[^;]+;stroke-dasharray:[^;]+$", currentStyles): | 355 if re.search(r";stroke:[^;]+;stroke-width:[^;]+;stroke-dasharray:[^;]+$", currentStyles): |
351 currentStyles = ';'.join(currentStyles.split(';')[:-3]) # TODO: why the last 3? Are we sure? | 356 currentStyles = ';'.join(currentStyles.split(';')[:-3]) |
352 | |
353 #TODO: this is attempting to solve the styling override problem, not sure it does tho | |
354 | 357 |
355 element.set("style", currentStyles + styleStr) | 358 element.set("style", currentStyles + styleStr) |
356 | 359 |
357 # TODO: maybe remove vvv | |
358 class ReactionDirection(Enum): | 360 class ReactionDirection(Enum): |
359 Unknown = "" | 361 Unknown = "" |
360 Direct = "_F" | 362 Direct = "_F" |
361 Inverse = "_B" | 363 Inverse = "_B" |
362 | 364 |
370 @classmethod | 372 @classmethod |
371 def fromReactionId(cls, reactionId :str) -> "ReactionDirection": | 373 def fromReactionId(cls, reactionId :str) -> "ReactionDirection": |
372 return ReactionDirection.fromDir(reactionId[-2:]) | 374 return ReactionDirection.fromDir(reactionId[-2:]) |
373 | 375 |
374 def getArrowBodyElementId(reactionId :str) -> str: | 376 def getArrowBodyElementId(reactionId :str) -> str: |
377 """Return the SVG element id for a reaction arrow body, normalizing direction tags.""" | |
375 if reactionId.endswith("_RV"): reactionId = reactionId[:-3] #TODO: standardize _RV | 378 if reactionId.endswith("_RV"): reactionId = reactionId[:-3] #TODO: standardize _RV |
376 elif ReactionDirection.fromReactionId(reactionId) is not ReactionDirection.Unknown: reactionId = reactionId[:-2] | 379 elif ReactionDirection.fromReactionId(reactionId) is not ReactionDirection.Unknown: reactionId = reactionId[:-2] |
377 return f"R_{reactionId}" | 380 return f"R_{reactionId}" |
378 | 381 |
379 def getArrowHeadElementId(reactionId :str) -> Tuple[str, str]: | 382 def getArrowHeadElementId(reactionId :str) -> Tuple[str, str]: |
399 Invalid = "#BEBEBE" # gray, fold-change under treshold or not significant p-value | 402 Invalid = "#BEBEBE" # gray, fold-change under treshold or not significant p-value |
400 Transparent = "#ffffff00" # transparent, to make some arrow segments disappear | 403 Transparent = "#ffffff00" # transparent, to make some arrow segments disappear |
401 UpRegulated = "#ecac68" # orange, up-regulated reaction | 404 UpRegulated = "#ecac68" # orange, up-regulated reaction |
402 DownRegulated = "#6495ed" # lightblue, down-regulated reaction | 405 DownRegulated = "#6495ed" # lightblue, down-regulated reaction |
403 | 406 |
404 UpRegulatedInv = "#FF0000" | 407 UpRegulatedInv = "#FF0000" # bright red for reversible with conflicting directions |
405 # ^^^ bright red, up-regulated net value for a reversible reaction with | 408 |
406 # conflicting enrichment in the two directions. | 409 DownRegulatedInv = "#0000FF" # bright blue for reversible with conflicting directions |
407 | |
408 DownRegulatedInv = "#0000FF" | |
409 # ^^^ bright blue, down-regulated net value for a reversible reaction with | |
410 # conflicting enrichment in the two directions. | |
411 | 410 |
412 @classmethod | 411 @classmethod |
413 def fromFoldChangeSign(cls, foldChange :float, *, useAltColor = False) -> "ArrowColor": | 412 def fromFoldChangeSign(cls, foldChange :float, *, useAltColor = False) -> "ArrowColor": |
414 colors = (cls.DownRegulated, cls.DownRegulatedInv) if foldChange < 0 else (cls.UpRegulated, cls.UpRegulatedInv) | 413 colors = (cls.DownRegulated, cls.DownRegulatedInv) if foldChange < 0 else (cls.UpRegulated, cls.UpRegulatedInv) |
415 return colors[useAltColor] | 414 return colors[useAltColor] |
442 def applyTo(self, reactionId :str, metabMap :ET.ElementTree, styleStr :str) -> None: | 441 def applyTo(self, reactionId :str, metabMap :ET.ElementTree, styleStr :str) -> None: |
443 if getElementById(reactionId, metabMap).map(lambda el : styleMapElement(el, styleStr)).isErr: | 442 if getElementById(reactionId, metabMap).map(lambda el : styleMapElement(el, styleStr)).isErr: |
444 ERRORS.append(reactionId) | 443 ERRORS.append(reactionId) |
445 | 444 |
446 def styleReactionElements(self, metabMap :ET.ElementTree, reactionId :str, *, mindReactionDir = True) -> None: | 445 def styleReactionElements(self, metabMap :ET.ElementTree, reactionId :str, *, mindReactionDir = True) -> None: |
447 # If We're dealing with RAS data or in general don't care about the direction of the reaction we only style the arrow body | 446 # If direction is irrelevant (e.g., RAS), style only the arrow body |
448 if not mindReactionDir: | 447 if not mindReactionDir: |
449 return self.applyTo(getArrowBodyElementId(reactionId), metabMap, self.toStyleStr()) | 448 return self.applyTo(getArrowBodyElementId(reactionId), metabMap, self.toStyleStr()) |
450 | 449 |
451 # Now we style the arrow head(s): | 450 # Now we style the arrow head(s): |
452 idOpt1, idOpt2 = getArrowHeadElementId(reactionId) | 451 idOpt1, idOpt2 = getArrowHeadElementId(reactionId) |
453 self.applyTo(idOpt1, metabMap, self.toStyleStr(downSizedForTips = True)) | 452 self.applyTo(idOpt1, metabMap, self.toStyleStr(downSizedForTips = True)) |
454 if idOpt2: self.applyTo(idOpt2, metabMap, self.toStyleStr(downSizedForTips = True)) | 453 if idOpt2: self.applyTo(idOpt2, metabMap, self.toStyleStr(downSizedForTips = True)) |
455 | 454 |
456 # TODO: this seems to be unused, remove | |
457 def getMapReactionId(self, reactionId :str, mindReactionDir :bool) -> str: | |
458 """ | |
459 Computes the reaction ID as encoded in the map for a given reaction ID from the dataset. | |
460 | |
461 Args: | |
462 reactionId: the reaction ID, as encoded in the dataset. | |
463 mindReactionDir: if True forward (F_) and backward (B_) directions will be encoded in the result. | |
464 | |
465 Returns: | |
466 str : the ID of an arrow's body or tips in the map. | |
467 """ | |
468 # we assume the reactionIds also don't encode reaction dir if they don't mind it when styling the map. | |
469 if not mindReactionDir: return "R_" + reactionId | |
470 | |
471 #TODO: this is clearly something we need to make consistent in RPS | |
472 return (reactionId[:-3:-1] + reactionId[:-2]) if reactionId[:-2] in ["_F", "_B"] else f"F_{reactionId}" # "Pyr_F" --> "F_Pyr" | |
473 | |
474 def toStyleStr(self, *, downSizedForTips = False) -> str: | 455 def toStyleStr(self, *, downSizedForTips = False) -> str: |
475 """ | 456 """ |
476 Collapses the styles of this Arrow into a str, ready to be applied as part of the "style" property on an svg element. | 457 Collapses the styles of this Arrow into a str, ready to be applied as part of the "style" property on an svg element. |
477 | 458 |
478 Returns: | 459 Returns: |
480 """ | 461 """ |
481 width = self.w | 462 width = self.w |
482 if downSizedForTips: width *= 0.8 | 463 if downSizedForTips: width *= 0.8 |
483 return f";stroke:{self.col};stroke-width:{width};stroke-dasharray:{'5,5' if self.dash else 'none'}" | 464 return f";stroke:{self.col};stroke-width:{width};stroke-dasharray:{'5,5' if self.dash else 'none'}" |
484 | 465 |
485 # vvv These constants could be inside the class itself a static properties, but python | 466 # Default arrows used for different significance states |
486 # was built by brainless organisms so here we are! | |
487 INVALID_ARROW = Arrow(Arrow.MIN_W, ArrowColor.Invalid) | 467 INVALID_ARROW = Arrow(Arrow.MIN_W, ArrowColor.Invalid) |
488 INSIGNIFICANT_ARROW = Arrow(Arrow.MIN_W, ArrowColor.Invalid, isDashed = True) | 468 INSIGNIFICANT_ARROW = Arrow(Arrow.MIN_W, ArrowColor.Invalid, isDashed = True) |
489 TRANSPARENT_ARROW = Arrow(Arrow.MIN_W, ArrowColor.Transparent) # Who cares how big it is if it's transparent | 469 TRANSPARENT_ARROW = Arrow(Arrow.MIN_W, ArrowColor.Transparent) # Who cares how big it is if it's transparent |
490 | 470 |
491 # TODO: A more general version of this can be used for RAS as well, we don't need "fix map" or whatever | |
492 def applyRpsEnrichmentToMap(rpsEnrichmentRes :Dict[str, Union[Tuple[float, FoldChange], Tuple[float, FoldChange, float, float]]], metabMap :ET.ElementTree, maxNumericZScore :float) -> None: | 471 def applyRpsEnrichmentToMap(rpsEnrichmentRes :Dict[str, Union[Tuple[float, FoldChange], Tuple[float, FoldChange, float, float]]], metabMap :ET.ElementTree, maxNumericZScore :float) -> None: |
493 """ | 472 """ |
494 Applies RPS enrichment results to the provided metabolic map. | 473 Applies RPS enrichment results to the provided metabolic map. |
495 | 474 |
496 Args: | 475 Args: |
579 sample_ids.append(pat_id) | 558 sample_ids.append(pat_id) |
580 classes.iloc[j, 1] = None # TODO: problems? | 559 classes.iloc[j, 1] = None # TODO: problems? |
581 | 560 |
582 if l: | 561 if l: |
583 class_pat[classe] = { | 562 class_pat[classe] = { |
584 "values": list(map(list, zip(*l))), # trasposta | 563 "values": list(map(list, zip(*l))), # transpose |
585 "samples": sample_ids | 564 "samples": sample_ids |
586 } | 565 } |
587 continue | 566 continue |
588 | 567 |
589 utils.logWarning( | 568 utils.logWarning( |
590 f"Warning: no sample found in class \"{classe}\", the class has been disregarded", ARGS.out_log) | 569 f"Warning: no sample found in class \"{classe}\", the class has been disregarded", ARGS.out_log) |
591 | 570 |
592 return class_pat | 571 return class_pat |
593 | 572 |
594 ############################ conversion ############################################## | 573 ############################ conversion ############################################## |
595 #conversion from svg to png | 574 # Conversion from SVG to PNG |
596 def svg_to_png_with_background(svg_path :utils.FilePath, png_path :utils.FilePath, dpi :int = 72, scale :int = 1, size :Optional[float] = None) -> None: | 575 def svg_to_png_with_background(svg_path :utils.FilePath, png_path :utils.FilePath, dpi :int = 72, scale :int = 1, size :Optional[float] = None) -> None: |
597 """ | 576 """ |
598 Internal utility to convert an SVG to PNG (forced opaque) to aid in PDF conversion. | 577 Internal utility to convert an SVG to PNG (forced opaque) to aid in PDF conversion. |
599 | 578 |
600 Args: | 579 Args: |
658 ext : _description_ | 637 ext : _description_ |
659 | 638 |
660 Returns: | 639 Returns: |
661 utils.FilePath : _description_ | 640 utils.FilePath : _description_ |
662 """ | 641 """ |
663 # This function returns a util data structure but is extremely specific to this module. | |
664 # RAS also uses collections as output and as such might benefit from a method like this, but I'd wait | |
665 # TODO: until a third tool with multiple outputs appears before porting this to utils. | |
666 return utils.FilePath( | 642 return utils.FilePath( |
667 f"{dataset1Name}_vs_{dataset2Name}" + (f" ({details})" if details else ""), | 643 f"{dataset1Name}_vs_{dataset2Name}" + (f" ({details})" if details else ""), |
668 # ^^^ yes this string is built every time even if the form is the same for the same 2 datasets in | |
669 # all output files: I don't care, this was never the performance bottleneck of the tool and | |
670 # there is no other net gain in saving and re-using the built string. | |
671 ext, | 644 ext, |
672 prefix = ARGS.output_path) | 645 prefix = ARGS.output_path) |
673 | 646 |
674 FIELD_NOT_AVAILABLE = '/' | 647 FIELD_NOT_AVAILABLE = '/' |
675 def writeToCsv(rows: List[list], fieldNames :List[str], outPath :utils.FilePath) -> None: | 648 def writeToCsv(rows: List[list], fieldNames :List[str], outPath :utils.FilePath) -> None: |
681 for row in rows: | 654 for row in rows: |
682 sizeMismatch = fieldsAmt - len(row) | 655 sizeMismatch = fieldsAmt - len(row) |
683 if sizeMismatch > 0: row.extend([FIELD_NOT_AVAILABLE] * sizeMismatch) | 656 if sizeMismatch > 0: row.extend([FIELD_NOT_AVAILABLE] * sizeMismatch) |
684 writer.writerow({ field : data for field, data in zip(fieldNames, row) }) | 657 writer.writerow({ field : data for field, data in zip(fieldNames, row) }) |
685 | 658 |
686 OldEnrichedScores = Dict[str, List[Union[float, FoldChange]]] #TODO: try to use Tuple whenever possible | 659 OldEnrichedScores = Dict[str, List[Union[float, FoldChange]]] |
687 def temp_thingsInCommon(tmp :OldEnrichedScores, core_map :ET.ElementTree, max_z_score :float, dataset1Name :str, dataset2Name = "rest", ras_enrichment = True) -> None: | 660 def temp_thingsInCommon(tmp :OldEnrichedScores, core_map :ET.ElementTree, max_z_score :float, dataset1Name :str, dataset2Name = "rest", ras_enrichment = True) -> None: |
688 # this function compiles the things always in common between comparison modes after enrichment. | |
689 # TODO: organize, name better. | |
690 suffix = "RAS" if ras_enrichment else "RPS" | 661 suffix = "RAS" if ras_enrichment else "RPS" |
691 writeToCsv( | 662 writeToCsv( |
692 [ [reactId] + values for reactId, values in tmp.items() ], | 663 [ [reactId] + values for reactId, values in tmp.items() ], |
693 ["ids", "P_Value", "fold change", "z-score", "average_1", "average_2"], | 664 ["ids", "P_Value", "fold change", "z-score", "average_1", "average_2"], |
694 buildOutputPath(dataset1Name, dataset2Name, details = f"Tabular Result ({suffix})", ext = utils.FileFormat.TSV)) | 665 buildOutputPath(dataset1Name, dataset2Name, details = f"Tabular Result ({suffix})", ext = utils.FileFormat.TSV)) |
807 | 778 |
808 | 779 |
809 # TODO: the net RPS computation should be done in the RPS module | 780 # TODO: the net RPS computation should be done in the RPS module |
810 def compareDatasetPair(dataset1Data :List[List[float]], dataset2Data :List[List[float]], ids :List[str]) -> Tuple[Dict[str, List[Union[float, FoldChange]]], float, Dict[str, Tuple[np.ndarray, np.ndarray]]]: | 781 def compareDatasetPair(dataset1Data :List[List[float]], dataset2Data :List[List[float]], ids :List[str]) -> Tuple[Dict[str, List[Union[float, FoldChange]]], float, Dict[str, Tuple[np.ndarray, np.ndarray]]]: |
811 | 782 |
812 #TODO: the following code still suffers from "dumbvarnames-osis" | |
813 netRPS :Dict[str, Tuple[np.ndarray, np.ndarray]] = {} | 783 netRPS :Dict[str, Tuple[np.ndarray, np.ndarray]] = {} |
814 comparisonResult :Dict[str, List[Union[float, FoldChange]]] = {} | 784 comparisonResult :Dict[str, List[Union[float, FoldChange]]] = {} |
815 count = 0 | 785 count = 0 |
816 max_z_score = 0 | 786 max_z_score = 0 |
817 | 787 |
818 for l1, l2 in zip(dataset1Data, dataset2Data): | 788 for l1, l2 in zip(dataset1Data, dataset2Data): |
819 reactId = ids[count] | 789 reactId = ids[count] |
820 count += 1 | 790 count += 1 |
821 if not reactId: continue # we skip ids that have already been processed | 791 if not reactId: continue |
822 | 792 |
823 try: #TODO: identify the source of these errors and minimize code in the try block | 793 try: #TODO: identify the source of these errors and minimize code in the try block |
824 reactDir = ReactionDirection.fromReactionId(reactId) | 794 reactDir = ReactionDirection.fromReactionId(reactId) |
825 # Net score is computed only for reversible reactions when user wants it on arrow tips or when RAS datasets aren't used | 795 # Net score is computed only for reversible reactions when user wants it on arrow tips or when RAS datasets aren't used |
826 if (ARGS.net or not ARGS.using_RAS) and reactDir is not ReactionDirection.Unknown: | 796 if (ARGS.net or not ARGS.using_RAS) and reactDir is not ReactionDirection.Unknown: |
945 print(f'PDF file {pdfPath.filePath} successfully generated.') | 915 print(f'PDF file {pdfPath.filePath} successfully generated.') |
946 | 916 |
947 except Exception as e: | 917 except Exception as e: |
948 raise utils.DataErr(pdfPath.show(), f'Error generating PDF file: {e}') | 918 raise utils.DataErr(pdfPath.show(), f'Error generating PDF file: {e}') |
949 | 919 |
950 if not ARGS.generate_svg: # This argument is useless, who cares if the user wants the svg or not | 920 if not ARGS.generate_svg: |
951 os.remove(svgFilePath.show()) | 921 os.remove(svgFilePath.show()) |
952 | 922 |
953 ClassPat = Dict[str, List[List[float]]] | 923 ClassPat = Dict[str, List[List[float]]] |
954 def getClassesAndIdsFromDatasets(datasetsPaths :List[str], datasetPath :str, classPath :str, names :List[str]) -> Tuple[List[str], ClassPat, Dict[str, List[str]]]: | 924 def getClassesAndIdsFromDatasets(datasetsPaths :List[str], datasetPath :str, classPath :str, names :List[str]) -> Tuple[List[str], ClassPat, Dict[str, List[str]]]: |
955 # TODO: I suggest creating dicts with ids as keys instead of keeping class_pat and ids separate, | |
956 # for the sake of everyone's sanity. | |
957 columnNames :Dict[str, List[str]] = {} # { datasetName : [ columnName, ... ], ... } | 925 columnNames :Dict[str, List[str]] = {} # { datasetName : [ columnName, ... ], ... } |
958 class_pat :ClassPat = {} | 926 class_pat :ClassPat = {} |
959 if ARGS.option == 'datasets': | 927 if ARGS.option == 'datasets': |
960 num = 1 | 928 num = 1 |
961 for path, name in zip(datasetsPaths, names): | 929 for path, name in zip(datasetsPaths, names): |
981 for clas, values_and_samples_id in class_pat_with_samples_id.items(): | 949 for clas, values_and_samples_id in class_pat_with_samples_id.items(): |
982 class_pat[clas] = values_and_samples_id["values"] | 950 class_pat[clas] = values_and_samples_id["values"] |
983 columnNames[clas] = ["Reactions", *values_and_samples_id["samples"]] | 951 columnNames[clas] = ["Reactions", *values_and_samples_id["samples"]] |
984 | 952 |
985 return ids, class_pat, columnNames | 953 return ids, class_pat, columnNames |
986 #^^^ TODO: this could be a match statement over an enum, make it happen future marea dev with python 3.12! (it's why I kept the ifs) | 954 |
987 | |
988 #TODO: create these damn args as FilePath objects | |
989 def getDatasetValues(datasetPath :str, datasetName :str) -> Tuple[ClassPat, List[str]]: | 955 def getDatasetValues(datasetPath :str, datasetName :str) -> Tuple[ClassPat, List[str]]: |
990 """ | 956 """ |
991 Opens the dataset at the given path and extracts the values (expected nullable numerics) and the IDs. | 957 Opens the dataset at the given path and extracts the values (expected nullable numerics) and the IDs. |
992 | 958 |
993 Args: | 959 Args: |
1023 | 989 |
1024 core_map: ET.ElementTree = ARGS.choice_map.getMap( | 990 core_map: ET.ElementTree = ARGS.choice_map.getMap( |
1025 ARGS.tool_dir, | 991 ARGS.tool_dir, |
1026 utils.FilePath.fromStrPath(ARGS.custom_map) if ARGS.custom_map else None) | 992 utils.FilePath.fromStrPath(ARGS.custom_map) if ARGS.custom_map else None) |
1027 | 993 |
1028 # TODO: in the future keep the indices WITH the data and fix the code below. | |
1029 | |
1030 # Prepare enrichment results containers | 994 # Prepare enrichment results containers |
1031 ras_results = [] | 995 ras_results = [] |
1032 rps_results = [] | 996 rps_results = [] |
1033 | 997 |
1034 # Compute RAS enrichment if requested | 998 # Compute RAS enrichment if requested |
1035 if ARGS.using_RAS: # vvv columnNames only matter with RPS data | 999 if ARGS.using_RAS: |
1036 ids_ras, class_pat_ras, _ = getClassesAndIdsFromDatasets( | 1000 ids_ras, class_pat_ras, _ = getClassesAndIdsFromDatasets( |
1037 ARGS.input_datas, ARGS.input_data, ARGS.input_class, ARGS.names) | 1001 ARGS.input_datas, ARGS.input_data, ARGS.input_class, ARGS.names) |
1038 ras_results, _ = computeEnrichment(class_pat_ras, ids_ras, fromRAS=True) | 1002 ras_results, _ = computeEnrichment(class_pat_ras, ids_ras, fromRAS=True) |
1039 # ^^^ netRPS only matter with RPS data | 1003 |
1040 | 1004 |
1041 # Compute RPS enrichment if requested | 1005 # Compute RPS enrichment if requested |
1042 if ARGS.using_RPS: | 1006 if ARGS.using_RPS: |
1043 ids_rps, class_pat_rps, columnNames = getClassesAndIdsFromDatasets( | 1007 ids_rps, class_pat_rps, columnNames = getClassesAndIdsFromDatasets( |
1044 ARGS.input_datas_rps, ARGS.input_data_rps, ARGS.input_class_rps, ARGS.names_rps) | 1008 ARGS.input_datas_rps, ARGS.input_data_rps, ARGS.input_class_rps, ARGS.names_rps) |
1063 temp_thingsInCommon(tmp_ras, map_copy, max_z_ras, i, j, ras_enrichment=True) | 1027 temp_thingsInCommon(tmp_ras, map_copy, max_z_ras, i, j, ras_enrichment=True) |
1064 | 1028 |
1065 # Apply RPS styling to arrow heads | 1029 # Apply RPS styling to arrow heads |
1066 if res.get('rps'): | 1030 if res.get('rps'): |
1067 tmp_rps, max_z_rps = res['rps'] | 1031 tmp_rps, max_z_rps = res['rps'] |
1068 # applyRpsEnrichmentToMap styles only heads unless only RPS are used | 1032 |
1069 temp_thingsInCommon(tmp_rps, map_copy, max_z_rps, i, j, ras_enrichment=False) | 1033 temp_thingsInCommon(tmp_rps, map_copy, max_z_rps, i, j, ras_enrichment=False) |
1070 | 1034 |
1071 # Output both SVG and PDF/PNG as configured | 1035 # Output both SVG and PDF/PNG as configured |
1072 createOutputMaps(i, j, map_copy) | 1036 createOutputMaps(i, j, map_copy) |
1073 | 1037 |
1074 # Add net RPS output file | 1038 # Add net RPS output file |
1075 if ARGS.net or not ARGS.using_RAS: | 1039 if ARGS.net or not ARGS.using_RAS: |
1076 for datasetName, rows in netRPS.items(): | 1040 for datasetName, rows in netRPS.items(): |
1077 writeToCsv( | 1041 writeToCsv( |
1078 [[reactId, *netValues] for reactId, netValues in rows.items()], | 1042 [[reactId, *netValues] for reactId, netValues in rows.items()], |
1079 # vvv In weird comparison modes the dataset names are not recorded properly.. | |
1080 columnNames.get(datasetName, ["Reactions"]), | 1043 columnNames.get(datasetName, ["Reactions"]), |
1081 utils.FilePath( | 1044 utils.FilePath( |
1082 "Net_RPS_" + datasetName, | 1045 "Net_RPS_" + datasetName, |
1083 ext = utils.FileFormat.CSV, | 1046 ext = utils.FileFormat.CSV, |
1084 prefix = ARGS.output_path)) | 1047 prefix = ARGS.output_path)) |