Skip to content

Library

This module provides managing classes and methods for the tool.

Feature

A Feature object represents a locus' feature (currently only CDS) and its properties.

Attributes:

  • feature_id (str) –

    Feature identifier.

  • feature_type (str) –

    Type of element (e.g. CDS or tRNA). Currently only CDS are supported.

  • start (int) –

    1-based start genomic coordinate.

  • end (int) –

    1-based end genomic coordinates

  • strand (int) –

    Genomic strand (1: plus strand, -1: minus strand).

  • name (str) –

    Name of the feature which will be used as a label.

  • sequence (Seq) –

    Feature's sequence.

  • record (SeqRecord) –

    SeqRecord object of corresponding feature sequence.

  • group (str) –

    Feature group that defines feature's colour and meant to represent a set of homologous features. Can be set with Loci.mmseqs_cluster() method that uses mmseqs clustering to define CDS feature groups.

  • group_type (str) –

    Type of feature group that allow to visualise different set of feature groups differently (e.g. plot labels only for features which group_type is "variable" or "labeled").

  • category (str) –

    Feature category which initially is built to handle phrogs category annotation. In visualisation it defines the "category" colour annotation under features. Supposed to represent clusters on locus or any second layer of feature properties.

  • vis_prms (dict) –

    Visualisation parameters that holds colours, label and other info for Drawing methods.

  • overlapping (bool) –

    Whether feature overlaps with visualised region or not.

  • prms (Parameters) –

    Parameters' class object that holds config and cmd arguments.

Source code in lovis4u/DataProcessing.py
class Feature:
    """A Feature object represents a locus' feature (currently only CDS) and its properties.

    Attributes:
        feature_id (str): Feature identifier.
        feature_type (str): Type of element (e.g. CDS or tRNA). Currently only CDS are supported.
        start (int): 1-based start genomic coordinate.
        end (int): 1-based end genomic coordinates
        strand (int): Genomic strand (1: plus strand, -1: minus strand).
        name (str): Name of the feature which will be used as a label.
        sequence (Bio.Seq.Seq): Feature's sequence.
        record (Bio.SeqRecord.SeqRecord): SeqRecord object of corresponding feature sequence.
        group (str): Feature group that defines feature's colour and meant to represent a set of homologous features.
            Can be set with Loci.mmseqs_cluster() method that uses mmseqs clustering to define CDS feature groups.
        group_type (str): Type of feature group that allow to visualise different set of feature groups differently
            (e.g. plot labels only for features which group_type is "variable" or "labeled").
        category (str): Feature category which initially is built to handle phrogs category annotation.
            In visualisation it defines the "category" colour annotation under features.
            Supposed to represent clusters on locus or any second layer of feature properties.
        vis_prms (dict): Visualisation parameters that holds colours, label and other info for Drawing methods.
        overlapping (bool): Whether feature overlaps with visualised region or not.
        prms (lovis4u.Manager.Parameters): Parameters' class object that holds config and cmd arguments.

    """

    def __init__(self, feature_id: str, feature_type: str, start: int, end: int, strand: int, name: str,
                 sequence: Bio.Seq.Seq, group: str, group_type: str, category: str, vis_prms: dict, overlapping: bool,
                 parameters: lovis4u.Manager.Parameters):
        """Create a Feature object.

        Arguments:
            feature_id (str): Feature identifier.
            feature_type (str): Type of element (e.g. CDS or tRNA). Currently only CDS are supported.
            start (int): 1-based start genomic coordinate.
            end (int): 1-based end genomic coordinates
            strand (int): Genomic strand (1: plus strand, -1: minus strand).
            name (str): Name of the feature which will be used as a label.
            sequence (Bio.Seq.Seq): Feature's sequence.
            group (str): Feature group that defines feature's colour and meant to represent a set of homologous features.
                Can be set with Loci.mmseqs_cluster() method that uses mmseqs clustering to define CDS feature groups.
            group_type (str): Type of feature group that allow to visualise different set of feature groups differently
                (e.g. plot labels only for features which group_type is "variable" or "labeled").
            category (str): Feature category which initially is built to handle phrogs category annotation.
                In visualisation it defines the "category" colour annotation under features.
                Supposed to represent clusters on locus or any second layer of feature properties.
            vis_prms (dict): Visualisation parameters that holds colours, label and other info for Drawing methods.
            prms (lovis4u.Manager.Parameters): Parameters' class object that holds config and cmd arguments.

        """
        self.feature_type = feature_type
        self.feature_id = feature_id
        self.start = start
        self.end = end
        self.strand = strand
        self.name = name
        self.sequence = sequence
        self.record = Bio.SeqRecord.SeqRecord(seq=self.sequence, id=self.feature_id)
        self.group = group  # maybe rename later
        self.group_type = group_type
        self.category = category
        self.vis_prms = vis_prms
        self.vis_prms["label"] = str(name)
        self.overlapping = overlapping
        self.prms = parameters

__init__(feature_id, feature_type, start, end, strand, name, sequence, group, group_type, category, vis_prms, overlapping, parameters)

Create a Feature object.

Parameters:

  • feature_id (str) –

    Feature identifier.

  • feature_type (str) –

    Type of element (e.g. CDS or tRNA). Currently only CDS are supported.

  • start (int) –

    1-based start genomic coordinate.

  • end (int) –

    1-based end genomic coordinates

  • strand (int) –

    Genomic strand (1: plus strand, -1: minus strand).

  • name (str) –

    Name of the feature which will be used as a label.

  • sequence (Seq) –

    Feature's sequence.

  • group (str) –

    Feature group that defines feature's colour and meant to represent a set of homologous features. Can be set with Loci.mmseqs_cluster() method that uses mmseqs clustering to define CDS feature groups.

  • group_type (str) –

    Type of feature group that allow to visualise different set of feature groups differently (e.g. plot labels only for features which group_type is "variable" or "labeled").

  • category (str) –

    Feature category which initially is built to handle phrogs category annotation. In visualisation it defines the "category" colour annotation under features. Supposed to represent clusters on locus or any second layer of feature properties.

  • vis_prms (dict) –

    Visualisation parameters that holds colours, label and other info for Drawing methods.

  • prms (Parameters) –

    Parameters' class object that holds config and cmd arguments.

Source code in lovis4u/DataProcessing.py
def __init__(self, feature_id: str, feature_type: str, start: int, end: int, strand: int, name: str,
             sequence: Bio.Seq.Seq, group: str, group_type: str, category: str, vis_prms: dict, overlapping: bool,
             parameters: lovis4u.Manager.Parameters):
    """Create a Feature object.

    Arguments:
        feature_id (str): Feature identifier.
        feature_type (str): Type of element (e.g. CDS or tRNA). Currently only CDS are supported.
        start (int): 1-based start genomic coordinate.
        end (int): 1-based end genomic coordinates
        strand (int): Genomic strand (1: plus strand, -1: minus strand).
        name (str): Name of the feature which will be used as a label.
        sequence (Bio.Seq.Seq): Feature's sequence.
        group (str): Feature group that defines feature's colour and meant to represent a set of homologous features.
            Can be set with Loci.mmseqs_cluster() method that uses mmseqs clustering to define CDS feature groups.
        group_type (str): Type of feature group that allow to visualise different set of feature groups differently
            (e.g. plot labels only for features which group_type is "variable" or "labeled").
        category (str): Feature category which initially is built to handle phrogs category annotation.
            In visualisation it defines the "category" colour annotation under features.
            Supposed to represent clusters on locus or any second layer of feature properties.
        vis_prms (dict): Visualisation parameters that holds colours, label and other info for Drawing methods.
        prms (lovis4u.Manager.Parameters): Parameters' class object that holds config and cmd arguments.

    """
    self.feature_type = feature_type
    self.feature_id = feature_id
    self.start = start
    self.end = end
    self.strand = strand
    self.name = name
    self.sequence = sequence
    self.record = Bio.SeqRecord.SeqRecord(seq=self.sequence, id=self.feature_id)
    self.group = group  # maybe rename later
    self.group_type = group_type
    self.category = category
    self.vis_prms = vis_prms
    self.vis_prms["label"] = str(name)
    self.overlapping = overlapping
    self.prms = parameters

Loci

A Loci object holds information about all loci to be plotted and methods for data preparation.

Attributes:

  • loci (list) –

    List of Locus objects.

  • locus_annotation (DataFrame) –

    Table with information about each locus that defines visualisation (e.g. coordinates for visualisation, description, etc).

  • feature_annotation (DataFrame) –

    Table with information about each feature that defines visualisation (e.g. group, name, category, etc).

  • prms (Parameters) –

    Parameters' class object that holds config and cmd arguments.

Source code in lovis4u/DataProcessing.py
 172
 173
 174
 175
 176
 177
 178
 179
 180
 181
 182
 183
 184
 185
 186
 187
 188
 189
 190
 191
 192
 193
 194
 195
 196
 197
 198
 199
 200
 201
 202
 203
 204
 205
 206
 207
 208
 209
 210
 211
 212
 213
 214
 215
 216
 217
 218
 219
 220
 221
 222
 223
 224
 225
 226
 227
 228
 229
 230
 231
 232
 233
 234
 235
 236
 237
 238
 239
 240
 241
 242
 243
 244
 245
 246
 247
 248
 249
 250
 251
 252
 253
 254
 255
 256
 257
 258
 259
 260
 261
 262
 263
 264
 265
 266
 267
 268
 269
 270
 271
 272
 273
 274
 275
 276
 277
 278
 279
 280
 281
 282
 283
 284
 285
 286
 287
 288
 289
 290
 291
 292
 293
 294
 295
 296
 297
 298
 299
 300
 301
 302
 303
 304
 305
 306
 307
 308
 309
 310
 311
 312
 313
 314
 315
 316
 317
 318
 319
 320
 321
 322
 323
 324
 325
 326
 327
 328
 329
 330
 331
 332
 333
 334
 335
 336
 337
 338
 339
 340
 341
 342
 343
 344
 345
 346
 347
 348
 349
 350
 351
 352
 353
 354
 355
 356
 357
 358
 359
 360
 361
 362
 363
 364
 365
 366
 367
 368
 369
 370
 371
 372
 373
 374
 375
 376
 377
 378
 379
 380
 381
 382
 383
 384
 385
 386
 387
 388
 389
 390
 391
 392
 393
 394
 395
 396
 397
 398
 399
 400
 401
 402
 403
 404
 405
 406
 407
 408
 409
 410
 411
 412
 413
 414
 415
 416
 417
 418
 419
 420
 421
 422
 423
 424
 425
 426
 427
 428
 429
 430
 431
 432
 433
 434
 435
 436
 437
 438
 439
 440
 441
 442
 443
 444
 445
 446
 447
 448
 449
 450
 451
 452
 453
 454
 455
 456
 457
 458
 459
 460
 461
 462
 463
 464
 465
 466
 467
 468
 469
 470
 471
 472
 473
 474
 475
 476
 477
 478
 479
 480
 481
 482
 483
 484
 485
 486
 487
 488
 489
 490
 491
 492
 493
 494
 495
 496
 497
 498
 499
 500
 501
 502
 503
 504
 505
 506
 507
 508
 509
 510
 511
 512
 513
 514
 515
 516
 517
 518
 519
 520
 521
 522
 523
 524
 525
 526
 527
 528
 529
 530
 531
 532
 533
 534
 535
 536
 537
 538
 539
 540
 541
 542
 543
 544
 545
 546
 547
 548
 549
 550
 551
 552
 553
 554
 555
 556
 557
 558
 559
 560
 561
 562
 563
 564
 565
 566
 567
 568
 569
 570
 571
 572
 573
 574
 575
 576
 577
 578
 579
 580
 581
 582
 583
 584
 585
 586
 587
 588
 589
 590
 591
 592
 593
 594
 595
 596
 597
 598
 599
 600
 601
 602
 603
 604
 605
 606
 607
 608
 609
 610
 611
 612
 613
 614
 615
 616
 617
 618
 619
 620
 621
 622
 623
 624
 625
 626
 627
 628
 629
 630
 631
 632
 633
 634
 635
 636
 637
 638
 639
 640
 641
 642
 643
 644
 645
 646
 647
 648
 649
 650
 651
 652
 653
 654
 655
 656
 657
 658
 659
 660
 661
 662
 663
 664
 665
 666
 667
 668
 669
 670
 671
 672
 673
 674
 675
 676
 677
 678
 679
 680
 681
 682
 683
 684
 685
 686
 687
 688
 689
 690
 691
 692
 693
 694
 695
 696
 697
 698
 699
 700
 701
 702
 703
 704
 705
 706
 707
 708
 709
 710
 711
 712
 713
 714
 715
 716
 717
 718
 719
 720
 721
 722
 723
 724
 725
 726
 727
 728
 729
 730
 731
 732
 733
 734
 735
 736
 737
 738
 739
 740
 741
 742
 743
 744
 745
 746
 747
 748
 749
 750
 751
 752
 753
 754
 755
 756
 757
 758
 759
 760
 761
 762
 763
 764
 765
 766
 767
 768
 769
 770
 771
 772
 773
 774
 775
 776
 777
 778
 779
 780
 781
 782
 783
 784
 785
 786
 787
 788
 789
 790
 791
 792
 793
 794
 795
 796
 797
 798
 799
 800
 801
 802
 803
 804
 805
 806
 807
 808
 809
 810
 811
 812
 813
 814
 815
 816
 817
 818
 819
 820
 821
 822
 823
 824
 825
 826
 827
 828
 829
 830
 831
 832
 833
 834
 835
 836
 837
 838
 839
 840
 841
 842
 843
 844
 845
 846
 847
 848
 849
 850
 851
 852
 853
 854
 855
 856
 857
 858
 859
 860
 861
 862
 863
 864
 865
 866
 867
 868
 869
 870
 871
 872
 873
 874
 875
 876
 877
 878
 879
 880
 881
 882
 883
 884
 885
 886
 887
 888
 889
 890
 891
 892
 893
 894
 895
 896
 897
 898
 899
 900
 901
 902
 903
 904
 905
 906
 907
 908
 909
 910
 911
 912
 913
 914
 915
 916
 917
 918
 919
 920
 921
 922
 923
 924
 925
 926
 927
 928
 929
 930
 931
 932
 933
 934
 935
 936
 937
 938
 939
 940
 941
 942
 943
 944
 945
 946
 947
 948
 949
 950
 951
 952
 953
 954
 955
 956
 957
 958
 959
 960
 961
 962
 963
 964
 965
 966
 967
 968
 969
 970
 971
 972
 973
 974
 975
 976
 977
 978
 979
 980
 981
 982
 983
 984
 985
 986
 987
 988
 989
 990
 991
 992
 993
 994
 995
 996
 997
 998
 999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
class Loci:
    """A Loci object holds information about all loci to be plotted and methods for data preparation.

    Attributes:
        loci (list): List of Locus objects.
        locus_annotation (pd.DataFrame): Table with information about each locus that defines visualisation
            (e.g. coordinates for visualisation, description, etc).
        feature_annotation (pd.DataFrame): Table with information about each feature that defines visualisation
            (e.g. group, name, category, etc).
        prms (lovis4u.Manager.Parameters): Parameters' class object that holds config and cmd arguments.

    """

    def __init__(self, parameters=lovis4u.Manager.Parameters):
        """Create a Loci object.

        Arguments:
            parameters (lovis4u.Manager.Parameters): Parameters' class object that holds config and cmd arguments.
        """
        self.loci = []
        self.locus_annotation = pd.DataFrame(columns=["sequence_id", "length", "coordinates", "circular", "description",
                                                      "order", "group"]).set_index("sequence_id")
        self.feature_annotation = pd.DataFrame(columns=["feature_id", "locus_id", "coordinates", "feature_type", "name",
                                                        "group", "group_type", "category", "fill_colour",
                                                        "stroke_colour",
                                                        "show_label"]).set_index("feature_id")
        self.prms = parameters

    def __load_annotation_file(self, file_path: str, annotation_columns: list, index_column: str) -> pd.DataFrame:
        """Private method to load an annotation file.

        Arguments:
            file_path (str): File path for an annotation file to be loaded.
            annotation_columns (list): List of columns that should be considered.
            index_column (str): Column name to be considered as index.

        Returns:
              pd.DataFrame: Preprocessed annotation file.
        """
        annotation_table = pd.read_table(file_path)
        found_allowed_columns = [i for i in annotation_columns if i in annotation_table.columns]
        not_found_allowed_columns = [i for i in annotation_columns if i not in annotation_table.columns]
        annotation_table = annotation_table[found_allowed_columns].set_index(index_column)
        annotation_table[not_found_allowed_columns] = None
        return annotation_table

    def load_locus_annotation_file(self, file_path: str) -> None:
        """Load loci annotation file.

        Arguments:
            file_path (str): File path for a loci annotation file to be loaded.

        Returns:
            None

        """
        annotation_columns = ["sequence_id", "length", "coordinates", "circular", "description", "order", "group"]
        self.locus_annotation = self.__load_annotation_file(file_path, annotation_columns, "sequence_id")
        return None

    def load_feature_annotation_file(self, file_path: str) -> None:
        """Load features annotation file.

        Arguments:
            file_path (str): File path for a features annotation file to be loaded.

        Returns:
            None

        """
        annotation_columns = ["feature_id", "locus_id", "coordinates", "feature_type", "name", "group", "group_type",
                              "category", "fill_colour", "stroke_colour", "show_label"]
        self.feature_annotation = self.__load_annotation_file(file_path, annotation_columns, "feature_id")
        return None

    def __update_locus_annotation(self, record_id: str, record_description: str, record_length: int) -> None:
        """Private method for updating loci annotation.

        Arguments:
            record_id (str): Sequence identifier.
            record_description (str): Sequence description.
            record_length (int): Sequence length.

        Returns:
            None

        """
        if record_id not in self.locus_annotation.index:
            self.locus_annotation.loc[record_id] = {col: None for col in self.locus_annotation.columns}

        default_values = dict(length=record_length, coordinates=f"1:{record_length}:1",
                              description=record_description, circular=1, order=len(self.loci), group=1)
        self.locus_annotation.loc[record_id] = self.locus_annotation.loc[record_id].fillna(default_values)
        return None

    def __update_feature_annotation(self, feature_id: str, locus_id: str, coordinates: str, feature_type: str,
                                    category: str, name: str) -> None:
        """Private method for updating feature annotation.

        Arguments:
            feature_id (str): Feature identifier.
            locus_id (str): Sequence description.
            coordinates (str): Feature coordinates.
            category (str): Feature type.
            name (str): Feature name.


        Returns:
            None

        """
        if feature_id not in self.feature_annotation.index:
            self.feature_annotation.loc[feature_id] = {col: None for col in self.feature_annotation.columns}

        if self.feature_annotation.loc[feature_id]["group_type"] in self.prms.args["feature_group_types_to_show_label"] \
                or "all" in self.prms.args["feature_group_types_to_show_label"]:
            show_label = 1
        else:
            show_label = 0
        stroke_colour = "default"
        if self.prms.args["set_feature_stroke_colour_based_on_fill_colour"] and \
                self.feature_annotation.loc[feature_id]["fill_colour"] and \
                self.feature_annotation.loc[feature_id]["fill_colour"] != "default":
            stroke_colour = lovis4u.Methods.scale_lightness(self.feature_annotation.loc[feature_id]["fill_colour"],
                                                            self.prms.args["feature_stroke_colour_relative_lightness"])
        default_values = dict(locus_id=locus_id, coordinates=coordinates, feature_type=feature_type,
                              name=name, group="", group_type="", category=category, fill_colour="default",
                              stroke_colour=stroke_colour,
                              show_label=show_label)
        self.feature_annotation.loc[feature_id] = self.feature_annotation.loc[feature_id].fillna(default_values)
        return None

    def load_loci_from_extended_gff(self, input_f: str, ilund4u_mode: bool = False) -> None:
        """Load loci from the folder with gff files. Each GFF file also should contain corresponding nucleotide
            sequence. Such files are produced for example by pharokka annotation tool.

        All files with extension other than .gff (not case-sensitive) will be ignored.

        Arguments:
            input_folder: folder name with gff files.

        Returns:
            None

        """

        try:
            if isinstance(input_f, str):
                input_folder = input_f
                if not os.path.exists(input_folder):
                    raise lovis4u.Manager.lovis4uError(f"Folder {input_folder} does not exist.")
                gff_files = [os.path.join(input_folder, f) for f in os.listdir(input_folder)]
            elif isinstance(input_f, list):
                gff_files = input_f
            else:
                raise lovis4u.Manager.lovis4uError(f"The input for the GFF parsing function must be either a folder or "
                                                   f"a list of files.")
            if not gff_files:
                raise lovis4u.Manager.lovis4uError(f"Folder {input_f} does not contain files.")
            if self.prms.args["verbose"]:
                print(f"○ Reading gff file{'s' if len(gff_files) > 1 else ''}...", file=sys.stdout)
            for gff_file_path in gff_files:
                try:
                    gff_file = gff_file_path
                    gff_records = list(BCBio.GFF.parse(gff_file_path, limit_info=dict(gff_type=["CDS"])))
                    if len(gff_records) != 1:
                        print(f"○ Warning: gff file {gff_file} contains information for more than 1 "
                              f"sequence. File will be skipped.")
                        continue
                    gff_record = gff_records[0]
                    try:
                        record_locus_sequence = gff_record.seq
                    except Bio.Seq.UndefinedSequenceError:
                        print(f"○ Warning: gff file {gff_file} doesn't contain corresponding sequences.")
                        continue
                    if self.prms.args["gff_description_source"] in gff_record.annotations:
                        record_description = gff_record.annotations[self.prms.args["gff_description_source"]][0]
                        if isinstance(record_description, tuple):
                            record_description = " ".join(record_description)
                    else:
                        record_description = ""
                    if self.prms.args["use_filename_as_contig_id"]:
                        gff_record.id = os.path.splitext(os.path.basename(gff_file))[0]
                    self.__update_locus_annotation(gff_record.id, record_description, len(record_locus_sequence))
                    locus_annotation_row = self.locus_annotation.loc[gff_record.id]
                    coordinates = [dict(zip(["start", "end", "strand"], map(int, c.split(":")))) for c in
                                   locus_annotation_row["coordinates"].split(",")]
                    record_locus = Locus(seq_id=gff_record.id, coordinates=coordinates,
                                         description=locus_annotation_row["description"],
                                         circular=locus_annotation_row["circular"],
                                         length=locus_annotation_row["length"], parameters=self.prms, features=[],
                                         order=locus_annotation_row["order"])
                    features_ids = [i.id for i in gff_record.features]
                    if len(features_ids) != len(set(features_ids)):
                        raise lovis4u.Manager.lovis4uError(f"Gff file {gff_file} contains duplicated feature ids while"
                                                           f" only unique are allowed.")
                    for gff_feature in gff_record.features:
                        feature_id = gff_feature.id
                        if ilund4u_mode:
                            if gff_record.id not in feature_id:
                                feature_id = f"{gff_record.id}-{feature_id}"
                        transl_table = self.prms.args["default_transl_table"]
                        if "transl_table" in gff_feature.qualifiers.keys():
                            transl_table = int(gff_feature.qualifiers["transl_table"][0])
                        name = ""
                        if self.prms.args["gff_CDS_name_source"] in gff_feature.qualifiers:
                            name = gff_feature.qualifiers[self.prms.args["gff_CDS_name_source"]][0]
                        category = ""
                        if self.prms.args["gff_CDS_category_source"] in gff_feature.qualifiers:
                            category = ",".join(gff_feature.qualifiers[self.prms.args["gff_CDS_category_source"]])
                        for coordinate in record_locus.coordinates:
                            overlapping = False
                            start, end = coordinate["start"], coordinate["end"]
                            if start <= gff_feature.location.start + 1 <= end or start <= gff_feature.location.end <= end:
                                overlapping = True
                                break
                        if not overlapping and not self.prms.args["cluster_all_proteins"]:
                            continue
                        self.__update_feature_annotation(feature_id, record_locus.seq_id,
                                                         f"{int(gff_feature.location.start) + 1}:"
                                                         f"{int(gff_feature.location.end)}:{gff_feature.location.strand}",
                                                         "CDS", category, name)
                        feature_annotation_row = self.feature_annotation.loc[feature_id]
                        feature = Feature(feature_type=feature_annotation_row["feature_type"],
                                          feature_id=feature_id, start=int(gff_feature.location.start) + 1,
                                          end=int(gff_feature.location.end), strand=gff_feature.location.strand,
                                          name=feature_annotation_row["name"],
                                          sequence=gff_feature.translate(record_locus_sequence, table=transl_table,
                                                                         cds=False)[:-1],
                                          group=feature_annotation_row["group"],
                                          group_type=feature_annotation_row["group_type"],
                                          category=feature_annotation_row["category"],
                                          vis_prms=dict(fill_colour=feature_annotation_row["fill_colour"],
                                                        stroke_colour=feature_annotation_row["stroke_colour"],
                                                        show_label=feature_annotation_row["show_label"]),
                                          overlapping=overlapping, parameters=self.prms)
                        record_locus.features.append(feature)
                    self.loci.append(record_locus)
                except:
                    print(f"○ Warning: gff file {gff_file} was not read properly and skipped")
                    if self.prms.args["parsing_debug"]:
                        self.prms.args["debug"] = True
                        raise lovis4u.Manager.lovis4uError()
            seq_id_to_order = self.locus_annotation["order"].to_dict()
            loci_ids = [l.seq_id for l in self.loci]
            if len(loci_ids) != len(set(loci_ids)):
                raise lovis4u.Manager.lovis4uError(f"The input gff files have duplicated contig ids.\n\t"
                                                   f"You can use `--use-filename-as-id` parameter to use file name "
                                                   f"as contig id which can help to fix the problem.")
            self.loci.sort(key=lambda locus: seq_id_to_order[locus.seq_id])
            if self.prms.args["verbose"]:
                print(f"⦿ {len(self.loci)} {'locus was' if len(self.loci) == 1 else 'loci were'} loaded from the gff "
                      f"files folder", file=sys.stdout)
            return None
        except Exception as error:
            raise lovis4u.Manager.lovis4uError("Unable to load loci from gff folder.") from error

    def load_loci_from_gb(self, input_folder: str) -> None:
        """Load loci from the folder with genbank files.

        All files with extension other than .gb (not case-sensitive) will be ignored.

        Arguments:
            input_folder: folder name with gb files.

        Returns:
            None

        """
        if not os.path.exists(input_folder):
            raise lovis4u.Manager.lovis4uError(f"Folder {input_folder} does not exist.")
        try:
            gb_files = [f for f in os.listdir(input_folder)]
            if not gb_files:
                raise lovis4u.Manager.lovis4uError(f"Folder {input_folder} does not contain files.")
            if self.prms.args["verbose"]:
                print(f"○ Reading gb file{'s' if len(gb_files) > 1 else ''}...", file=sys.stdout)
            for gb_file in gb_files:
                try:
                    gb_file_path = os.path.join(input_folder, gb_file)
                    gb_records = list(Bio.SeqIO.parse(gb_file_path, "genbank"))
                    if len(gb_records) != 1:
                        print(f"○ Warning: gb file {gb_file} contains information for more than 1 "
                              f"sequence. File will be skipped.")
                        continue
                    gb_record = gb_records[0]
                    record_locus_sequence = gb_record.seq
                    if self.prms.args["genbank_description_source"] == "description":
                        record_description = gb_record.description
                    elif "annotations:" in self.prms.args["genbank_description_source"]:
                        feature_description_key = self.prms.args["genbank_description_source"].split(":")[1]
                        record_description = gb_record.annotations[feature_description_key]
                    else:
                        record_description = ""
                    if self.prms.args["use_filename_as_contig_id"]:
                        gb_record.id = os.path.splitext(os.path.basename(gb_file))[0]
                    self.__update_locus_annotation(gb_record.id, record_description, len(record_locus_sequence))
                    locus_annotation_row = self.locus_annotation.loc[gb_record.id]
                    coordinates = [dict(zip(["start", "end", "strand"], map(int, c.split(":")))) for c in
                                   locus_annotation_row["coordinates"].split(",")]
                    record_locus = Locus(seq_id=gb_record.id, coordinates=coordinates,
                                         description=locus_annotation_row["description"],
                                         circular=locus_annotation_row["circular"],
                                         length=locus_annotation_row["length"], parameters=self.prms, features=[],
                                         order=locus_annotation_row["order"])

                    gb_CDSs = [i for i in gb_record.features if i.type == "CDS"]
                    first_CDS_record = gb_CDSs[0]
                    id_source = self.prms.args["genbank_id_source"]
                    if self.prms.args["genbank_id_source"] not in first_CDS_record.qualifiers:
                        for alternative_id_source in self.prms.args["genbank_id_alternative_source"]:
                            if alternative_id_source in first_CDS_record.qualifiers:
                                id_source = alternative_id_source
                                if self.prms.args["verbose"]:
                                    print(f"○ Warning: there is no <{self.prms.args['genbank_id_source']}> attribute "
                                          f"for CDS records in {gb_file}. Alternative <{id_source}> was used instead.",
                                          file=sys.stdout)
                                break
                        if id_source == self.prms.args["genbank_id_source"]:
                            print(f"There is no <{self.prms.args['genbank_id_source']}> "
                                  f"attribute for CDS record found in {gb_file}. We tried to"
                                  f" find any from the alternative list: "
                                  f"{','.join(self.prms.args['genbank_id_alternative_source'])}"
                                  f", but they also weren't found.")  # add about cmd parameter
                    features_ids = [i.qualifiers[id_source][0] for i in gb_CDSs if id_source in i.qualifiers]
                    if len(features_ids) != len(set(features_ids)):
                        print(f"GB file {gb_record} contains duplicated feature ids while"
                                                           f" only unique are allowed.")
                    for gb_feature in gb_CDSs:
                        if id_source not in gb_feature.qualifiers:
                            print(f"    ○ Warning: genbank CDS feature for {gb_file} located at {gb_feature.location} "
                                  f"was skipped\n    since it does not have id qualifier {id_source} found for other "
                                  f"features.\n    it could be a case of zero length ORF. ", file=sys.stdout)
                            continue
                        feature_id = gb_feature.qualifiers[id_source][0].replace("|", "_")
                        transl_table = self.prms.args["default_transl_table"]
                        if "transl_table" in gb_feature.qualifiers.keys():
                            transl_table = int(gb_feature.qualifiers["transl_table"][0])
                        name = ""
                        if self.prms.args["genbank_CDS_name_source"] in gb_feature.qualifiers:
                            name = gb_feature.qualifiers[self.prms.args["genbank_CDS_name_source"]][0]
                        category = ""
                        if self.prms.args["genbank_CDS_category_source"] in gb_feature.qualifiers:
                            category = ",".join(gb_feature.qualifiers[self.prms.args["genbank_CDS_category_source"]])

                        for coordinate in record_locus.coordinates:
                            overlapping = False
                            start, end = coordinate["start"], coordinate["end"]
                            if start <= gb_feature.location.start + 1 <= end or start <= gb_feature.location.end <= end:
                                overlapping = True
                                break
                        if not overlapping and not self.prms.args["cluster_all_proteins"]:
                            continue
                        self.__update_feature_annotation(feature_id, record_locus.seq_id,
                                                         f"{int(gb_feature.location.start) + 1}:"
                                                         f"{int(gb_feature.location.end)}:"
                                                         f"{gb_feature.location.strand}", "CDS", category, name)
                        feature_annotation_row = self.feature_annotation.loc[feature_id]
                        feature = Feature(feature_type=feature_annotation_row["feature_type"],
                                          feature_id=feature_id, start=int(gb_feature.location.start) + 1,
                                          end=int(gb_feature.location.end),
                                          strand=gb_feature.location.strand,
                                          name=feature_annotation_row["name"],
                                          sequence=gb_feature.translate(record_locus_sequence,
                                                                        table=transl_table,
                                                                        cds=False)[:-1],
                                          group=feature_annotation_row["group"],
                                          group_type=feature_annotation_row["group_type"],
                                          category=feature_annotation_row["category"],
                                          vis_prms=dict(fill_colour=feature_annotation_row["fill_colour"],
                                                        stroke_colour=feature_annotation_row["stroke_colour"],
                                                        show_label=feature_annotation_row["show_label"]),
                                          overlapping = overlapping,
                                          parameters=self.prms)

                        record_locus.features.append(feature)
                    self.loci.append(record_locus)
                except:
                    print(f"○ Warning: gb file {gb_file} was not read properly and skipped")
                    if self.prms.args["parsing_debug"]:
                        self.prms.args["debug"] = True
                        raise lovis4u.Manager.lovis4uError()
            seq_id_to_order = self.locus_annotation["order"].to_dict()
            loci_ids = [l.seq_id for l in self.loci]
            if len(loci_ids) != len(set(loci_ids)):
                raise lovis4u.Manager.lovis4uError(f"The input gb files have duplicated contig ids. "
                                                   f"You can use `--use-filename-as-id` parameter to use file name "
                                                   f"as contig id which can help to fix the problem.")
            self.loci.sort(key=lambda locus: seq_id_to_order[locus.seq_id])
            if self.prms.args["verbose"]:
                print(f"⦿ {len(self.loci)} {'locus was' if len(self.loci) == 1 else 'loci were'} loaded from the "
                      f"genbank files folder", file=sys.stdout)
            return None
        except Exception as error:
            raise lovis4u.Manager.lovis4uError("Unable to load loci from gb folder.") from error

    def save_locus_annotation_table(self) -> None:
        """Save loci annotation table to the output folder.

        Output file name is locus_annotation_table.tsv

        Returns:
            None

        """
        try:
            if not os.path.exists(self.prms.args["output_dir"]):
                os.mkdir(self.prms.args["output_dir"])
            file_path = os.path.join(self.prms.args["output_dir"], "locus_annotation_table.tsv")
            self.locus_annotation.to_csv(file_path, sep="\t", index_label="sequence_id")
            if self.prms.args["verbose"]:
                print(f"⦿ Loci annotation table was saved to {file_path}", file=sys.stdout)
            return None
        except Exception as error:
            raise lovis4u.Manager.lovis4uError("Unable to save loci annotation table.") from error

    def save_feature_annotation_table(self) -> None:
        """Save feature annotation table to the output folder.

        Output file name is feature_annotation_table.tsv

        Returns:
            None

        """
        try:
            if not os.path.exists(self.prms.args["output_dir"]):
                os.mkdir(self.prms.args["output_dir"])
            file_path = os.path.join(self.prms.args["output_dir"], "feature_annotation_table.tsv")
            self.feature_annotation.to_csv(file_path, sep="\t", index_label="feature_id")
            if self.prms.args["verbose"]:
                print(f"⦿ Feature annotation table was saved to {file_path}", file=sys.stdout)
            return None
        except Exception as error:
            raise lovis4u.Manager.lovis4uError("Unable to save feature annotation table.") from error

    def mmseqs_cluster(self) -> pd.DataFrame:
        """Cluster all proteins using mmseqs in order to define groups of homologues.

        Returns:
            pd.DataFrame: parsed mmseqs table (pandas dataframe) with columns: cluster, protein_id; where cluster is
                defined by representative sequence id within a corresponding cluster.

        """
        if self.prms.args["verbose"]:
            print(f"○ Running mmseqs for protein clustering...", file=sys.stdout)
        try:
            feature_records = [feature.record for locus in self.loci for feature in locus.features]
            temp_input = tempfile.NamedTemporaryFile()
            Bio.SeqIO.write(feature_records, temp_input.name, "fasta")
            if not os.path.exists(self.prms.args["output_dir"]):
                os.mkdir(self.prms.args["output_dir"])
            mmseqs_output_folder = os.path.join(self.prms.args["output_dir"], "mmseqs")
            if os.path.exists(mmseqs_output_folder):
                shutil.rmtree(mmseqs_output_folder)
            os.mkdir(mmseqs_output_folder)
            Bio.SeqIO.write(feature_records, os.path.join(mmseqs_output_folder, "input_proteins.fa"), "fasta")
            mmseqs_output_folder_db = os.path.join(mmseqs_output_folder, "DB")
            os.mkdir(mmseqs_output_folder_db)
            mmseqs_stdout = open(os.path.join(mmseqs_output_folder, "mmseqs_stdout.txt"), "w")
            mmseqs_stderr = open(os.path.join(mmseqs_output_folder, "mmseqs_stderr.txt"), "w")
            subprocess.run([self.prms.args["mmseqs_binary"], "createdb", temp_input.name,
                            os.path.join(mmseqs_output_folder_db, "sequencesDB")], stdout=mmseqs_stdout,
                           stderr=mmseqs_stderr)
            subprocess.run([self.prms.args["mmseqs_binary"], "cluster",
                            os.path.join(mmseqs_output_folder_db, "sequencesDB"),
                            os.path.join(mmseqs_output_folder_db, "clusterDB"),
                            os.path.join(mmseqs_output_folder_db, "tmp"),
                            "--cluster-mode", str(self.prms.args["mmseqs_cluster_mode"]),
                            "--cov-mode", str(self.prms.args["mmseqs_cov_mode"]),
                            "--min-seq-id", str(self.prms.args["mmseqs_min_seq_id"]),
                            "-c", str(self.prms.args["mmseqs_c"]),
                            "-s", str(self.prms.args["mmseqs_s"])], stdout=mmseqs_stdout, stderr=mmseqs_stderr)
            subprocess.run([self.prms.args["mmseqs_binary"], "createtsv",
                            os.path.join(mmseqs_output_folder_db, "sequencesDB"),
                            os.path.join(mmseqs_output_folder_db, "sequencesDB"),
                            os.path.join(mmseqs_output_folder_db, "clusterDB"),
                            os.path.join(mmseqs_output_folder, "mmseqs_clustering.tsv")],
                           stdout=mmseqs_stdout, stderr=mmseqs_stderr)
            mmseqs_clustering_results = pd.read_table(os.path.join(mmseqs_output_folder, "mmseqs_clustering.tsv"),
                                                      sep="\t", header=None, names=["cluster", "protein_id"])
            mmseqs_clustering_results = mmseqs_clustering_results.set_index("protein_id")

            num_of_unique_clusters = len(set(mmseqs_clustering_results["cluster"].to_list()))
            num_of_proteins = len(mmseqs_clustering_results.index)
            if self.prms.args["verbose"]:
                print(f"⦿ {num_of_unique_clusters} clusters for {num_of_proteins} proteins were found with mmseqs\n"
                      f"\tmmseqs clustering results were saved to "
                      f"{os.path.join(mmseqs_output_folder, 'mmseqs_clustering.tsv')}", file=sys.stdout)
            return mmseqs_clustering_results
        except Exception as error:
            raise lovis4u.Manager.lovis4uError("Unable to run mmseqs clustering. In case you use a linux machine"
                                               ",  have you run a post-install 'lovis4u --linux` command to switch to"
                                               " the linux mmseqs binary?") from error

    def define_feature_groups(self, dataframe: pd.DataFrame, group_column_name: str = "cluster") -> None:
        """Set features attribute "group" based on input dataframe.

        By default is designed to use mmseqs_cluster() function results as input. If you already have precomputed
            feature groups you can set them with feature table.

        Arguments:
            dataframe (pd.DataFrame): dataframe with feature id - group pairs. Its index column should
                represent all loci CDS features.
            group_column_name (str): column name of the dataframe that represent corresponding group to each feature.

        Returns:
            None

        """
        try:

            for locus in self.loci:
                for feature in locus.features:
                    if feature.group and self.prms.args["keep_predefined_groups"]:
                        continue
                    feature.group = dataframe.loc[feature.feature_id, group_column_name]
                    self.feature_annotation.loc[feature.feature_id, "group"] = feature.group
            return None
        except Exception as error:
            raise lovis4u.Manager.lovis4uError("Unable to define protein features groups.") from error

    def remove_non_overlapping_features(self) -> None:
        """Removes features that are not overlapping with visualisation window.

        Returns:
            None
        """
        try:
            ids_of_non_overlapping_objects = []
            for locus in self.loci:
                ids_of_non_overlapping_objects += [obj.feature_id for obj in locus.features if not obj.overlapping]
                filtered_objects = [obj for obj in locus.features if obj.overlapping]
                locus.features = filtered_objects
            if ids_of_non_overlapping_objects:
                print("○ Warning message: LoVis4u clusters all proteins by default to define their classes"
                      "\n\t('variable' or 'conserved'), including those outside the visualisation window."
                      "\n\tTo cluster only proteins within the visualised area, use the -cl-owp parameter.")
            self.feature_annotation = self.feature_annotation.drop(ids_of_non_overlapping_objects)
        except Exception as error:
            raise lovis4u.Manager.lovis4uError("Unable to clean non overlapping features.") from error

    def define_labels_to_be_shown(self):
        """Set feature visaulisation attribute "show_label" based on feature groups.

        controlled by feature_labels_to_ignore, feature_group_types_to_show_label, and
            feature_group_types_to_show_label_on_first_occurrence parameters.

        Returns:
            None

        """
        try:
            added_first_occurrence_labels = []
            for locus in self.loci:
                for feature in locus.features:
                    if self.prms.args["show_all_feature_labels"]:
                        feature.vis_prms["show_label"] = 1
                        continue
                    if feature.vis_prms["label"] not in self.prms.args["feature_labels_to_ignore"]:
                        if "any" in self.prms.args["feature_group_types_to_show_label"]:
                            feature.vis_prms["show_label"] = 1
                        elif feature.group_type in self.prms.args["feature_group_types_to_show_label"]:
                            feature.vis_prms["show_label"] = 1
                        elif feature.group_type in \
                                self.prms.args["feature_group_types_to_show_label_on_first_occurrence"]:
                            if feature.group not in added_first_occurrence_labels:
                                feature.vis_prms["show_label"] = 1
                                added_first_occurrence_labels.append(feature.group)
                    else:
                        feature.vis_prms["show_label"] = 0
        except Exception as error:
            raise lovis4u.Manager.lovis4uError("Unable define feature labels to be shown.") from error

    def cluster_sequences(self, dataframe: pd.DataFrame, one_cluster: bool) -> None:
        """Define loci order and clusters with proteome similarity based hierarchical clustering.
            This function changes the order of loci that are plotted and also updates corresponding to each loci group
            attribute which defines homologues groups of proteomes.

        It's designed to use as input mmseqs_cluster() function results. However, if you have obtained homologues
            groups by other method you can also build pandas dataframe based on that with index corresponding to
            feature id and column "cluster" corresponding to the group.

        Arguments:
              dataframe (pd.DataFrame): dataframe with feature id - group pairs. Its index column should
                represent all loci CDS features.
                one_cluster (bool): consider all sequences to be members of one cluster, but still define the
                optimal order.

        Returns:
            None

        """
        try:
            proteins_loci_dict = collections.defaultdict(collections.deque)
            loci_clusters_dict = dict()
            number_of_loci = len(self.loci)
            if number_of_loci < 2:
                return None
            proteome_sizes = pd.Series(np.zeros(number_of_loci, dtype=int))
            for locus_index in range(number_of_loci):
                locus = self.loci[locus_index]
                loci_clusters = [dataframe.loc[feature.feature_id, "cluster"] for feature in locus.features]
                loci_clusters_dict[locus_index] = list(set(loci_clusters))
                proteome_sizes.iloc[locus_index] = len(set(loci_clusters))
                for l_cl in loci_clusters:
                    proteins_loci_dict[l_cl].append(locus_index)

            loci_ids = [locus.seq_id for locus in self.loci]
            similarity_matrix = pd.DataFrame(0.0, index=loci_ids, columns=loci_ids)
            for locus_index in range(number_of_loci):
                counts = pd.Series(np.zeros(number_of_loci, dtype=int))
                for cluster in loci_clusters_dict[locus_index]:
                    js = proteins_loci_dict[cluster]
                    counts.iloc[js] += 1
                locus_size = proteome_sizes[locus_index]
                norm_factors = pd.Series(0.5 * (locus_size + proteome_sizes) / (locus_size * proteome_sizes),
                                         index=counts.index)
                weights = counts.mul(norm_factors)
                similarity_matrix.iloc[locus_index] = weights
            symmetric_distance_matrix = 1 - similarity_matrix
            np.fill_diagonal(symmetric_distance_matrix.values, 0)
            linkage_matrix = scipy.cluster.hierarchy.linkage(
                scipy.spatial.distance.squareform(symmetric_distance_matrix),
                method="average")
            dendrogram = scipy.cluster.hierarchy.dendrogram(linkage_matrix, no_plot=True)
            if not one_cluster:
                clusters = pd.Series(scipy.cluster.hierarchy.fcluster(linkage_matrix,
                                                                      self.prms.args["clustering_h_value"],
                                                                      criterion="distance"),
                                     index=loci_ids)
                for locus in self.loci:
                    locus.group = clusters[locus.seq_id]
                    self.locus_annotation.loc[locus.seq_id, "group"] = locus.group
            order = dendrogram["leaves"][::-1]
            self.locus_annotation["initial_order"] = self.locus_annotation["order"]
            for locus_index in range(number_of_loci):
                locus = self.loci[locus_index]
                self.locus_annotation.loc[locus.seq_id, "order"] = order.index(locus_index)
            self.locus_annotation.sort_values(by="order", inplace=True)
            seq_id_to_order = self.locus_annotation["order"].to_dict()
            self.loci.sort(key=lambda locus: seq_id_to_order[locus.seq_id])

            reordered_similarity_matrix = similarity_matrix.reindex(index=self.locus_annotation.index,
                                                                    columns=self.locus_annotation.index)
            if not os.path.exists(self.prms.args["output_dir"]):
                os.mkdir(self.prms.args["output_dir"])
            file_path = os.path.join(self.prms.args["output_dir"], "proteome_similarity_matrix.tsv")
            reordered_similarity_matrix.to_csv(file_path, sep="\t")
            num_of_loci_groups = len(set(self.locus_annotation["group"].to_list()))
            if self.prms.args["verbose"]:
                if num_of_loci_groups == 1:
                    print(f"⦿ Loci order and {num_of_loci_groups} cluster was defined with proteome similarity based "
                          f"hierarchical clustering", file=sys.stdout)
                elif num_of_loci_groups > 1:
                    print(f"⦿ Loci order and {num_of_loci_groups} clusters were defined with proteome similarity based "
                          f"hierarchical clustering", file=sys.stdout)
                print(f"⦿ Proteome similarity matrix of loci was saved to {file_path}", file=sys.stdout)
            return None
        except Exception as error:
            raise lovis4u.Manager.lovis4uError("Unable to cluster loci sequences.") from error

    def find_variable_feature_groups(self, mmseqs_results: pd.DataFrame) -> None:
        """Define feature group type attributes (variable or conserved) based on their conservation in corresponding
            loci group feature.

        It's designed to use as input mmseqs_cluster() function results. However, if you have obtained homologues
            groups by other method you can also build pandas dataframe based on that with index corresponding to
            feature id and column "cluster" corresponding to the group.

        Arguments:
              mmseqs_results (pd.DataFrame): dataframe with feature id - group pairs. Its index column should
                represent all loci CDS features.

        Returns:
            None

        """
        try:
            loci_clusters_sizes = self.locus_annotation["group"].value_counts()
            loci_clusters_cutoff_v = np.round(self.prms.args["CDS_is_variable_cutoff"] * loci_clusters_sizes).astype(
                int)
            loci_clusters_cutoff_c = np.round(self.prms.args["CDF_is_conserved_cutoff"] * loci_clusters_sizes).astype(
                int)
            loci_clusters_cutoff_v[loci_clusters_cutoff_v == 0] = 1
            cluster_types = collections.defaultdict(dict)
            for cluster in set(mmseqs_results["cluster"].to_list()):
                cluster_proteins = mmseqs_results[mmseqs_results["cluster"] == cluster].index
                cluster_loci = [locus for locus in self.loci if
                                any(feature.feature_id in cluster_proteins for feature in locus.features)]
                cluster_loci_groups = [locus.group for locus in cluster_loci]
                for cluster_locus_group in cluster_loci_groups:
                    current_group_cluster_loci = [locus.seq_id for locus in cluster_loci if
                                                  locus.group == cluster_locus_group]
                    current_group_cluster_size = len(set(current_group_cluster_loci))
                    if loci_clusters_sizes[cluster_locus_group] > 1 and \
                            current_group_cluster_size <= loci_clusters_cutoff_v[cluster_locus_group]:
                        cluster_types[cluster_locus_group][cluster] = "variable"
                    elif loci_clusters_sizes[cluster_locus_group] > 1 and \
                            (loci_clusters_cutoff_v[cluster_locus_group] < current_group_cluster_size <
                             loci_clusters_cutoff_c[cluster_locus_group]):
                        cluster_types[cluster_locus_group][cluster] = "intermediate"
                    else:
                        cluster_types[cluster_locus_group][cluster] = "conserved"
            for locus in self.loci:
                locus_group = locus.group
                for feature in locus.features:
                    if feature.group_type and self.prms.args["keep_predefined_groups"]:
                        continue
                    feature.group_type = cluster_types[locus_group][feature.group]
                    self.feature_annotation.loc[feature.feature_id, "group_type"] = feature.group_type
            return None
        except Exception as error:
            raise lovis4u.Manager.lovis4uError("Unable to define variable feature groups.") from error

    def set_feature_colours_based_on_groups(self) -> None:
        """Define features fill colour based on corresponding feature group and group types.

        Returns:
            None

        """
        try:
            feature_groups = set([feature.group for locus in self.loci for feature in locus.features if feature.group])
            if self.prms.args["feature_group_types_to_set_colour"] and \
                    "all" not in self.prms.args["feature_group_types_to_set_colour"]:
                feature_groups = set([feature.group for locus in self.loci for feature in locus.features
                                      if feature.group and feature.group_type in
                                      self.prms.args["feature_group_types_to_set_colour"]])
            number_of_unique_feature_groups = len(feature_groups)
            if self.prms.args["groups_fill_colour_palette_lib"] == "seaborn":
                colours_rgb = seaborn.color_palette(self.prms.args["groups_fill_colour_seaborn_palette"],
                                                    number_of_unique_feature_groups,
                                                    desat=self.prms.args["groups_fill_colour_seaborn_desat"])
                random.shuffle(colours_rgb)
            elif self.prms.args["groups_fill_colour_palette_lib"] == "distinctipy":
                colours_rgb = distinctipy.get_colors(number_of_unique_feature_groups,
                                                     exclude_colours=[(1, 1, 1), (0, 0, 0)],
                                                     pastel_factor=self.prms.args["groups_fill_colours_pastel_factor"])
            colours = list(map(lambda x: matplotlib.colors.rgb2hex(x), colours_rgb))
            colours_dict = {g: c for g, c in zip(list(feature_groups), colours)}
            for locus in self.loci:
                for feature in locus.features:
                    if feature.group in feature_groups:
                        if self.prms.args["keep_predefined_colours"] and feature.vis_prms["fill_colour"] != "default":
                            continue
                        feature.vis_prms["fill_colour"] = colours_dict[feature.group]
                        self.feature_annotation.loc[feature.feature_id, "fill_colour"] = feature.vis_prms["fill_colour"]
            return None
        except Exception as error:
            raise lovis4u.Manager.lovis4uError("Unable to set feature colours based on groups.") from error

    def set_category_colours(self, use_table: bool = True) -> None:
        """Define colours for each category.

        Arguments:
            use_table (bool): Bool value whether table with predefined colours should be used or not.

        Returns:
            None

        """
        try:
            colours_dict = dict()
            if use_table:
                colours_dict.update(
                    pd.read_table(self.prms.args["category_colours"]).set_index("category")["colour"].to_dict())

            feature_categories = list(set([feature.category for locus in self.loci for feature in locus.features
                                           if feature.category and feature.category]))
            if not feature_categories:
                if self.prms.args["verbose"]:
                    print("○ Warning: there are no feature categories to set colours", file=sys.stdout)
            colours_dict = {cat: col for cat, col in colours_dict.items() if cat in feature_categories}

            feature_categories = [ff for ff in feature_categories if ff not in colours_dict.keys()]
            number_of_unique_feature_functions = len(feature_categories)
            colours_rgb = seaborn.color_palette(self.prms.args["category_colour_seaborn_palette"],
                                                number_of_unique_feature_functions,
                                                desat=self.prms.args["category_colour_seaborn_desat"])
            random.shuffle(colours_rgb)
            colours = list(map(lambda x: matplotlib.colors.rgb2hex(x), colours_rgb))
            colours_dict.update({g: c for g, c in zip(list(feature_categories), colours)})
            for locus in self.loci:
                locus.category_colours = colours_dict
            return None
        except Exception as error:
            raise lovis4u.Manager.lovis4uError("Unable to set category colours.") from error

    def reorient_loci(self, ilund4u_mode: bool = False) -> None:
        """Auto re-orient loci (reset strands) of loci if they are not matched.

        Function tries to maximise co-orientation of homologous features.

        Returns:
            None

        """
        try:
            count_of_changed_strands = 0
            loci = [locus for locus in self.loci]
            for locus_index in range(1, len(loci)):
                p_locus = loci[locus_index - 1]
                c_locus = loci[locus_index]
                p_locus_strands = list(set([c["strand"] for c in p_locus.coordinates]))
                c_locus_strands = list(set([c["strand"] for c in c_locus.coordinates]))
                if len(p_locus_strands) == 1 and len(c_locus_strands) == 1:
                    if not ilund4u_mode:
                        pr_locus_features_groups = set([f.group for f in p_locus.features])
                        c_locus_features_groups = set([f.group for f in c_locus.features])
                    else:
                        pr_locus_features_groups = set(
                            [f.group for f in p_locus.features if f.group_type == "conserved"])
                        c_locus_features_groups = set(
                            [f.group for f in c_locus.features if f.group_type == "conserved"])
                    overlapped_f_groups = pr_locus_features_groups & c_locus_features_groups
                    prl_strand, cl_strand = p_locus_strands[0], c_locus_strands[0]
                    pr_locus_features_strands = {f.group: f.strand * prl_strand for f in p_locus.features if
                                                 f.group in overlapped_f_groups}
                    c_locus_features_strands = {f.group: f.strand * cl_strand for f in c_locus.features if
                                                f.group in overlapped_f_groups}
                    codirection_score = 0

                    for ovg in overlapped_f_groups:
                        codirection_score += pr_locus_features_strands[ovg] * c_locus_features_strands[ovg]
                    if codirection_score < 0:
                        count_of_changed_strands += 1
                        annot_coordinates = []
                        for cc in loci[locus_index].coordinates:
                            cc["strand"] *= -1
                            annot_coordinates.append(f"{cc['start']}:{cc['end']}:{cc['strand']}")
                        loci[locus_index].coordinates = loci[locus_index].coordinates[::-1]
                        annot_coordinates = annot_coordinates[::-1]
                        self.locus_annotation.loc[c_locus.seq_id, "coordinates"] = ",".join(annot_coordinates)
                else:
                    if self.prms.args["verbose"]:
                        print("○ Warning: loci reorientation cannot be applied for loci that have both strands in"
                              " pre-defined coordinates for visualisation")
            if self.prms.args["verbose"]:
                if count_of_changed_strands == 0:
                    print(f"⦿ Orientation was not changed for any locus", file=sys.stdout)
                elif count_of_changed_strands == 1:
                    print(f"⦿ Orientation was changed for 1 locus", file=sys.stdout)
                elif count_of_changed_strands > 1:
                    print(f"⦿ Orientation was changed for {count_of_changed_strands} loci", file=sys.stdout)

            return None
        except Exception as error:
            raise lovis4u.Manager.lovis4uError("Unable to define variable feature groups.") from error

    def get_loci_lengths_and_n_of_regions(self) -> list[list[int]]:
        """Get loci lengths and number of regions.

        Returns:
            list: list each element of each contains locus size and number of breaks for visualisation track.

        """
        try:
            loci_sizes = []
            for locus in self.loci:
                number_of_gaps = len(locus.coordinates) - 1
                if locus.circular:
                    for i in range(number_of_gaps):
                        if locus.coordinates[i]["end"] == locus.length and locus.coordinates[i + 1]["start"] == 1:
                            number_of_gaps -= 1
                loci_sizes.append([locus.size, number_of_gaps])
            return loci_sizes
        except Exception as error:
            raise lovis4u.Manager.lovis4uError("Unable to get loci lengths.") from error

__init__(parameters=lovis4u.Manager.Parameters)

Create a Loci object.

Parameters:

  • parameters (Parameters, default: Parameters ) –

    Parameters' class object that holds config and cmd arguments.

Source code in lovis4u/DataProcessing.py
def __init__(self, parameters=lovis4u.Manager.Parameters):
    """Create a Loci object.

    Arguments:
        parameters (lovis4u.Manager.Parameters): Parameters' class object that holds config and cmd arguments.
    """
    self.loci = []
    self.locus_annotation = pd.DataFrame(columns=["sequence_id", "length", "coordinates", "circular", "description",
                                                  "order", "group"]).set_index("sequence_id")
    self.feature_annotation = pd.DataFrame(columns=["feature_id", "locus_id", "coordinates", "feature_type", "name",
                                                    "group", "group_type", "category", "fill_colour",
                                                    "stroke_colour",
                                                    "show_label"]).set_index("feature_id")
    self.prms = parameters

__load_annotation_file(file_path, annotation_columns, index_column)

Private method to load an annotation file.

Parameters:

  • file_path (str) –

    File path for an annotation file to be loaded.

  • annotation_columns (list) –

    List of columns that should be considered.

  • index_column (str) –

    Column name to be considered as index.

Returns:

  • DataFrame

    pd.DataFrame: Preprocessed annotation file.

Source code in lovis4u/DataProcessing.py
def __load_annotation_file(self, file_path: str, annotation_columns: list, index_column: str) -> pd.DataFrame:
    """Private method to load an annotation file.

    Arguments:
        file_path (str): File path for an annotation file to be loaded.
        annotation_columns (list): List of columns that should be considered.
        index_column (str): Column name to be considered as index.

    Returns:
          pd.DataFrame: Preprocessed annotation file.
    """
    annotation_table = pd.read_table(file_path)
    found_allowed_columns = [i for i in annotation_columns if i in annotation_table.columns]
    not_found_allowed_columns = [i for i in annotation_columns if i not in annotation_table.columns]
    annotation_table = annotation_table[found_allowed_columns].set_index(index_column)
    annotation_table[not_found_allowed_columns] = None
    return annotation_table

__update_feature_annotation(feature_id, locus_id, coordinates, feature_type, category, name)

Private method for updating feature annotation.

Parameters:

  • feature_id (str) –

    Feature identifier.

  • locus_id (str) –

    Sequence description.

  • coordinates (str) –

    Feature coordinates.

  • category (str) –

    Feature type.

  • name (str) –

    Feature name.

Returns:

  • None

    None

Source code in lovis4u/DataProcessing.py
def __update_feature_annotation(self, feature_id: str, locus_id: str, coordinates: str, feature_type: str,
                                category: str, name: str) -> None:
    """Private method for updating feature annotation.

    Arguments:
        feature_id (str): Feature identifier.
        locus_id (str): Sequence description.
        coordinates (str): Feature coordinates.
        category (str): Feature type.
        name (str): Feature name.


    Returns:
        None

    """
    if feature_id not in self.feature_annotation.index:
        self.feature_annotation.loc[feature_id] = {col: None for col in self.feature_annotation.columns}

    if self.feature_annotation.loc[feature_id]["group_type"] in self.prms.args["feature_group_types_to_show_label"] \
            or "all" in self.prms.args["feature_group_types_to_show_label"]:
        show_label = 1
    else:
        show_label = 0
    stroke_colour = "default"
    if self.prms.args["set_feature_stroke_colour_based_on_fill_colour"] and \
            self.feature_annotation.loc[feature_id]["fill_colour"] and \
            self.feature_annotation.loc[feature_id]["fill_colour"] != "default":
        stroke_colour = lovis4u.Methods.scale_lightness(self.feature_annotation.loc[feature_id]["fill_colour"],
                                                        self.prms.args["feature_stroke_colour_relative_lightness"])
    default_values = dict(locus_id=locus_id, coordinates=coordinates, feature_type=feature_type,
                          name=name, group="", group_type="", category=category, fill_colour="default",
                          stroke_colour=stroke_colour,
                          show_label=show_label)
    self.feature_annotation.loc[feature_id] = self.feature_annotation.loc[feature_id].fillna(default_values)
    return None

__update_locus_annotation(record_id, record_description, record_length)

Private method for updating loci annotation.

Parameters:

  • record_id (str) –

    Sequence identifier.

  • record_description (str) –

    Sequence description.

  • record_length (int) –

    Sequence length.

Returns:

  • None

    None

Source code in lovis4u/DataProcessing.py
def __update_locus_annotation(self, record_id: str, record_description: str, record_length: int) -> None:
    """Private method for updating loci annotation.

    Arguments:
        record_id (str): Sequence identifier.
        record_description (str): Sequence description.
        record_length (int): Sequence length.

    Returns:
        None

    """
    if record_id not in self.locus_annotation.index:
        self.locus_annotation.loc[record_id] = {col: None for col in self.locus_annotation.columns}

    default_values = dict(length=record_length, coordinates=f"1:{record_length}:1",
                          description=record_description, circular=1, order=len(self.loci), group=1)
    self.locus_annotation.loc[record_id] = self.locus_annotation.loc[record_id].fillna(default_values)
    return None

cluster_sequences(dataframe, one_cluster)

Define loci order and clusters with proteome similarity based hierarchical clustering. This function changes the order of loci that are plotted and also updates corresponding to each loci group attribute which defines homologues groups of proteomes.

It's designed to use as input mmseqs_cluster() function results. However, if you have obtained homologues groups by other method you can also build pandas dataframe based on that with index corresponding to feature id and column "cluster" corresponding to the group.

Parameters:

  • dataframe (DataFrame) –

    dataframe with feature id - group pairs. Its index column should represent all loci CDS features. one_cluster (bool): consider all sequences to be members of one cluster, but still define the optimal order.

Returns:

  • None

    None

Source code in lovis4u/DataProcessing.py
def cluster_sequences(self, dataframe: pd.DataFrame, one_cluster: bool) -> None:
    """Define loci order and clusters with proteome similarity based hierarchical clustering.
        This function changes the order of loci that are plotted and also updates corresponding to each loci group
        attribute which defines homologues groups of proteomes.

    It's designed to use as input mmseqs_cluster() function results. However, if you have obtained homologues
        groups by other method you can also build pandas dataframe based on that with index corresponding to
        feature id and column "cluster" corresponding to the group.

    Arguments:
          dataframe (pd.DataFrame): dataframe with feature id - group pairs. Its index column should
            represent all loci CDS features.
            one_cluster (bool): consider all sequences to be members of one cluster, but still define the
            optimal order.

    Returns:
        None

    """
    try:
        proteins_loci_dict = collections.defaultdict(collections.deque)
        loci_clusters_dict = dict()
        number_of_loci = len(self.loci)
        if number_of_loci < 2:
            return None
        proteome_sizes = pd.Series(np.zeros(number_of_loci, dtype=int))
        for locus_index in range(number_of_loci):
            locus = self.loci[locus_index]
            loci_clusters = [dataframe.loc[feature.feature_id, "cluster"] for feature in locus.features]
            loci_clusters_dict[locus_index] = list(set(loci_clusters))
            proteome_sizes.iloc[locus_index] = len(set(loci_clusters))
            for l_cl in loci_clusters:
                proteins_loci_dict[l_cl].append(locus_index)

        loci_ids = [locus.seq_id for locus in self.loci]
        similarity_matrix = pd.DataFrame(0.0, index=loci_ids, columns=loci_ids)
        for locus_index in range(number_of_loci):
            counts = pd.Series(np.zeros(number_of_loci, dtype=int))
            for cluster in loci_clusters_dict[locus_index]:
                js = proteins_loci_dict[cluster]
                counts.iloc[js] += 1
            locus_size = proteome_sizes[locus_index]
            norm_factors = pd.Series(0.5 * (locus_size + proteome_sizes) / (locus_size * proteome_sizes),
                                     index=counts.index)
            weights = counts.mul(norm_factors)
            similarity_matrix.iloc[locus_index] = weights
        symmetric_distance_matrix = 1 - similarity_matrix
        np.fill_diagonal(symmetric_distance_matrix.values, 0)
        linkage_matrix = scipy.cluster.hierarchy.linkage(
            scipy.spatial.distance.squareform(symmetric_distance_matrix),
            method="average")
        dendrogram = scipy.cluster.hierarchy.dendrogram(linkage_matrix, no_plot=True)
        if not one_cluster:
            clusters = pd.Series(scipy.cluster.hierarchy.fcluster(linkage_matrix,
                                                                  self.prms.args["clustering_h_value"],
                                                                  criterion="distance"),
                                 index=loci_ids)
            for locus in self.loci:
                locus.group = clusters[locus.seq_id]
                self.locus_annotation.loc[locus.seq_id, "group"] = locus.group
        order = dendrogram["leaves"][::-1]
        self.locus_annotation["initial_order"] = self.locus_annotation["order"]
        for locus_index in range(number_of_loci):
            locus = self.loci[locus_index]
            self.locus_annotation.loc[locus.seq_id, "order"] = order.index(locus_index)
        self.locus_annotation.sort_values(by="order", inplace=True)
        seq_id_to_order = self.locus_annotation["order"].to_dict()
        self.loci.sort(key=lambda locus: seq_id_to_order[locus.seq_id])

        reordered_similarity_matrix = similarity_matrix.reindex(index=self.locus_annotation.index,
                                                                columns=self.locus_annotation.index)
        if not os.path.exists(self.prms.args["output_dir"]):
            os.mkdir(self.prms.args["output_dir"])
        file_path = os.path.join(self.prms.args["output_dir"], "proteome_similarity_matrix.tsv")
        reordered_similarity_matrix.to_csv(file_path, sep="\t")
        num_of_loci_groups = len(set(self.locus_annotation["group"].to_list()))
        if self.prms.args["verbose"]:
            if num_of_loci_groups == 1:
                print(f"⦿ Loci order and {num_of_loci_groups} cluster was defined with proteome similarity based "
                      f"hierarchical clustering", file=sys.stdout)
            elif num_of_loci_groups > 1:
                print(f"⦿ Loci order and {num_of_loci_groups} clusters were defined with proteome similarity based "
                      f"hierarchical clustering", file=sys.stdout)
            print(f"⦿ Proteome similarity matrix of loci was saved to {file_path}", file=sys.stdout)
        return None
    except Exception as error:
        raise lovis4u.Manager.lovis4uError("Unable to cluster loci sequences.") from error

define_feature_groups(dataframe, group_column_name='cluster')

Set features attribute "group" based on input dataframe.

By default is designed to use mmseqs_cluster() function results as input. If you already have precomputed feature groups you can set them with feature table.

Parameters:

  • dataframe (DataFrame) –

    dataframe with feature id - group pairs. Its index column should represent all loci CDS features.

  • group_column_name (str, default: 'cluster' ) –

    column name of the dataframe that represent corresponding group to each feature.

Returns:

  • None

    None

Source code in lovis4u/DataProcessing.py
def define_feature_groups(self, dataframe: pd.DataFrame, group_column_name: str = "cluster") -> None:
    """Set features attribute "group" based on input dataframe.

    By default is designed to use mmseqs_cluster() function results as input. If you already have precomputed
        feature groups you can set them with feature table.

    Arguments:
        dataframe (pd.DataFrame): dataframe with feature id - group pairs. Its index column should
            represent all loci CDS features.
        group_column_name (str): column name of the dataframe that represent corresponding group to each feature.

    Returns:
        None

    """
    try:

        for locus in self.loci:
            for feature in locus.features:
                if feature.group and self.prms.args["keep_predefined_groups"]:
                    continue
                feature.group = dataframe.loc[feature.feature_id, group_column_name]
                self.feature_annotation.loc[feature.feature_id, "group"] = feature.group
        return None
    except Exception as error:
        raise lovis4u.Manager.lovis4uError("Unable to define protein features groups.") from error

define_labels_to_be_shown()

Set feature visaulisation attribute "show_label" based on feature groups.

controlled by feature_labels_to_ignore, feature_group_types_to_show_label, and feature_group_types_to_show_label_on_first_occurrence parameters.

Returns:

  • None

Source code in lovis4u/DataProcessing.py
def define_labels_to_be_shown(self):
    """Set feature visaulisation attribute "show_label" based on feature groups.

    controlled by feature_labels_to_ignore, feature_group_types_to_show_label, and
        feature_group_types_to_show_label_on_first_occurrence parameters.

    Returns:
        None

    """
    try:
        added_first_occurrence_labels = []
        for locus in self.loci:
            for feature in locus.features:
                if self.prms.args["show_all_feature_labels"]:
                    feature.vis_prms["show_label"] = 1
                    continue
                if feature.vis_prms["label"] not in self.prms.args["feature_labels_to_ignore"]:
                    if "any" in self.prms.args["feature_group_types_to_show_label"]:
                        feature.vis_prms["show_label"] = 1
                    elif feature.group_type in self.prms.args["feature_group_types_to_show_label"]:
                        feature.vis_prms["show_label"] = 1
                    elif feature.group_type in \
                            self.prms.args["feature_group_types_to_show_label_on_first_occurrence"]:
                        if feature.group not in added_first_occurrence_labels:
                            feature.vis_prms["show_label"] = 1
                            added_first_occurrence_labels.append(feature.group)
                else:
                    feature.vis_prms["show_label"] = 0
    except Exception as error:
        raise lovis4u.Manager.lovis4uError("Unable define feature labels to be shown.") from error

find_variable_feature_groups(mmseqs_results)

Define feature group type attributes (variable or conserved) based on their conservation in corresponding loci group feature.

It's designed to use as input mmseqs_cluster() function results. However, if you have obtained homologues groups by other method you can also build pandas dataframe based on that with index corresponding to feature id and column "cluster" corresponding to the group.

Parameters:

  • mmseqs_results (DataFrame) –

    dataframe with feature id - group pairs. Its index column should represent all loci CDS features.

Returns:

  • None

    None

Source code in lovis4u/DataProcessing.py
def find_variable_feature_groups(self, mmseqs_results: pd.DataFrame) -> None:
    """Define feature group type attributes (variable or conserved) based on their conservation in corresponding
        loci group feature.

    It's designed to use as input mmseqs_cluster() function results. However, if you have obtained homologues
        groups by other method you can also build pandas dataframe based on that with index corresponding to
        feature id and column "cluster" corresponding to the group.

    Arguments:
          mmseqs_results (pd.DataFrame): dataframe with feature id - group pairs. Its index column should
            represent all loci CDS features.

    Returns:
        None

    """
    try:
        loci_clusters_sizes = self.locus_annotation["group"].value_counts()
        loci_clusters_cutoff_v = np.round(self.prms.args["CDS_is_variable_cutoff"] * loci_clusters_sizes).astype(
            int)
        loci_clusters_cutoff_c = np.round(self.prms.args["CDF_is_conserved_cutoff"] * loci_clusters_sizes).astype(
            int)
        loci_clusters_cutoff_v[loci_clusters_cutoff_v == 0] = 1
        cluster_types = collections.defaultdict(dict)
        for cluster in set(mmseqs_results["cluster"].to_list()):
            cluster_proteins = mmseqs_results[mmseqs_results["cluster"] == cluster].index
            cluster_loci = [locus for locus in self.loci if
                            any(feature.feature_id in cluster_proteins for feature in locus.features)]
            cluster_loci_groups = [locus.group for locus in cluster_loci]
            for cluster_locus_group in cluster_loci_groups:
                current_group_cluster_loci = [locus.seq_id for locus in cluster_loci if
                                              locus.group == cluster_locus_group]
                current_group_cluster_size = len(set(current_group_cluster_loci))
                if loci_clusters_sizes[cluster_locus_group] > 1 and \
                        current_group_cluster_size <= loci_clusters_cutoff_v[cluster_locus_group]:
                    cluster_types[cluster_locus_group][cluster] = "variable"
                elif loci_clusters_sizes[cluster_locus_group] > 1 and \
                        (loci_clusters_cutoff_v[cluster_locus_group] < current_group_cluster_size <
                         loci_clusters_cutoff_c[cluster_locus_group]):
                    cluster_types[cluster_locus_group][cluster] = "intermediate"
                else:
                    cluster_types[cluster_locus_group][cluster] = "conserved"
        for locus in self.loci:
            locus_group = locus.group
            for feature in locus.features:
                if feature.group_type and self.prms.args["keep_predefined_groups"]:
                    continue
                feature.group_type = cluster_types[locus_group][feature.group]
                self.feature_annotation.loc[feature.feature_id, "group_type"] = feature.group_type
        return None
    except Exception as error:
        raise lovis4u.Manager.lovis4uError("Unable to define variable feature groups.") from error

get_loci_lengths_and_n_of_regions()

Get loci lengths and number of regions.

Returns:

  • list ( list[list[int]] ) –

    list each element of each contains locus size and number of breaks for visualisation track.

Source code in lovis4u/DataProcessing.py
def get_loci_lengths_and_n_of_regions(self) -> list[list[int]]:
    """Get loci lengths and number of regions.

    Returns:
        list: list each element of each contains locus size and number of breaks for visualisation track.

    """
    try:
        loci_sizes = []
        for locus in self.loci:
            number_of_gaps = len(locus.coordinates) - 1
            if locus.circular:
                for i in range(number_of_gaps):
                    if locus.coordinates[i]["end"] == locus.length and locus.coordinates[i + 1]["start"] == 1:
                        number_of_gaps -= 1
            loci_sizes.append([locus.size, number_of_gaps])
        return loci_sizes
    except Exception as error:
        raise lovis4u.Manager.lovis4uError("Unable to get loci lengths.") from error

load_feature_annotation_file(file_path)

Load features annotation file.

Parameters:

  • file_path (str) –

    File path for a features annotation file to be loaded.

Returns:

  • None

    None

Source code in lovis4u/DataProcessing.py
def load_feature_annotation_file(self, file_path: str) -> None:
    """Load features annotation file.

    Arguments:
        file_path (str): File path for a features annotation file to be loaded.

    Returns:
        None

    """
    annotation_columns = ["feature_id", "locus_id", "coordinates", "feature_type", "name", "group", "group_type",
                          "category", "fill_colour", "stroke_colour", "show_label"]
    self.feature_annotation = self.__load_annotation_file(file_path, annotation_columns, "feature_id")
    return None

load_loci_from_extended_gff(input_f, ilund4u_mode=False)

Load loci from the folder with gff files. Each GFF file also should contain corresponding nucleotide sequence. Such files are produced for example by pharokka annotation tool.

All files with extension other than .gff (not case-sensitive) will be ignored.

Parameters:

  • input_folder

    folder name with gff files.

Returns:

  • None

    None

Source code in lovis4u/DataProcessing.py
def load_loci_from_extended_gff(self, input_f: str, ilund4u_mode: bool = False) -> None:
    """Load loci from the folder with gff files. Each GFF file also should contain corresponding nucleotide
        sequence. Such files are produced for example by pharokka annotation tool.

    All files with extension other than .gff (not case-sensitive) will be ignored.

    Arguments:
        input_folder: folder name with gff files.

    Returns:
        None

    """

    try:
        if isinstance(input_f, str):
            input_folder = input_f
            if not os.path.exists(input_folder):
                raise lovis4u.Manager.lovis4uError(f"Folder {input_folder} does not exist.")
            gff_files = [os.path.join(input_folder, f) for f in os.listdir(input_folder)]
        elif isinstance(input_f, list):
            gff_files = input_f
        else:
            raise lovis4u.Manager.lovis4uError(f"The input for the GFF parsing function must be either a folder or "
                                               f"a list of files.")
        if not gff_files:
            raise lovis4u.Manager.lovis4uError(f"Folder {input_f} does not contain files.")
        if self.prms.args["verbose"]:
            print(f"○ Reading gff file{'s' if len(gff_files) > 1 else ''}...", file=sys.stdout)
        for gff_file_path in gff_files:
            try:
                gff_file = gff_file_path
                gff_records = list(BCBio.GFF.parse(gff_file_path, limit_info=dict(gff_type=["CDS"])))
                if len(gff_records) != 1:
                    print(f"○ Warning: gff file {gff_file} contains information for more than 1 "
                          f"sequence. File will be skipped.")
                    continue
                gff_record = gff_records[0]
                try:
                    record_locus_sequence = gff_record.seq
                except Bio.Seq.UndefinedSequenceError:
                    print(f"○ Warning: gff file {gff_file} doesn't contain corresponding sequences.")
                    continue
                if self.prms.args["gff_description_source"] in gff_record.annotations:
                    record_description = gff_record.annotations[self.prms.args["gff_description_source"]][0]
                    if isinstance(record_description, tuple):
                        record_description = " ".join(record_description)
                else:
                    record_description = ""
                if self.prms.args["use_filename_as_contig_id"]:
                    gff_record.id = os.path.splitext(os.path.basename(gff_file))[0]
                self.__update_locus_annotation(gff_record.id, record_description, len(record_locus_sequence))
                locus_annotation_row = self.locus_annotation.loc[gff_record.id]
                coordinates = [dict(zip(["start", "end", "strand"], map(int, c.split(":")))) for c in
                               locus_annotation_row["coordinates"].split(",")]
                record_locus = Locus(seq_id=gff_record.id, coordinates=coordinates,
                                     description=locus_annotation_row["description"],
                                     circular=locus_annotation_row["circular"],
                                     length=locus_annotation_row["length"], parameters=self.prms, features=[],
                                     order=locus_annotation_row["order"])
                features_ids = [i.id for i in gff_record.features]
                if len(features_ids) != len(set(features_ids)):
                    raise lovis4u.Manager.lovis4uError(f"Gff file {gff_file} contains duplicated feature ids while"
                                                       f" only unique are allowed.")
                for gff_feature in gff_record.features:
                    feature_id = gff_feature.id
                    if ilund4u_mode:
                        if gff_record.id not in feature_id:
                            feature_id = f"{gff_record.id}-{feature_id}"
                    transl_table = self.prms.args["default_transl_table"]
                    if "transl_table" in gff_feature.qualifiers.keys():
                        transl_table = int(gff_feature.qualifiers["transl_table"][0])
                    name = ""
                    if self.prms.args["gff_CDS_name_source"] in gff_feature.qualifiers:
                        name = gff_feature.qualifiers[self.prms.args["gff_CDS_name_source"]][0]
                    category = ""
                    if self.prms.args["gff_CDS_category_source"] in gff_feature.qualifiers:
                        category = ",".join(gff_feature.qualifiers[self.prms.args["gff_CDS_category_source"]])
                    for coordinate in record_locus.coordinates:
                        overlapping = False
                        start, end = coordinate["start"], coordinate["end"]
                        if start <= gff_feature.location.start + 1 <= end or start <= gff_feature.location.end <= end:
                            overlapping = True
                            break
                    if not overlapping and not self.prms.args["cluster_all_proteins"]:
                        continue
                    self.__update_feature_annotation(feature_id, record_locus.seq_id,
                                                     f"{int(gff_feature.location.start) + 1}:"
                                                     f"{int(gff_feature.location.end)}:{gff_feature.location.strand}",
                                                     "CDS", category, name)
                    feature_annotation_row = self.feature_annotation.loc[feature_id]
                    feature = Feature(feature_type=feature_annotation_row["feature_type"],
                                      feature_id=feature_id, start=int(gff_feature.location.start) + 1,
                                      end=int(gff_feature.location.end), strand=gff_feature.location.strand,
                                      name=feature_annotation_row["name"],
                                      sequence=gff_feature.translate(record_locus_sequence, table=transl_table,
                                                                     cds=False)[:-1],
                                      group=feature_annotation_row["group"],
                                      group_type=feature_annotation_row["group_type"],
                                      category=feature_annotation_row["category"],
                                      vis_prms=dict(fill_colour=feature_annotation_row["fill_colour"],
                                                    stroke_colour=feature_annotation_row["stroke_colour"],
                                                    show_label=feature_annotation_row["show_label"]),
                                      overlapping=overlapping, parameters=self.prms)
                    record_locus.features.append(feature)
                self.loci.append(record_locus)
            except:
                print(f"○ Warning: gff file {gff_file} was not read properly and skipped")
                if self.prms.args["parsing_debug"]:
                    self.prms.args["debug"] = True
                    raise lovis4u.Manager.lovis4uError()
        seq_id_to_order = self.locus_annotation["order"].to_dict()
        loci_ids = [l.seq_id for l in self.loci]
        if len(loci_ids) != len(set(loci_ids)):
            raise lovis4u.Manager.lovis4uError(f"The input gff files have duplicated contig ids.\n\t"
                                               f"You can use `--use-filename-as-id` parameter to use file name "
                                               f"as contig id which can help to fix the problem.")
        self.loci.sort(key=lambda locus: seq_id_to_order[locus.seq_id])
        if self.prms.args["verbose"]:
            print(f"⦿ {len(self.loci)} {'locus was' if len(self.loci) == 1 else 'loci were'} loaded from the gff "
                  f"files folder", file=sys.stdout)
        return None
    except Exception as error:
        raise lovis4u.Manager.lovis4uError("Unable to load loci from gff folder.") from error

load_loci_from_gb(input_folder)

Load loci from the folder with genbank files.

All files with extension other than .gb (not case-sensitive) will be ignored.

Parameters:

  • input_folder (str) –

    folder name with gb files.

Returns:

  • None

    None

Source code in lovis4u/DataProcessing.py
def load_loci_from_gb(self, input_folder: str) -> None:
    """Load loci from the folder with genbank files.

    All files with extension other than .gb (not case-sensitive) will be ignored.

    Arguments:
        input_folder: folder name with gb files.

    Returns:
        None

    """
    if not os.path.exists(input_folder):
        raise lovis4u.Manager.lovis4uError(f"Folder {input_folder} does not exist.")
    try:
        gb_files = [f for f in os.listdir(input_folder)]
        if not gb_files:
            raise lovis4u.Manager.lovis4uError(f"Folder {input_folder} does not contain files.")
        if self.prms.args["verbose"]:
            print(f"○ Reading gb file{'s' if len(gb_files) > 1 else ''}...", file=sys.stdout)
        for gb_file in gb_files:
            try:
                gb_file_path = os.path.join(input_folder, gb_file)
                gb_records = list(Bio.SeqIO.parse(gb_file_path, "genbank"))
                if len(gb_records) != 1:
                    print(f"○ Warning: gb file {gb_file} contains information for more than 1 "
                          f"sequence. File will be skipped.")
                    continue
                gb_record = gb_records[0]
                record_locus_sequence = gb_record.seq
                if self.prms.args["genbank_description_source"] == "description":
                    record_description = gb_record.description
                elif "annotations:" in self.prms.args["genbank_description_source"]:
                    feature_description_key = self.prms.args["genbank_description_source"].split(":")[1]
                    record_description = gb_record.annotations[feature_description_key]
                else:
                    record_description = ""
                if self.prms.args["use_filename_as_contig_id"]:
                    gb_record.id = os.path.splitext(os.path.basename(gb_file))[0]
                self.__update_locus_annotation(gb_record.id, record_description, len(record_locus_sequence))
                locus_annotation_row = self.locus_annotation.loc[gb_record.id]
                coordinates = [dict(zip(["start", "end", "strand"], map(int, c.split(":")))) for c in
                               locus_annotation_row["coordinates"].split(",")]
                record_locus = Locus(seq_id=gb_record.id, coordinates=coordinates,
                                     description=locus_annotation_row["description"],
                                     circular=locus_annotation_row["circular"],
                                     length=locus_annotation_row["length"], parameters=self.prms, features=[],
                                     order=locus_annotation_row["order"])

                gb_CDSs = [i for i in gb_record.features if i.type == "CDS"]
                first_CDS_record = gb_CDSs[0]
                id_source = self.prms.args["genbank_id_source"]
                if self.prms.args["genbank_id_source"] not in first_CDS_record.qualifiers:
                    for alternative_id_source in self.prms.args["genbank_id_alternative_source"]:
                        if alternative_id_source in first_CDS_record.qualifiers:
                            id_source = alternative_id_source
                            if self.prms.args["verbose"]:
                                print(f"○ Warning: there is no <{self.prms.args['genbank_id_source']}> attribute "
                                      f"for CDS records in {gb_file}. Alternative <{id_source}> was used instead.",
                                      file=sys.stdout)
                            break
                    if id_source == self.prms.args["genbank_id_source"]:
                        print(f"There is no <{self.prms.args['genbank_id_source']}> "
                              f"attribute for CDS record found in {gb_file}. We tried to"
                              f" find any from the alternative list: "
                              f"{','.join(self.prms.args['genbank_id_alternative_source'])}"
                              f", but they also weren't found.")  # add about cmd parameter
                features_ids = [i.qualifiers[id_source][0] for i in gb_CDSs if id_source in i.qualifiers]
                if len(features_ids) != len(set(features_ids)):
                    print(f"GB file {gb_record} contains duplicated feature ids while"
                                                       f" only unique are allowed.")
                for gb_feature in gb_CDSs:
                    if id_source not in gb_feature.qualifiers:
                        print(f"    ○ Warning: genbank CDS feature for {gb_file} located at {gb_feature.location} "
                              f"was skipped\n    since it does not have id qualifier {id_source} found for other "
                              f"features.\n    it could be a case of zero length ORF. ", file=sys.stdout)
                        continue
                    feature_id = gb_feature.qualifiers[id_source][0].replace("|", "_")
                    transl_table = self.prms.args["default_transl_table"]
                    if "transl_table" in gb_feature.qualifiers.keys():
                        transl_table = int(gb_feature.qualifiers["transl_table"][0])
                    name = ""
                    if self.prms.args["genbank_CDS_name_source"] in gb_feature.qualifiers:
                        name = gb_feature.qualifiers[self.prms.args["genbank_CDS_name_source"]][0]
                    category = ""
                    if self.prms.args["genbank_CDS_category_source"] in gb_feature.qualifiers:
                        category = ",".join(gb_feature.qualifiers[self.prms.args["genbank_CDS_category_source"]])

                    for coordinate in record_locus.coordinates:
                        overlapping = False
                        start, end = coordinate["start"], coordinate["end"]
                        if start <= gb_feature.location.start + 1 <= end or start <= gb_feature.location.end <= end:
                            overlapping = True
                            break
                    if not overlapping and not self.prms.args["cluster_all_proteins"]:
                        continue
                    self.__update_feature_annotation(feature_id, record_locus.seq_id,
                                                     f"{int(gb_feature.location.start) + 1}:"
                                                     f"{int(gb_feature.location.end)}:"
                                                     f"{gb_feature.location.strand}", "CDS", category, name)
                    feature_annotation_row = self.feature_annotation.loc[feature_id]
                    feature = Feature(feature_type=feature_annotation_row["feature_type"],
                                      feature_id=feature_id, start=int(gb_feature.location.start) + 1,
                                      end=int(gb_feature.location.end),
                                      strand=gb_feature.location.strand,
                                      name=feature_annotation_row["name"],
                                      sequence=gb_feature.translate(record_locus_sequence,
                                                                    table=transl_table,
                                                                    cds=False)[:-1],
                                      group=feature_annotation_row["group"],
                                      group_type=feature_annotation_row["group_type"],
                                      category=feature_annotation_row["category"],
                                      vis_prms=dict(fill_colour=feature_annotation_row["fill_colour"],
                                                    stroke_colour=feature_annotation_row["stroke_colour"],
                                                    show_label=feature_annotation_row["show_label"]),
                                      overlapping = overlapping,
                                      parameters=self.prms)

                    record_locus.features.append(feature)
                self.loci.append(record_locus)
            except:
                print(f"○ Warning: gb file {gb_file} was not read properly and skipped")
                if self.prms.args["parsing_debug"]:
                    self.prms.args["debug"] = True
                    raise lovis4u.Manager.lovis4uError()
        seq_id_to_order = self.locus_annotation["order"].to_dict()
        loci_ids = [l.seq_id for l in self.loci]
        if len(loci_ids) != len(set(loci_ids)):
            raise lovis4u.Manager.lovis4uError(f"The input gb files have duplicated contig ids. "
                                               f"You can use `--use-filename-as-id` parameter to use file name "
                                               f"as contig id which can help to fix the problem.")
        self.loci.sort(key=lambda locus: seq_id_to_order[locus.seq_id])
        if self.prms.args["verbose"]:
            print(f"⦿ {len(self.loci)} {'locus was' if len(self.loci) == 1 else 'loci were'} loaded from the "
                  f"genbank files folder", file=sys.stdout)
        return None
    except Exception as error:
        raise lovis4u.Manager.lovis4uError("Unable to load loci from gb folder.") from error

load_locus_annotation_file(file_path)

Load loci annotation file.

Parameters:

  • file_path (str) –

    File path for a loci annotation file to be loaded.

Returns:

  • None

    None

Source code in lovis4u/DataProcessing.py
def load_locus_annotation_file(self, file_path: str) -> None:
    """Load loci annotation file.

    Arguments:
        file_path (str): File path for a loci annotation file to be loaded.

    Returns:
        None

    """
    annotation_columns = ["sequence_id", "length", "coordinates", "circular", "description", "order", "group"]
    self.locus_annotation = self.__load_annotation_file(file_path, annotation_columns, "sequence_id")
    return None

mmseqs_cluster()

Cluster all proteins using mmseqs in order to define groups of homologues.

Returns:

  • DataFrame

    pd.DataFrame: parsed mmseqs table (pandas dataframe) with columns: cluster, protein_id; where cluster is defined by representative sequence id within a corresponding cluster.

Source code in lovis4u/DataProcessing.py
def mmseqs_cluster(self) -> pd.DataFrame:
    """Cluster all proteins using mmseqs in order to define groups of homologues.

    Returns:
        pd.DataFrame: parsed mmseqs table (pandas dataframe) with columns: cluster, protein_id; where cluster is
            defined by representative sequence id within a corresponding cluster.

    """
    if self.prms.args["verbose"]:
        print(f"○ Running mmseqs for protein clustering...", file=sys.stdout)
    try:
        feature_records = [feature.record for locus in self.loci for feature in locus.features]
        temp_input = tempfile.NamedTemporaryFile()
        Bio.SeqIO.write(feature_records, temp_input.name, "fasta")
        if not os.path.exists(self.prms.args["output_dir"]):
            os.mkdir(self.prms.args["output_dir"])
        mmseqs_output_folder = os.path.join(self.prms.args["output_dir"], "mmseqs")
        if os.path.exists(mmseqs_output_folder):
            shutil.rmtree(mmseqs_output_folder)
        os.mkdir(mmseqs_output_folder)
        Bio.SeqIO.write(feature_records, os.path.join(mmseqs_output_folder, "input_proteins.fa"), "fasta")
        mmseqs_output_folder_db = os.path.join(mmseqs_output_folder, "DB")
        os.mkdir(mmseqs_output_folder_db)
        mmseqs_stdout = open(os.path.join(mmseqs_output_folder, "mmseqs_stdout.txt"), "w")
        mmseqs_stderr = open(os.path.join(mmseqs_output_folder, "mmseqs_stderr.txt"), "w")
        subprocess.run([self.prms.args["mmseqs_binary"], "createdb", temp_input.name,
                        os.path.join(mmseqs_output_folder_db, "sequencesDB")], stdout=mmseqs_stdout,
                       stderr=mmseqs_stderr)
        subprocess.run([self.prms.args["mmseqs_binary"], "cluster",
                        os.path.join(mmseqs_output_folder_db, "sequencesDB"),
                        os.path.join(mmseqs_output_folder_db, "clusterDB"),
                        os.path.join(mmseqs_output_folder_db, "tmp"),
                        "--cluster-mode", str(self.prms.args["mmseqs_cluster_mode"]),
                        "--cov-mode", str(self.prms.args["mmseqs_cov_mode"]),
                        "--min-seq-id", str(self.prms.args["mmseqs_min_seq_id"]),
                        "-c", str(self.prms.args["mmseqs_c"]),
                        "-s", str(self.prms.args["mmseqs_s"])], stdout=mmseqs_stdout, stderr=mmseqs_stderr)
        subprocess.run([self.prms.args["mmseqs_binary"], "createtsv",
                        os.path.join(mmseqs_output_folder_db, "sequencesDB"),
                        os.path.join(mmseqs_output_folder_db, "sequencesDB"),
                        os.path.join(mmseqs_output_folder_db, "clusterDB"),
                        os.path.join(mmseqs_output_folder, "mmseqs_clustering.tsv")],
                       stdout=mmseqs_stdout, stderr=mmseqs_stderr)
        mmseqs_clustering_results = pd.read_table(os.path.join(mmseqs_output_folder, "mmseqs_clustering.tsv"),
                                                  sep="\t", header=None, names=["cluster", "protein_id"])
        mmseqs_clustering_results = mmseqs_clustering_results.set_index("protein_id")

        num_of_unique_clusters = len(set(mmseqs_clustering_results["cluster"].to_list()))
        num_of_proteins = len(mmseqs_clustering_results.index)
        if self.prms.args["verbose"]:
            print(f"⦿ {num_of_unique_clusters} clusters for {num_of_proteins} proteins were found with mmseqs\n"
                  f"\tmmseqs clustering results were saved to "
                  f"{os.path.join(mmseqs_output_folder, 'mmseqs_clustering.tsv')}", file=sys.stdout)
        return mmseqs_clustering_results
    except Exception as error:
        raise lovis4u.Manager.lovis4uError("Unable to run mmseqs clustering. In case you use a linux machine"
                                           ",  have you run a post-install 'lovis4u --linux` command to switch to"
                                           " the linux mmseqs binary?") from error

remove_non_overlapping_features()

Removes features that are not overlapping with visualisation window.

Returns:

  • None

    None

Source code in lovis4u/DataProcessing.py
def remove_non_overlapping_features(self) -> None:
    """Removes features that are not overlapping with visualisation window.

    Returns:
        None
    """
    try:
        ids_of_non_overlapping_objects = []
        for locus in self.loci:
            ids_of_non_overlapping_objects += [obj.feature_id for obj in locus.features if not obj.overlapping]
            filtered_objects = [obj for obj in locus.features if obj.overlapping]
            locus.features = filtered_objects
        if ids_of_non_overlapping_objects:
            print("○ Warning message: LoVis4u clusters all proteins by default to define their classes"
                  "\n\t('variable' or 'conserved'), including those outside the visualisation window."
                  "\n\tTo cluster only proteins within the visualised area, use the -cl-owp parameter.")
        self.feature_annotation = self.feature_annotation.drop(ids_of_non_overlapping_objects)
    except Exception as error:
        raise lovis4u.Manager.lovis4uError("Unable to clean non overlapping features.") from error

reorient_loci(ilund4u_mode=False)

Auto re-orient loci (reset strands) of loci if they are not matched.

Function tries to maximise co-orientation of homologous features.

Returns:

  • None

    None

Source code in lovis4u/DataProcessing.py
def reorient_loci(self, ilund4u_mode: bool = False) -> None:
    """Auto re-orient loci (reset strands) of loci if they are not matched.

    Function tries to maximise co-orientation of homologous features.

    Returns:
        None

    """
    try:
        count_of_changed_strands = 0
        loci = [locus for locus in self.loci]
        for locus_index in range(1, len(loci)):
            p_locus = loci[locus_index - 1]
            c_locus = loci[locus_index]
            p_locus_strands = list(set([c["strand"] for c in p_locus.coordinates]))
            c_locus_strands = list(set([c["strand"] for c in c_locus.coordinates]))
            if len(p_locus_strands) == 1 and len(c_locus_strands) == 1:
                if not ilund4u_mode:
                    pr_locus_features_groups = set([f.group for f in p_locus.features])
                    c_locus_features_groups = set([f.group for f in c_locus.features])
                else:
                    pr_locus_features_groups = set(
                        [f.group for f in p_locus.features if f.group_type == "conserved"])
                    c_locus_features_groups = set(
                        [f.group for f in c_locus.features if f.group_type == "conserved"])
                overlapped_f_groups = pr_locus_features_groups & c_locus_features_groups
                prl_strand, cl_strand = p_locus_strands[0], c_locus_strands[0]
                pr_locus_features_strands = {f.group: f.strand * prl_strand for f in p_locus.features if
                                             f.group in overlapped_f_groups}
                c_locus_features_strands = {f.group: f.strand * cl_strand for f in c_locus.features if
                                            f.group in overlapped_f_groups}
                codirection_score = 0

                for ovg in overlapped_f_groups:
                    codirection_score += pr_locus_features_strands[ovg] * c_locus_features_strands[ovg]
                if codirection_score < 0:
                    count_of_changed_strands += 1
                    annot_coordinates = []
                    for cc in loci[locus_index].coordinates:
                        cc["strand"] *= -1
                        annot_coordinates.append(f"{cc['start']}:{cc['end']}:{cc['strand']}")
                    loci[locus_index].coordinates = loci[locus_index].coordinates[::-1]
                    annot_coordinates = annot_coordinates[::-1]
                    self.locus_annotation.loc[c_locus.seq_id, "coordinates"] = ",".join(annot_coordinates)
            else:
                if self.prms.args["verbose"]:
                    print("○ Warning: loci reorientation cannot be applied for loci that have both strands in"
                          " pre-defined coordinates for visualisation")
        if self.prms.args["verbose"]:
            if count_of_changed_strands == 0:
                print(f"⦿ Orientation was not changed for any locus", file=sys.stdout)
            elif count_of_changed_strands == 1:
                print(f"⦿ Orientation was changed for 1 locus", file=sys.stdout)
            elif count_of_changed_strands > 1:
                print(f"⦿ Orientation was changed for {count_of_changed_strands} loci", file=sys.stdout)

        return None
    except Exception as error:
        raise lovis4u.Manager.lovis4uError("Unable to define variable feature groups.") from error

save_feature_annotation_table()

Save feature annotation table to the output folder.

Output file name is feature_annotation_table.tsv

Returns:

  • None

    None

Source code in lovis4u/DataProcessing.py
def save_feature_annotation_table(self) -> None:
    """Save feature annotation table to the output folder.

    Output file name is feature_annotation_table.tsv

    Returns:
        None

    """
    try:
        if not os.path.exists(self.prms.args["output_dir"]):
            os.mkdir(self.prms.args["output_dir"])
        file_path = os.path.join(self.prms.args["output_dir"], "feature_annotation_table.tsv")
        self.feature_annotation.to_csv(file_path, sep="\t", index_label="feature_id")
        if self.prms.args["verbose"]:
            print(f"⦿ Feature annotation table was saved to {file_path}", file=sys.stdout)
        return None
    except Exception as error:
        raise lovis4u.Manager.lovis4uError("Unable to save feature annotation table.") from error

save_locus_annotation_table()

Save loci annotation table to the output folder.

Output file name is locus_annotation_table.tsv

Returns:

  • None

    None

Source code in lovis4u/DataProcessing.py
def save_locus_annotation_table(self) -> None:
    """Save loci annotation table to the output folder.

    Output file name is locus_annotation_table.tsv

    Returns:
        None

    """
    try:
        if not os.path.exists(self.prms.args["output_dir"]):
            os.mkdir(self.prms.args["output_dir"])
        file_path = os.path.join(self.prms.args["output_dir"], "locus_annotation_table.tsv")
        self.locus_annotation.to_csv(file_path, sep="\t", index_label="sequence_id")
        if self.prms.args["verbose"]:
            print(f"⦿ Loci annotation table was saved to {file_path}", file=sys.stdout)
        return None
    except Exception as error:
        raise lovis4u.Manager.lovis4uError("Unable to save loci annotation table.") from error

set_category_colours(use_table=True)

Define colours for each category.

Parameters:

  • use_table (bool, default: True ) –

    Bool value whether table with predefined colours should be used or not.

Returns:

  • None

    None

Source code in lovis4u/DataProcessing.py
def set_category_colours(self, use_table: bool = True) -> None:
    """Define colours for each category.

    Arguments:
        use_table (bool): Bool value whether table with predefined colours should be used or not.

    Returns:
        None

    """
    try:
        colours_dict = dict()
        if use_table:
            colours_dict.update(
                pd.read_table(self.prms.args["category_colours"]).set_index("category")["colour"].to_dict())

        feature_categories = list(set([feature.category for locus in self.loci for feature in locus.features
                                       if feature.category and feature.category]))
        if not feature_categories:
            if self.prms.args["verbose"]:
                print("○ Warning: there are no feature categories to set colours", file=sys.stdout)
        colours_dict = {cat: col for cat, col in colours_dict.items() if cat in feature_categories}

        feature_categories = [ff for ff in feature_categories if ff not in colours_dict.keys()]
        number_of_unique_feature_functions = len(feature_categories)
        colours_rgb = seaborn.color_palette(self.prms.args["category_colour_seaborn_palette"],
                                            number_of_unique_feature_functions,
                                            desat=self.prms.args["category_colour_seaborn_desat"])
        random.shuffle(colours_rgb)
        colours = list(map(lambda x: matplotlib.colors.rgb2hex(x), colours_rgb))
        colours_dict.update({g: c for g, c in zip(list(feature_categories), colours)})
        for locus in self.loci:
            locus.category_colours = colours_dict
        return None
    except Exception as error:
        raise lovis4u.Manager.lovis4uError("Unable to set category colours.") from error

set_feature_colours_based_on_groups()

Define features fill colour based on corresponding feature group and group types.

Returns:

  • None

    None

Source code in lovis4u/DataProcessing.py
def set_feature_colours_based_on_groups(self) -> None:
    """Define features fill colour based on corresponding feature group and group types.

    Returns:
        None

    """
    try:
        feature_groups = set([feature.group for locus in self.loci for feature in locus.features if feature.group])
        if self.prms.args["feature_group_types_to_set_colour"] and \
                "all" not in self.prms.args["feature_group_types_to_set_colour"]:
            feature_groups = set([feature.group for locus in self.loci for feature in locus.features
                                  if feature.group and feature.group_type in
                                  self.prms.args["feature_group_types_to_set_colour"]])
        number_of_unique_feature_groups = len(feature_groups)
        if self.prms.args["groups_fill_colour_palette_lib"] == "seaborn":
            colours_rgb = seaborn.color_palette(self.prms.args["groups_fill_colour_seaborn_palette"],
                                                number_of_unique_feature_groups,
                                                desat=self.prms.args["groups_fill_colour_seaborn_desat"])
            random.shuffle(colours_rgb)
        elif self.prms.args["groups_fill_colour_palette_lib"] == "distinctipy":
            colours_rgb = distinctipy.get_colors(number_of_unique_feature_groups,
                                                 exclude_colours=[(1, 1, 1), (0, 0, 0)],
                                                 pastel_factor=self.prms.args["groups_fill_colours_pastel_factor"])
        colours = list(map(lambda x: matplotlib.colors.rgb2hex(x), colours_rgb))
        colours_dict = {g: c for g, c in zip(list(feature_groups), colours)}
        for locus in self.loci:
            for feature in locus.features:
                if feature.group in feature_groups:
                    if self.prms.args["keep_predefined_colours"] and feature.vis_prms["fill_colour"] != "default":
                        continue
                    feature.vis_prms["fill_colour"] = colours_dict[feature.group]
                    self.feature_annotation.loc[feature.feature_id, "fill_colour"] = feature.vis_prms["fill_colour"]
        return None
    except Exception as error:
        raise lovis4u.Manager.lovis4uError("Unable to set feature colours based on groups.") from error

Locus

A Locus object represents a particular locus that will be one of the sequence tracks on final figure.

Attributes:

  • seq_id (str) –

    Sequence identifier. Can be used to label locus.

  • coordinates (list) –

    List of regions to be shown. Each region format: dict with keys: start, end, strand and corresponding 1-based start, end coordinates and strand (1: plus strand, -1: minus strand).

  • size (int) –

    total length of regions to be plotted.

  • description (str) –

    Sequence description that can be used to label locus.

  • length (int) –

    full length of the locus independent on region that should be plotted.

  • circular (bool) –

    Bool value whether locus is circular or not. It defines whether you have gap or not passing 1 value on the final figure.

  • features (list) –

    list of Feature objects that overlapped with coordinates.

  • order (int) –

    index on ordered list of loci visualisation. Can be pre-defined or found based on loci hierarchical clustering with cluster_sequences category.

  • group (int | str) –

    locus group that defines set of closely-related loci.

  • category_colours (dict) –

    colours for locus' features categories.

  • prms (Parameters) –

    Parameters' class object that holds config and cmd arguments.

Source code in lovis4u/DataProcessing.py
class Locus:
    """A Locus object represents a particular locus that will be one of the sequence tracks on final figure.

    Attributes:
        seq_id (str): Sequence identifier. Can be used to label locus.
        coordinates (list): List of regions to be shown. Each region format: dict with keys: start, end, strand and
            corresponding 1-based start, end coordinates and strand (1: plus strand, -1: minus strand).
        size (int): total length of regions to be plotted.
        description (str): Sequence description that can be used to label locus.
        length (int): full length of the locus independent on region that should be plotted.
        circular (bool): Bool value whether locus is circular or not. It defines whether you have gap or not passing
            1 value on the final figure.
        features (list): list of Feature objects that overlapped with coordinates.
        order (int): index on ordered list of loci visualisation. Can be pre-defined or found based on loci
            hierarchical clustering with cluster_sequences category.
        group (int | str): locus group that defines set of closely-related loci.
        category_colours (dict): colours for locus' features categories.
        prms (lovis4u.Manager.Parameters): Parameters' class object that holds config and cmd arguments.

    """

    def __init__(self, seq_id: str, coordinates: list, description: str, length: int, circular: bool,
                 features: list, order: int, parameters: lovis4u.Manager.Parameters, group: typing.Union[int, str] = 1):
        """Create a Locus object.

        Arguments:
            seq_id (str): Sequence identifier. Can be used to label locus.
            coordinates (list): List of regions to be shown. Each region format: dict with keys: start, end, strand and
                corresponding 1-based start, end coordinates and strand (1: plus strand, -1: minus strand).
            description (str): Sequence description that can be used to label locus.
            length (int): full length of the locus independent on region that should be plotted.
            circular (bool): Bool value whether locus is circular or not. It defines whether you have gap or not passing
                1 value on the final figure.
            features (list): list of Feature objects that overlapped with coordinates.
            order (int): index on ordered list of loci visualisation. Can be pre-defined or found based on loci
            hierarchical clustering with cluster_sequences category.
            parameters (lovis4u.Manager.Parameters): Parameters' class object that holds config and cmd arguments.
            group (int | str): locus group that defines set of closely-related loci [1].

        """
        self.seq_id = seq_id
        self.coordinates = coordinates
        self.size = 0
        taken_coordinates = []
        for coordinate in coordinates:
            self.size += abs(coordinate["end"] - coordinate["start"] + 1)
            taken_coordinates += list(range(coordinate["start"], coordinate["end"] + 1))
        if len(taken_coordinates) != len(set(taken_coordinates)):
            raise lovis4u.Manager.lovis4uError(f"Specified coordinates seems to be overlapped"
                                               f" or not in 0-based format.")
        for coordinate in coordinates:
            if coordinate["start"] < 1:
                raise lovis4u.Manager.lovis4uError(f"Coordinates for {seq_id}: {coordinate} is in 0-based format"
                                                   f" while input should be in 1-based.")
            if coordinate["end"] > length:
                raise lovis4u.Manager.lovis4uError(f"Coordinates for {seq_id}: {coordinate} is out of sequence length"
                                                   f" ({length} nt).")

        self.description = description
        self.length = length
        self.circular = circular
        self.features = features
        self.order = order
        self.group = group
        self.category_colours = dict()
        self.prms = parameters

__init__(seq_id, coordinates, description, length, circular, features, order, parameters, group=1)

Create a Locus object.

Parameters:

  • seq_id (str) –

    Sequence identifier. Can be used to label locus.

  • coordinates (list) –

    List of regions to be shown. Each region format: dict with keys: start, end, strand and corresponding 1-based start, end coordinates and strand (1: plus strand, -1: minus strand).

  • description (str) –

    Sequence description that can be used to label locus.

  • length (int) –

    full length of the locus independent on region that should be plotted.

  • circular (bool) –

    Bool value whether locus is circular or not. It defines whether you have gap or not passing 1 value on the final figure.

  • features (list) –

    list of Feature objects that overlapped with coordinates.

  • order (int) –

    index on ordered list of loci visualisation. Can be pre-defined or found based on loci

  • parameters (Parameters) –

    Parameters' class object that holds config and cmd arguments.

  • group (int | str, default: 1 ) –

    locus group that defines set of closely-related loci [1].

Source code in lovis4u/DataProcessing.py
def __init__(self, seq_id: str, coordinates: list, description: str, length: int, circular: bool,
             features: list, order: int, parameters: lovis4u.Manager.Parameters, group: typing.Union[int, str] = 1):
    """Create a Locus object.

    Arguments:
        seq_id (str): Sequence identifier. Can be used to label locus.
        coordinates (list): List of regions to be shown. Each region format: dict with keys: start, end, strand and
            corresponding 1-based start, end coordinates and strand (1: plus strand, -1: minus strand).
        description (str): Sequence description that can be used to label locus.
        length (int): full length of the locus independent on region that should be plotted.
        circular (bool): Bool value whether locus is circular or not. It defines whether you have gap or not passing
            1 value on the final figure.
        features (list): list of Feature objects that overlapped with coordinates.
        order (int): index on ordered list of loci visualisation. Can be pre-defined or found based on loci
        hierarchical clustering with cluster_sequences category.
        parameters (lovis4u.Manager.Parameters): Parameters' class object that holds config and cmd arguments.
        group (int | str): locus group that defines set of closely-related loci [1].

    """
    self.seq_id = seq_id
    self.coordinates = coordinates
    self.size = 0
    taken_coordinates = []
    for coordinate in coordinates:
        self.size += abs(coordinate["end"] - coordinate["start"] + 1)
        taken_coordinates += list(range(coordinate["start"], coordinate["end"] + 1))
    if len(taken_coordinates) != len(set(taken_coordinates)):
        raise lovis4u.Manager.lovis4uError(f"Specified coordinates seems to be overlapped"
                                           f" or not in 0-based format.")
    for coordinate in coordinates:
        if coordinate["start"] < 1:
            raise lovis4u.Manager.lovis4uError(f"Coordinates for {seq_id}: {coordinate} is in 0-based format"
                                               f" while input should be in 1-based.")
        if coordinate["end"] > length:
            raise lovis4u.Manager.lovis4uError(f"Coordinates for {seq_id}: {coordinate} is out of sequence length"
                                               f" ({length} nt).")

    self.description = description
    self.length = length
    self.circular = circular
    self.features = features
    self.order = order
    self.group = group
    self.category_colours = dict()
    self.prms = parameters

This module provides visualisation of loci annotation.

ColorLegendVis

Bases: Track

ColorLegend track object that handles visualisation of legend to feature's category colours.

Attributes:

  • layout (dict) –

    Layout built by CanvasManager's define_layout() method.

  • track_data (dict) –

    a dictionary with prepared track specific data.

  • prms (Parameters) –

    Parameters' class object.

Source code in lovis4u/Drawing.py
class ColorLegendVis(Track):
    """ColorLegend track object that handles visualisation of legend to feature's category colours.

    Attributes:
        layout (dict): Layout built by CanvasManager's define_layout() method.
        track_data (dict): a dictionary with prepared track specific data.
        prms (lovis4u.Manager.Parameters): Parameters' class object.

    """

    def __init__(self, layout: dict, track_data: dict, parameters):
        """Create a ColorLegend object.

        Arguments:
            layout (dict): Layout built by CanvasManager's define_layout() method.
            track_data (dict): a dictionary with prepared track specific data.
            parameters (lovis4u.Manager.Parameters): Parameters' class object.

        """
        super().__init__(layout, track_data, parameters)
        self.track_height = None

    def draw(self, canvas: reportlab.pdfgen.canvas.Canvas) -> None:
        """Draw a ColorLegend track.

        Arguments:
            canvas (reportlab.pdfgen.canvas.Canvas): a canvas object.

        Returns:
            None

        """
        try:
            y_upper = self.layout["current_y_coordinate"]
            canvas.setLineWidth(self.prms.args["scale_line_width"])
            canvas.setFont(self.prms.args["colour_legend_font_face"],
                           self.track_data["colour_legend_label_size"])
            for label_dict in self.track_data["labels"]:
                yl = y_upper + label_dict["relative_y"]
                yt = y_upper + label_dict["relative_y_text"]
                canvas.setFillColorRGB(*matplotlib.colors.hex2color(label_dict["colour"]),
                                       self.prms.args["category_annotation_alpha"])
                canvas.rect(label_dict["label_x"], yl, label_dict["label_width"],
                            self.track_data["line_width"], fill=1, stroke=0)
                canvas.setFillColorRGB(*lovis4u.Methods.get_colour_rgba("colour_legend_label_colour", self.prms))
                canvas.drawString(label_dict["label_x"], yt, label_dict["label"])
            return None
        except Exception as error:
            raise lovis4u.Manager.lovis4uError("Unable to draw a colour legend track.") from error

__init__(layout, track_data, parameters)

Create a ColorLegend object.

Parameters:

  • layout (dict) –

    Layout built by CanvasManager's define_layout() method.

  • track_data (dict) –

    a dictionary with prepared track specific data.

  • parameters (Parameters) –

    Parameters' class object.

Source code in lovis4u/Drawing.py
def __init__(self, layout: dict, track_data: dict, parameters):
    """Create a ColorLegend object.

    Arguments:
        layout (dict): Layout built by CanvasManager's define_layout() method.
        track_data (dict): a dictionary with prepared track specific data.
        parameters (lovis4u.Manager.Parameters): Parameters' class object.

    """
    super().__init__(layout, track_data, parameters)
    self.track_height = None

draw(canvas)

Draw a ColorLegend track.

Parameters:

  • canvas (Canvas) –

    a canvas object.

Returns:

  • None

    None

Source code in lovis4u/Drawing.py
def draw(self, canvas: reportlab.pdfgen.canvas.Canvas) -> None:
    """Draw a ColorLegend track.

    Arguments:
        canvas (reportlab.pdfgen.canvas.Canvas): a canvas object.

    Returns:
        None

    """
    try:
        y_upper = self.layout["current_y_coordinate"]
        canvas.setLineWidth(self.prms.args["scale_line_width"])
        canvas.setFont(self.prms.args["colour_legend_font_face"],
                       self.track_data["colour_legend_label_size"])
        for label_dict in self.track_data["labels"]:
            yl = y_upper + label_dict["relative_y"]
            yt = y_upper + label_dict["relative_y_text"]
            canvas.setFillColorRGB(*matplotlib.colors.hex2color(label_dict["colour"]),
                                   self.prms.args["category_annotation_alpha"])
            canvas.rect(label_dict["label_x"], yl, label_dict["label_width"],
                        self.track_data["line_width"], fill=1, stroke=0)
            canvas.setFillColorRGB(*lovis4u.Methods.get_colour_rgba("colour_legend_label_colour", self.prms))
            canvas.drawString(label_dict["label_x"], yt, label_dict["label"])
        return None
    except Exception as error:
        raise lovis4u.Manager.lovis4uError("Unable to draw a colour legend track.") from error

CrossTrack

Parent class for Cross-Tracks visualisation.

Attributes:

  • layout (dict) –

    Layout built by CanvasManager's define_layout() method.

  • tracks (dict) –

    List with track objects participated in CrossTrack.

  • prms (Parameters) –

    Parameters' class object.

Source code in lovis4u/Drawing.py
class CrossTrack:
    """Parent class for Cross-Tracks visualisation.

    Attributes:
        layout (dict): Layout built by CanvasManager's define_layout() method.
        tracks (dict): List with track objects participated in CrossTrack.
        prms (lovis4u.Manager.Parameters): Parameters' class object.

    """

    def __init__(self, layout, tracks, parameters):
        """Parent's constructor for creating a CrossTrack object.

        Attributes:
            layout (dict): Layout built by CanvasManager's define_layout() method.
            tracks (dict): List with track objects participated in CrossTrack.
            parameters (lovis4u.Manager.Parameters): Parameters' class object.

        """
        self.layout = layout
        self.tracks = tracks
        self.prms = parameters

    def draw(self, canvas: reportlab.pdfgen.canvas.Canvas):
        """Empy parent's method for cross track drawing.

        Arguments:
            canvas (reportlab.pdfgen.canvas.Canvas): a canvas object.

        Returns:
            None

        """
        pass

__init__(layout, tracks, parameters)

Parent's constructor for creating a CrossTrack object.

Attributes:

  • layout (dict) –

    Layout built by CanvasManager's define_layout() method.

  • tracks (dict) –

    List with track objects participated in CrossTrack.

  • parameters (Parameters) –

    Parameters' class object.

Source code in lovis4u/Drawing.py
def __init__(self, layout, tracks, parameters):
    """Parent's constructor for creating a CrossTrack object.

    Attributes:
        layout (dict): Layout built by CanvasManager's define_layout() method.
        tracks (dict): List with track objects participated in CrossTrack.
        parameters (lovis4u.Manager.Parameters): Parameters' class object.

    """
    self.layout = layout
    self.tracks = tracks
    self.prms = parameters

draw(canvas)

Empy parent's method for cross track drawing.

Parameters:

  • canvas (Canvas) –

    a canvas object.

Returns:

  • None

Source code in lovis4u/Drawing.py
def draw(self, canvas: reportlab.pdfgen.canvas.Canvas):
    """Empy parent's method for cross track drawing.

    Arguments:
        canvas (reportlab.pdfgen.canvas.Canvas): a canvas object.

    Returns:
        None

    """
    pass

HomologyTrack

Bases: CrossTrack

Track that handle visualisation of homology lines between homologous features on neighbours' loci.

Attributes:

  • layout (dict) –

    Layout built by CanvasManager's define_layout() method.

  • tracks (dict) –

    List with track objects participated in CrossTrack.

  • prms (Parameters) –

    Parameters' class object.

Source code in lovis4u/Drawing.py
class HomologyTrack(CrossTrack):
    """Track that handle visualisation of homology lines between homologous features on neighbours' loci.

    Attributes:
        layout (dict): Layout built by CanvasManager's define_layout() method.
        tracks (dict): List with track objects participated in CrossTrack.
        prms (lovis4u.Manager.Parameters): Parameters' class object.

    """

    def __init__(self, layout, tracks, parameters):
        """Create a HomologyTrack.

        Attributes:
            layout (dict): Layout built by CanvasManager's define_layout() method.
            tracks (dict): List with track objects participated in CrossTrack.
            parameters (lovis4u.Manager.Parameters): Parameters' class object.

        """
        super().__init__(layout, tracks, parameters)

    def draw(self, canvas: reportlab.pdfgen.canvas.Canvas) -> None:
        """Draw HomologyTrack on canvas.

        Arguments:
            canvas (reportlab.pdfgen.canvas.Canvas): a canvas object.

        Returns:
            None

        """
        try:
            for track in self.tracks:
                track.track_data["y_top"] = self.layout["figure_height"] - track.layout["inverse_y_coordinate"]
                track.track_data["feature_upper"] = track.track_data["y_top"] - \
                                                    (track.track_data["n_label_rows"] * track.track_data[
                                                        "f_label_height"] *
                                                     (1 + track.prms.args["feature_label_gap"]))
            num_of_loci_tracks = len(self.tracks)
            for ti in range(num_of_loci_tracks - 1):
                current_track = self.tracks[ti]
                current_track_features = current_track.track_data["features"]
                next_track = self.tracks[ti + 1]
                next_track_features = next_track.track_data["features"]
                for ctf in current_track_features:
                    ctf_group = ctf["group"]
                    next_track_same_group_features = [i for i in next_track_features if i["group"] == ctf_group]
                    for ntf in next_track_same_group_features:
                        cty_u = current_track.track_data["feature_upper"]
                        cty_c = current_track.track_data["feature_upper"] - 0.5 * self.prms.args["feature_height"] * mm
                        cty_b = current_track.track_data["feature_upper"] - self.prms.args["feature_height"] * mm
                        nty_u = next_track.track_data["feature_upper"]
                        nty_c = next_track.track_data["feature_upper"] - 0.5 * self.prms.args["feature_height"] * mm
                        nty_b = next_track.track_data["feature_upper"] - self.prms.args["feature_height"] * mm
                        ct_arrow_len = min(
                            self.prms.args["feature_height"] * mm * self.prms.args["feature_arrow_length"],
                            (ctf["coordinates"]["end"] - ctf["coordinates"]["start"]))
                        nt_arrow_len = min(
                            self.prms.args["feature_height"] * mm * self.prms.args["feature_arrow_length"],
                            (ntf["coordinates"]["end"] - ntf["coordinates"]["start"]))
                        canvas.setLineCap(0)
                        canvas.setLineJoin(1)
                        canvas.setLineWidth(self.prms.args["homology_line_width"])
                        canvas.setStrokeColorRGB(*lovis4u.Methods.get_colour_rgba("homology_stroke_colour", self.prms))
                        canvas.setFillColorRGB(*lovis4u.Methods.get_colour_rgba("homology_fill_colour", self.prms))
                        p = canvas.beginPath()

                        if ctf["coordinates"]["orient"] == 1:
                            cts, cte = ctf["coordinates"]["start"], ctf["coordinates"]["end"]
                            p.moveTo(ctf["coordinates"]["start"], cty_b)
                            if not ctf["coordinates"]["rout"]:
                                p.lineTo(ctf["coordinates"]["end"] - ct_arrow_len, cty_b)
                                p.lineTo(ctf["coordinates"]["end"], cty_c)
                            else:
                                p.lineTo(ctf["coordinates"]["end"], cty_b)
                        elif ctf["coordinates"]["orient"] == -1:
                            cts, cte = ctf["coordinates"]["end"], ctf["coordinates"]["start"]
                            p.moveTo(ctf["coordinates"]["end"], cty_b)
                            if not ctf["coordinates"]["lout"]:
                                p.lineTo(ctf["coordinates"]["start"] + ct_arrow_len, cty_b)
                                p.lineTo(ctf["coordinates"]["start"], cty_c)
                            else:
                                p.lineTo(ctf["coordinates"]["start"], cty_b)
                        if ntf["coordinates"]["orient"] == 1:
                            nts, nte = ntf["coordinates"]["end"], ntf["coordinates"]["start"]
                            if not ntf["coordinates"]["rout"]:
                                p.lineTo(ntf["coordinates"]["end"], nty_c)
                                p.lineTo(ntf["coordinates"]["end"] - nt_arrow_len, nty_u)
                            else:
                                p.lineTo(ntf["coordinates"]["end"], nty_u)
                            p.lineTo(ntf["coordinates"]["start"], nty_u)
                            if nte >= cts:
                                p.lineTo(ntf["coordinates"]["start"], nty_c)
                        elif ntf["coordinates"]["orient"] == -1:
                            nts, nte = ntf["coordinates"]["start"], ntf["coordinates"]["end"]
                            if not ntf["coordinates"]["lout"]:
                                p.lineTo(ntf["coordinates"]["start"], nty_c)
                                p.lineTo(ntf["coordinates"]["start"] + nt_arrow_len, nty_u)
                            else:
                                p.lineTo(ntf["coordinates"]["start"], nty_u)
                            p.lineTo(ntf["coordinates"]["end"], nty_u)
                            if nte <= cts:
                                p.lineTo(ntf["coordinates"]["end"], nty_c)
                        if (nte <= cts and ctf["coordinates"]["orient"] == 1) or (
                                nte >= cts and ctf["coordinates"]["orient"] == -1):
                            p.lineTo(cts, cty_c)
                        p.lineTo(cts, cty_b)
                        canvas.drawPath(p, stroke=1, fill=1)
        except Exception as error:
            raise lovis4u.Manager.lovis4uError("Unable to draw homology line track.") from error

__init__(layout, tracks, parameters)

Create a HomologyTrack.

Attributes:

  • layout (dict) –

    Layout built by CanvasManager's define_layout() method.

  • tracks (dict) –

    List with track objects participated in CrossTrack.

  • parameters (Parameters) –

    Parameters' class object.

Source code in lovis4u/Drawing.py
def __init__(self, layout, tracks, parameters):
    """Create a HomologyTrack.

    Attributes:
        layout (dict): Layout built by CanvasManager's define_layout() method.
        tracks (dict): List with track objects participated in CrossTrack.
        parameters (lovis4u.Manager.Parameters): Parameters' class object.

    """
    super().__init__(layout, tracks, parameters)

draw(canvas)

Draw HomologyTrack on canvas.

Parameters:

  • canvas (Canvas) –

    a canvas object.

Returns:

  • None

    None

Source code in lovis4u/Drawing.py
def draw(self, canvas: reportlab.pdfgen.canvas.Canvas) -> None:
    """Draw HomologyTrack on canvas.

    Arguments:
        canvas (reportlab.pdfgen.canvas.Canvas): a canvas object.

    Returns:
        None

    """
    try:
        for track in self.tracks:
            track.track_data["y_top"] = self.layout["figure_height"] - track.layout["inverse_y_coordinate"]
            track.track_data["feature_upper"] = track.track_data["y_top"] - \
                                                (track.track_data["n_label_rows"] * track.track_data[
                                                    "f_label_height"] *
                                                 (1 + track.prms.args["feature_label_gap"]))
        num_of_loci_tracks = len(self.tracks)
        for ti in range(num_of_loci_tracks - 1):
            current_track = self.tracks[ti]
            current_track_features = current_track.track_data["features"]
            next_track = self.tracks[ti + 1]
            next_track_features = next_track.track_data["features"]
            for ctf in current_track_features:
                ctf_group = ctf["group"]
                next_track_same_group_features = [i for i in next_track_features if i["group"] == ctf_group]
                for ntf in next_track_same_group_features:
                    cty_u = current_track.track_data["feature_upper"]
                    cty_c = current_track.track_data["feature_upper"] - 0.5 * self.prms.args["feature_height"] * mm
                    cty_b = current_track.track_data["feature_upper"] - self.prms.args["feature_height"] * mm
                    nty_u = next_track.track_data["feature_upper"]
                    nty_c = next_track.track_data["feature_upper"] - 0.5 * self.prms.args["feature_height"] * mm
                    nty_b = next_track.track_data["feature_upper"] - self.prms.args["feature_height"] * mm
                    ct_arrow_len = min(
                        self.prms.args["feature_height"] * mm * self.prms.args["feature_arrow_length"],
                        (ctf["coordinates"]["end"] - ctf["coordinates"]["start"]))
                    nt_arrow_len = min(
                        self.prms.args["feature_height"] * mm * self.prms.args["feature_arrow_length"],
                        (ntf["coordinates"]["end"] - ntf["coordinates"]["start"]))
                    canvas.setLineCap(0)
                    canvas.setLineJoin(1)
                    canvas.setLineWidth(self.prms.args["homology_line_width"])
                    canvas.setStrokeColorRGB(*lovis4u.Methods.get_colour_rgba("homology_stroke_colour", self.prms))
                    canvas.setFillColorRGB(*lovis4u.Methods.get_colour_rgba("homology_fill_colour", self.prms))
                    p = canvas.beginPath()

                    if ctf["coordinates"]["orient"] == 1:
                        cts, cte = ctf["coordinates"]["start"], ctf["coordinates"]["end"]
                        p.moveTo(ctf["coordinates"]["start"], cty_b)
                        if not ctf["coordinates"]["rout"]:
                            p.lineTo(ctf["coordinates"]["end"] - ct_arrow_len, cty_b)
                            p.lineTo(ctf["coordinates"]["end"], cty_c)
                        else:
                            p.lineTo(ctf["coordinates"]["end"], cty_b)
                    elif ctf["coordinates"]["orient"] == -1:
                        cts, cte = ctf["coordinates"]["end"], ctf["coordinates"]["start"]
                        p.moveTo(ctf["coordinates"]["end"], cty_b)
                        if not ctf["coordinates"]["lout"]:
                            p.lineTo(ctf["coordinates"]["start"] + ct_arrow_len, cty_b)
                            p.lineTo(ctf["coordinates"]["start"], cty_c)
                        else:
                            p.lineTo(ctf["coordinates"]["start"], cty_b)
                    if ntf["coordinates"]["orient"] == 1:
                        nts, nte = ntf["coordinates"]["end"], ntf["coordinates"]["start"]
                        if not ntf["coordinates"]["rout"]:
                            p.lineTo(ntf["coordinates"]["end"], nty_c)
                            p.lineTo(ntf["coordinates"]["end"] - nt_arrow_len, nty_u)
                        else:
                            p.lineTo(ntf["coordinates"]["end"], nty_u)
                        p.lineTo(ntf["coordinates"]["start"], nty_u)
                        if nte >= cts:
                            p.lineTo(ntf["coordinates"]["start"], nty_c)
                    elif ntf["coordinates"]["orient"] == -1:
                        nts, nte = ntf["coordinates"]["start"], ntf["coordinates"]["end"]
                        if not ntf["coordinates"]["lout"]:
                            p.lineTo(ntf["coordinates"]["start"], nty_c)
                            p.lineTo(ntf["coordinates"]["start"] + nt_arrow_len, nty_u)
                        else:
                            p.lineTo(ntf["coordinates"]["start"], nty_u)
                        p.lineTo(ntf["coordinates"]["end"], nty_u)
                        if nte <= cts:
                            p.lineTo(ntf["coordinates"]["end"], nty_c)
                    if (nte <= cts and ctf["coordinates"]["orient"] == 1) or (
                            nte >= cts and ctf["coordinates"]["orient"] == -1):
                        p.lineTo(cts, cty_c)
                    p.lineTo(cts, cty_b)
                    canvas.drawPath(p, stroke=1, fill=1)
    except Exception as error:
        raise lovis4u.Manager.lovis4uError("Unable to draw homology line track.") from error

LocusVis

Bases: Track

LocusVis track object that handles each locus visualisation.

Attributes:

  • layout (dict) –

    Layout built by CanvasManager's define_layout() method.

  • track_data (dict) –

    a dictionary with prepared track specific data.

  • prms (Parameters) –

    Parameters' class object.

Source code in lovis4u/Drawing.py
class LocusVis(Track):
    """LocusVis track object that handles each locus visualisation.

    Attributes:
        layout (dict): Layout built by CanvasManager's define_layout() method.
        track_data (dict): a dictionary with prepared track specific data.
        prms (lovis4u.Manager.Parameters): Parameters' class object.

    """

    def __init__(self, layout: dict, track_data: dict, parameters):
        """Create a LocusVis object.

        Arguments:
            layout (dict): Layout built by CanvasManager's define_layout() method.
            track_data (dict): a dictionary with prepared track specific data.
            parameters (lovis4u.Manager.Parameters): Parameters' class object.


        """
        super().__init__(layout, track_data, parameters)

    def draw(self, canvas: reportlab.pdfgen.canvas.Canvas) -> None:
        """Draw a LocusVis track.

        Arguments:
            canvas (reportlab.pdfgen.canvas.Canvas): a canvas object.

        Returns:
            None

        """
        try:
            y_track_bottom = self.layout["current_y_coordinate"]  - self.track_data["track_height"]
            feature_height = self.prms.args["feature_height"] * mm
            y_feature_upper = self.layout["current_y_coordinate"] - (self.track_data["n_label_rows"] *
                                                                     self.track_data["f_label_height"] *
                                                                     (1 + self.prms.args["feature_label_gap"]))
            y_feature_bottom = y_feature_upper - feature_height
            y_feature_center = y_feature_upper - feature_height * 0.5

            # Sequence label
            canvas.setFillColorRGB(*lovis4u.Methods.get_colour_rgba("locus_label_colour", self.prms))
            canvas.setFont(self.prms.args["locus_label_description_font_face"], self.prms.args["locus_label_font_size"])
            if self.prms.args["locus_label_position"] == "left":
                if self.prms.args["locus_label_style"] == "full" and self.track_data["locus_description"]:
                    label_bottom = y_feature_upper - self.prms.args["locus_label_height"]
                    canvas.drawRightString(self.layout["locus_label_right_border"], label_bottom,
                                           self.track_data["locus_description"])
                    label_bottom = y_feature_bottom
                else:
                    label_bottom = y_feature_bottom + (feature_height - self.prms.args["locus_label_height"]) * 0.5
                    label = self.track_data["locus_description"]
                if self.prms.args["locus_label_style"] != "description":
                    label = self.track_data["locus_id"]
                    canvas.setFont(self.prms.args["locus_label_id_font_face"], self.prms.args["locus_label_font_size"])
                canvas.drawRightString(self.layout["locus_label_right_border"], label_bottom, label)
            elif self.prms.args["locus_label_position"] == "bottom":
                label_bottom = y_track_bottom
                current_left = self.layout["locus_label_left_border"]
                if self.prms.args["locus_label_style"] == "full" and self.track_data["locus_description"]:
                    canvas.drawString(current_left, label_bottom, self.track_data["locus_description"])
                    current_left += self.track_data["locus_description_width"] + self.track_data["two_space_width"]
                canvas.setFont(self.prms.args["locus_label_id_font_face"], self.prms.args["locus_label_font_size"])
                canvas.drawString(current_left, label_bottom, self.track_data["locus_id"])
                current_left += self.track_data["locus_id_width"] + self.track_data["two_space_width"]
                canvas.drawString(current_left, label_bottom, self.track_data["text_coordinates"])

            # Middle line
            if self.prms.args["draw_middle_line"]:
                canvas.setLineWidth(self.prms.args["x_axis_line_width"])
                canvas.setStrokeColorRGB(*lovis4u.Methods.get_colour_rgba("x_axis_line_colour", self.prms))
                canvas.setLineCap(0)
                canvas.setFillColorRGB(*lovis4u.Methods.get_colour_rgba("x_axis_line_colour", self.prms))
                p = canvas.beginPath()
                for md_line_coordinates in self.track_data["middle_line_coordinates"]:
                    p.moveTo(md_line_coordinates["start"], y_feature_center)
                    p.lineTo(md_line_coordinates["end"], y_feature_center)
                canvas.drawPath(p, stroke=1, fill=0)

            # Category annotation
            if self.track_data["functions_coordinates"]:
                for feature_function, ff_region in self.track_data["functions_coordinates"].items():
                    feature_colour = self.track_data["category_colours"][feature_function]
                    canvas.setFillColorRGB(*matplotlib.colors.hex2color(feature_colour),
                                           self.prms.args["category_annotation_alpha"])
                    canvas.setLineJoin(1)
                    y_upper_sausage = y_feature_bottom - self.prms.args["feature_bottom_gap"] * mm
                    y_bottom_sausage = y_upper_sausage - self.prms.args["category_annotation_line_width"] * mm
                    for ffr in ff_region:
                        p = canvas.beginPath()
                        p.moveTo(ffr[0], y_bottom_sausage)
                        p.lineTo(ffr[0], y_upper_sausage)
                        p.lineTo(ffr[1], y_upper_sausage)
                        p.lineTo(ffr[1], y_bottom_sausage)
                        p.lineTo(ffr[0], y_bottom_sausage)
                        p.close()
                        canvas.drawPath(p, stroke=0, fill=1)
            # Features
            canvas.setLineCap(0)
            canvas.setLineJoin(1)
            canvas.setLineWidth(self.prms.args["feature_stroke_width"])
            if self.track_data["clean_features_coordinates"]:
                for f_data in self.track_data["features"]:
                    f_data_copy = copy.deepcopy(f_data)
                    f_data_copy["stroke_colour"] = None
                    f_data_copy["fill_colour"] = (*matplotlib.colors.hex2color(self.prms.args["palette"]["white"]), 1)
                    self.__plot_cds_feature(canvas, f_data_copy, y_center=y_feature_center, height=feature_height)
            for f_data in self.track_data["features"]:
                f_data["stroke_colour"] = *matplotlib.colors.hex2color(f_data["stroke_colour"]), self.prms.args[
                    "feature_stroke_colour_alpha"]
                f_data["fill_colour"] = *matplotlib.colors.hex2color(f_data["fill_colour"]), self.prms.args[
                    "feature_fill_colour_alpha"]
                self.__plot_cds_feature(canvas, f_data, y_center=y_feature_center, height=feature_height)
                if f_data["label_width"]:
                    canvas.setFillColorRGB(*f_data["stroke_colour"])
                    canvas.setFont(self.prms.args["feature_label_font_face"],
                                   self.track_data["f_label_font_size"])
                    fx_center = f_data["coordinates"]["center"]
                    canvas.drawString(f_data["label_position"][0], f_data["label_y_bottom"] + y_feature_upper,
                                      f_data["label"])
                    canvas.setLineWidth(self.prms.args["feature_stroke_width"])
                    underline_colour = f_data["stroke_colour"]
                    canvas.setStrokeColorRGB(*underline_colour)
                    canvas.setLineCap(1)
                    if f_data["label_row"] > 0:
                        p = canvas.beginPath()
                        for ls, le in f_data["label_line_coordinates"]:
                            p.moveTo(fx_center, ls + y_feature_upper)
                            p.lineTo(fx_center, le + y_feature_upper)
                        canvas.drawPath(p, stroke=1, fill=0)
                        l_start = f_data["coordinates"]["start"]
                        l_end = min(f_data["coordinates"]["end"], fx_center + self.track_data["feature_label_gap"])
                        l_end = f_data["coordinates"]["end"]
                        ly = y_feature_upper + f_data["label_y_bottom"] - self.track_data["feature_label_gap"] * 0.5
                        canvas.line(l_start, ly, l_end, ly)
                    else:
                        overlapping = min(f_data["coordinates"]["end"], f_data["label_position"][1]) - (
                            max(f_data["coordinates"]["start"], f_data["label_position"][0]))
                        if overlapping / (f_data["label_position"][1] - f_data["label_position"][0]) < 1:
                            l_start = max(f_data["coordinates"]["start"], fx_center -
                                          self.track_data["feature_label_gap"])
                            l_start = f_data["coordinates"]["start"]
                            l_end = min(f_data["coordinates"]["end"], fx_center + self.track_data["feature_label_gap"])
                            l_end = f_data["coordinates"]["end"]
                            ly = y_feature_upper + f_data["label_y_bottom"] - self.track_data["feature_label_gap"] * 0.5
                            canvas.line(l_start, ly, l_end, ly)
            # Axis ticks
            if self.prms.args["draw_individual_x_axis"]:
                canvas.setLineWidth(self.prms.args["x_axis_line_width"])
                canvas.setStrokeColorRGB(*lovis4u.Methods.get_colour_rgba("x_axis_line_colour", self.prms))
                canvas.setLineCap(1)
                canvas.setFillColorRGB(*lovis4u.Methods.get_colour_rgba("x_axis_line_colour", self.prms))
                canvas.setFont(self.prms.args["x_axis_ticks_labels_font_face"],
                               self.track_data["x_axis_annotation"]["label_size"])
                axis_line_y_coordinate = y_feature_bottom - self.prms.args["feature_bottom_gap"] * mm
                axis_tick_height = self.prms.args["x_axis_ticks_height"] * mm
                axis_tick_label_y_coordinate = axis_line_y_coordinate - self.prms.args["x_axis_ticks_height"] * \
                                               1.3 * mm - self.prms.args["x_axis_ticks_labels_height"] * mm
                for ati in range(len(self.track_data["x_axis_annotation"]["axis_tics_position"])):
                    tick_coordinate = self.track_data["x_axis_annotation"]["axis_tics_position"][ati]
                    tick_label_position = self.track_data["x_axis_annotation"]["tics_labels_coordinates"][ati]
                    tick_label = self.track_data["x_axis_annotation"]["axis_tics_labels"][ati]
                    canvas.drawCentredString(tick_label_position, axis_tick_label_y_coordinate, tick_label)
                    canvas.line(tick_coordinate, axis_line_y_coordinate, tick_coordinate,
                                axis_line_y_coordinate - axis_tick_height)
                for region in self.track_data["x_axis_annotation"]["axis_regions"]:
                    canvas.setLineCap(0)
                    canvas.line(region["start"], axis_line_y_coordinate, region["end"], axis_line_y_coordinate)
            return None
        except Exception as error:
            raise lovis4u.Manager.lovis4uError("Unable to draw a Locus track.") from error

    def __plot_cds_feature(self, canvas: reportlab.pdfgen.canvas.Canvas, feature_data: dict, y_center: float,
                           height: float) -> None:
        """Helper method to plot feature polygone

        Returns:
            None

        """
        x_start = feature_data["coordinates"]["start"]
        x_end = feature_data["coordinates"]["end"]
        orientation = feature_data["coordinates"]["orient"]
        left_out = feature_data["coordinates"]["lout"]
        right_out = feature_data["coordinates"]["rout"]
        fill_colour = feature_data["fill_colour"]
        stroke_colour = feature_data["stroke_colour"]
        y_center = y_center
        height = height

        canvas.setLineCap(0)
        canvas.setLineWidth(self.prms.args["feature_stroke_width"])
        arrow_length = min(height * self.prms.args["feature_arrow_length"], (x_end - x_start))
        p = canvas.beginPath()
        if orientation == 1:
            if right_out:
                p.moveTo(x_end, y_center - height / 2)
                p.lineTo(x_start, y_center - height / 2)
                p.lineTo(x_start, y_center + height / 2)
                p.lineTo(x_end, y_center + height / 2)
            else:
                p.moveTo(x_start, y_center + height / 2)
                p.lineTo(x_end - arrow_length, y_center + height / 2)
                p.lineTo(x_end, y_center)
                p.lineTo(x_end - arrow_length, y_center - height / 2)
                p.lineTo(x_start, y_center - height / 2)
                if not left_out:
                    p.lineTo(x_start, y_center + height / 2)
        elif orientation == -1:
            if left_out:
                p.moveTo(x_start, y_center - height / 2)
                p.lineTo(x_end, y_center - height / 2)
                p.lineTo(x_end, y_center + height / 2)
                p.lineTo(x_start, y_center + height / 2)
            else:
                p.moveTo(x_end, y_center + height / 2)
                p.lineTo(x_start + arrow_length, y_center + height / 2)
                p.lineTo(x_start, y_center)
                p.lineTo(x_start + arrow_length, y_center - height / 2)
                p.lineTo(x_end, y_center - height / 2)
                if not right_out:
                    p.lineTo(x_end, y_center + height / 2)
        if left_out and right_out:
            p.moveTo(x_start, y_center + height / 2)
            p.lineTo(x_end, y_center + height / 2)
            p.moveTo(x_start, y_center - height / 2)
            p.lineTo(x_end, y_center - height / 2)
        if not left_out and not right_out:
            p.close()
        stroke, fill = 0, 0
        if stroke_colour:
            canvas.setStrokeColorRGB(*stroke_colour)
            stroke = 1
        if fill_colour:
            canvas.setFillColorRGB(*fill_colour)
            fill = 1
        canvas.drawPath(p, stroke=stroke, fill=fill)
        return None

__init__(layout, track_data, parameters)

Create a LocusVis object.

Parameters:

  • layout (dict) –

    Layout built by CanvasManager's define_layout() method.

  • track_data (dict) –

    a dictionary with prepared track specific data.

  • parameters (Parameters) –

    Parameters' class object.

Source code in lovis4u/Drawing.py
def __init__(self, layout: dict, track_data: dict, parameters):
    """Create a LocusVis object.

    Arguments:
        layout (dict): Layout built by CanvasManager's define_layout() method.
        track_data (dict): a dictionary with prepared track specific data.
        parameters (lovis4u.Manager.Parameters): Parameters' class object.


    """
    super().__init__(layout, track_data, parameters)

__plot_cds_feature(canvas, feature_data, y_center, height)

Helper method to plot feature polygone

Returns:

  • None

    None

Source code in lovis4u/Drawing.py
def __plot_cds_feature(self, canvas: reportlab.pdfgen.canvas.Canvas, feature_data: dict, y_center: float,
                       height: float) -> None:
    """Helper method to plot feature polygone

    Returns:
        None

    """
    x_start = feature_data["coordinates"]["start"]
    x_end = feature_data["coordinates"]["end"]
    orientation = feature_data["coordinates"]["orient"]
    left_out = feature_data["coordinates"]["lout"]
    right_out = feature_data["coordinates"]["rout"]
    fill_colour = feature_data["fill_colour"]
    stroke_colour = feature_data["stroke_colour"]
    y_center = y_center
    height = height

    canvas.setLineCap(0)
    canvas.setLineWidth(self.prms.args["feature_stroke_width"])
    arrow_length = min(height * self.prms.args["feature_arrow_length"], (x_end - x_start))
    p = canvas.beginPath()
    if orientation == 1:
        if right_out:
            p.moveTo(x_end, y_center - height / 2)
            p.lineTo(x_start, y_center - height / 2)
            p.lineTo(x_start, y_center + height / 2)
            p.lineTo(x_end, y_center + height / 2)
        else:
            p.moveTo(x_start, y_center + height / 2)
            p.lineTo(x_end - arrow_length, y_center + height / 2)
            p.lineTo(x_end, y_center)
            p.lineTo(x_end - arrow_length, y_center - height / 2)
            p.lineTo(x_start, y_center - height / 2)
            if not left_out:
                p.lineTo(x_start, y_center + height / 2)
    elif orientation == -1:
        if left_out:
            p.moveTo(x_start, y_center - height / 2)
            p.lineTo(x_end, y_center - height / 2)
            p.lineTo(x_end, y_center + height / 2)
            p.lineTo(x_start, y_center + height / 2)
        else:
            p.moveTo(x_end, y_center + height / 2)
            p.lineTo(x_start + arrow_length, y_center + height / 2)
            p.lineTo(x_start, y_center)
            p.lineTo(x_start + arrow_length, y_center - height / 2)
            p.lineTo(x_end, y_center - height / 2)
            if not right_out:
                p.lineTo(x_end, y_center + height / 2)
    if left_out and right_out:
        p.moveTo(x_start, y_center + height / 2)
        p.lineTo(x_end, y_center + height / 2)
        p.moveTo(x_start, y_center - height / 2)
        p.lineTo(x_end, y_center - height / 2)
    if not left_out and not right_out:
        p.close()
    stroke, fill = 0, 0
    if stroke_colour:
        canvas.setStrokeColorRGB(*stroke_colour)
        stroke = 1
    if fill_colour:
        canvas.setFillColorRGB(*fill_colour)
        fill = 1
    canvas.drawPath(p, stroke=stroke, fill=fill)
    return None

draw(canvas)

Draw a LocusVis track.

Parameters:

  • canvas (Canvas) –

    a canvas object.

Returns:

  • None

    None

Source code in lovis4u/Drawing.py
def draw(self, canvas: reportlab.pdfgen.canvas.Canvas) -> None:
    """Draw a LocusVis track.

    Arguments:
        canvas (reportlab.pdfgen.canvas.Canvas): a canvas object.

    Returns:
        None

    """
    try:
        y_track_bottom = self.layout["current_y_coordinate"]  - self.track_data["track_height"]
        feature_height = self.prms.args["feature_height"] * mm
        y_feature_upper = self.layout["current_y_coordinate"] - (self.track_data["n_label_rows"] *
                                                                 self.track_data["f_label_height"] *
                                                                 (1 + self.prms.args["feature_label_gap"]))
        y_feature_bottom = y_feature_upper - feature_height
        y_feature_center = y_feature_upper - feature_height * 0.5

        # Sequence label
        canvas.setFillColorRGB(*lovis4u.Methods.get_colour_rgba("locus_label_colour", self.prms))
        canvas.setFont(self.prms.args["locus_label_description_font_face"], self.prms.args["locus_label_font_size"])
        if self.prms.args["locus_label_position"] == "left":
            if self.prms.args["locus_label_style"] == "full" and self.track_data["locus_description"]:
                label_bottom = y_feature_upper - self.prms.args["locus_label_height"]
                canvas.drawRightString(self.layout["locus_label_right_border"], label_bottom,
                                       self.track_data["locus_description"])
                label_bottom = y_feature_bottom
            else:
                label_bottom = y_feature_bottom + (feature_height - self.prms.args["locus_label_height"]) * 0.5
                label = self.track_data["locus_description"]
            if self.prms.args["locus_label_style"] != "description":
                label = self.track_data["locus_id"]
                canvas.setFont(self.prms.args["locus_label_id_font_face"], self.prms.args["locus_label_font_size"])
            canvas.drawRightString(self.layout["locus_label_right_border"], label_bottom, label)
        elif self.prms.args["locus_label_position"] == "bottom":
            label_bottom = y_track_bottom
            current_left = self.layout["locus_label_left_border"]
            if self.prms.args["locus_label_style"] == "full" and self.track_data["locus_description"]:
                canvas.drawString(current_left, label_bottom, self.track_data["locus_description"])
                current_left += self.track_data["locus_description_width"] + self.track_data["two_space_width"]
            canvas.setFont(self.prms.args["locus_label_id_font_face"], self.prms.args["locus_label_font_size"])
            canvas.drawString(current_left, label_bottom, self.track_data["locus_id"])
            current_left += self.track_data["locus_id_width"] + self.track_data["two_space_width"]
            canvas.drawString(current_left, label_bottom, self.track_data["text_coordinates"])

        # Middle line
        if self.prms.args["draw_middle_line"]:
            canvas.setLineWidth(self.prms.args["x_axis_line_width"])
            canvas.setStrokeColorRGB(*lovis4u.Methods.get_colour_rgba("x_axis_line_colour", self.prms))
            canvas.setLineCap(0)
            canvas.setFillColorRGB(*lovis4u.Methods.get_colour_rgba("x_axis_line_colour", self.prms))
            p = canvas.beginPath()
            for md_line_coordinates in self.track_data["middle_line_coordinates"]:
                p.moveTo(md_line_coordinates["start"], y_feature_center)
                p.lineTo(md_line_coordinates["end"], y_feature_center)
            canvas.drawPath(p, stroke=1, fill=0)

        # Category annotation
        if self.track_data["functions_coordinates"]:
            for feature_function, ff_region in self.track_data["functions_coordinates"].items():
                feature_colour = self.track_data["category_colours"][feature_function]
                canvas.setFillColorRGB(*matplotlib.colors.hex2color(feature_colour),
                                       self.prms.args["category_annotation_alpha"])
                canvas.setLineJoin(1)
                y_upper_sausage = y_feature_bottom - self.prms.args["feature_bottom_gap"] * mm
                y_bottom_sausage = y_upper_sausage - self.prms.args["category_annotation_line_width"] * mm
                for ffr in ff_region:
                    p = canvas.beginPath()
                    p.moveTo(ffr[0], y_bottom_sausage)
                    p.lineTo(ffr[0], y_upper_sausage)
                    p.lineTo(ffr[1], y_upper_sausage)
                    p.lineTo(ffr[1], y_bottom_sausage)
                    p.lineTo(ffr[0], y_bottom_sausage)
                    p.close()
                    canvas.drawPath(p, stroke=0, fill=1)
        # Features
        canvas.setLineCap(0)
        canvas.setLineJoin(1)
        canvas.setLineWidth(self.prms.args["feature_stroke_width"])
        if self.track_data["clean_features_coordinates"]:
            for f_data in self.track_data["features"]:
                f_data_copy = copy.deepcopy(f_data)
                f_data_copy["stroke_colour"] = None
                f_data_copy["fill_colour"] = (*matplotlib.colors.hex2color(self.prms.args["palette"]["white"]), 1)
                self.__plot_cds_feature(canvas, f_data_copy, y_center=y_feature_center, height=feature_height)
        for f_data in self.track_data["features"]:
            f_data["stroke_colour"] = *matplotlib.colors.hex2color(f_data["stroke_colour"]), self.prms.args[
                "feature_stroke_colour_alpha"]
            f_data["fill_colour"] = *matplotlib.colors.hex2color(f_data["fill_colour"]), self.prms.args[
                "feature_fill_colour_alpha"]
            self.__plot_cds_feature(canvas, f_data, y_center=y_feature_center, height=feature_height)
            if f_data["label_width"]:
                canvas.setFillColorRGB(*f_data["stroke_colour"])
                canvas.setFont(self.prms.args["feature_label_font_face"],
                               self.track_data["f_label_font_size"])
                fx_center = f_data["coordinates"]["center"]
                canvas.drawString(f_data["label_position"][0], f_data["label_y_bottom"] + y_feature_upper,
                                  f_data["label"])
                canvas.setLineWidth(self.prms.args["feature_stroke_width"])
                underline_colour = f_data["stroke_colour"]
                canvas.setStrokeColorRGB(*underline_colour)
                canvas.setLineCap(1)
                if f_data["label_row"] > 0:
                    p = canvas.beginPath()
                    for ls, le in f_data["label_line_coordinates"]:
                        p.moveTo(fx_center, ls + y_feature_upper)
                        p.lineTo(fx_center, le + y_feature_upper)
                    canvas.drawPath(p, stroke=1, fill=0)
                    l_start = f_data["coordinates"]["start"]
                    l_end = min(f_data["coordinates"]["end"], fx_center + self.track_data["feature_label_gap"])
                    l_end = f_data["coordinates"]["end"]
                    ly = y_feature_upper + f_data["label_y_bottom"] - self.track_data["feature_label_gap"] * 0.5
                    canvas.line(l_start, ly, l_end, ly)
                else:
                    overlapping = min(f_data["coordinates"]["end"], f_data["label_position"][1]) - (
                        max(f_data["coordinates"]["start"], f_data["label_position"][0]))
                    if overlapping / (f_data["label_position"][1] - f_data["label_position"][0]) < 1:
                        l_start = max(f_data["coordinates"]["start"], fx_center -
                                      self.track_data["feature_label_gap"])
                        l_start = f_data["coordinates"]["start"]
                        l_end = min(f_data["coordinates"]["end"], fx_center + self.track_data["feature_label_gap"])
                        l_end = f_data["coordinates"]["end"]
                        ly = y_feature_upper + f_data["label_y_bottom"] - self.track_data["feature_label_gap"] * 0.5
                        canvas.line(l_start, ly, l_end, ly)
        # Axis ticks
        if self.prms.args["draw_individual_x_axis"]:
            canvas.setLineWidth(self.prms.args["x_axis_line_width"])
            canvas.setStrokeColorRGB(*lovis4u.Methods.get_colour_rgba("x_axis_line_colour", self.prms))
            canvas.setLineCap(1)
            canvas.setFillColorRGB(*lovis4u.Methods.get_colour_rgba("x_axis_line_colour", self.prms))
            canvas.setFont(self.prms.args["x_axis_ticks_labels_font_face"],
                           self.track_data["x_axis_annotation"]["label_size"])
            axis_line_y_coordinate = y_feature_bottom - self.prms.args["feature_bottom_gap"] * mm
            axis_tick_height = self.prms.args["x_axis_ticks_height"] * mm
            axis_tick_label_y_coordinate = axis_line_y_coordinate - self.prms.args["x_axis_ticks_height"] * \
                                           1.3 * mm - self.prms.args["x_axis_ticks_labels_height"] * mm
            for ati in range(len(self.track_data["x_axis_annotation"]["axis_tics_position"])):
                tick_coordinate = self.track_data["x_axis_annotation"]["axis_tics_position"][ati]
                tick_label_position = self.track_data["x_axis_annotation"]["tics_labels_coordinates"][ati]
                tick_label = self.track_data["x_axis_annotation"]["axis_tics_labels"][ati]
                canvas.drawCentredString(tick_label_position, axis_tick_label_y_coordinate, tick_label)
                canvas.line(tick_coordinate, axis_line_y_coordinate, tick_coordinate,
                            axis_line_y_coordinate - axis_tick_height)
            for region in self.track_data["x_axis_annotation"]["axis_regions"]:
                canvas.setLineCap(0)
                canvas.line(region["start"], axis_line_y_coordinate, region["end"], axis_line_y_coordinate)
        return None
    except Exception as error:
        raise lovis4u.Manager.lovis4uError("Unable to draw a Locus track.") from error

ScaleVis

Bases: Track

ScaleVis track object that handles visualisation of scale bottom line.

Attributes:

  • layout (dict) –

    Layout built by CanvasManager's define_layout() method.

  • track_data (dict) –

    a dictionary with prepared track specific data.

  • prms (Parameters) –

    Parameters' class object.

Source code in lovis4u/Drawing.py
class ScaleVis(Track):
    """ScaleVis track object that handles visualisation of scale bottom line.

    Attributes:
        layout (dict): Layout built by CanvasManager's define_layout() method.
        track_data (dict): a dictionary with prepared track specific data.
        prms (lovis4u.Manager.Parameters): Parameters' class object.

    """

    def __init__(self, layout: dict, track_data: dict, parameters):
        """Create a LocusVis object.

        Arguments:
            layout (dict): Layout built by CanvasManager's define_layout() method.
            track_data (dict): a dictionary with prepared track specific data.
            parameters (lovis4u.Manager.Parameters): Parameters' class object.


        """
        super().__init__(layout, track_data, parameters)
        self.track_height = None

    def draw(self, canvas: reportlab.pdfgen.canvas.Canvas) -> None:
        """Draw a ScaleVis track.

        Arguments:
            canvas (reportlab.pdfgen.canvas.Canvas): a canvas object.

        Returns:
            None

        """
        try:
            y_upper = self.layout["current_y_coordinate"]
            y_bottom = self.layout["current_y_coordinate"] - self.track_data["track_height"]
            y_center = self.layout["current_y_coordinate"] - 0.5 * self.track_data["track_height"]

            middle_line_x_position = np.mean(self.track_data["coordinates"])

            canvas.setLineWidth(self.prms.args["scale_line_width"])
            canvas.setStrokeColorRGB(*lovis4u.Methods.get_colour_rgba("scale_line_colour", self.prms))
            canvas.setLineCap(0)
            canvas.setLineJoin(1)
            if self.track_data["style"] == "fancy":
                tick_height = self.track_data["scale_line_label_height"]

                p = canvas.beginPath()
                p.moveTo(self.track_data["coordinates"][0], y_center - 0.5 * tick_height)
                p.lineTo(self.track_data["coordinates"][0], y_center + 0.5 * tick_height)
                p.moveTo(self.track_data["coordinates"][0], y_center)
                p.lineTo(middle_line_x_position - 0.5 * self.track_data["scale_line_label_width"] -
                         self.track_data["space_width"], y_center)
                p.moveTo(self.track_data["coordinates"][1], y_center - 0.5 * tick_height)
                p.lineTo(self.track_data["coordinates"][1], y_center + 0.5 * tick_height)
                p.moveTo(self.track_data["coordinates"][1], y_center)
                p.lineTo(middle_line_x_position + 0.5 * self.track_data["scale_line_label_width"] +
                         self.track_data["space_width"], y_center)
            else:
                tick_height = self.prms.args["scale_line_tics_height"] * mm

                p = canvas.beginPath()
                p.moveTo(self.track_data["coordinates"][0], y_upper - tick_height)
                p.lineTo(self.track_data["coordinates"][0], y_upper)
                p.moveTo(self.track_data["coordinates"][0], y_upper - tick_height * 0.5)
                p.lineTo(self.track_data["coordinates"][1], y_upper - tick_height * 0.5)
                p.lineTo(self.track_data["coordinates"][1], y_upper)
                p.lineTo(self.track_data["coordinates"][1], y_upper - tick_height)
            canvas.drawPath(p, stroke=1, fill=0)
            canvas.setFillColorRGB(*lovis4u.Methods.get_colour_rgba("scale_line_colour", self.prms))
            canvas.setFont(self.prms.args["scale_line_label_font_face"],
                           self.track_data["scale_line_label_font_size"])
            canvas.drawCentredString(middle_line_x_position, y_bottom, self.track_data["scale_label"])
            return None
        except Exception as error:
            raise lovis4u.Manager.lovis4uError("Unable to draw a scale track.") from error

__init__(layout, track_data, parameters)

Create a LocusVis object.

Parameters:

  • layout (dict) –

    Layout built by CanvasManager's define_layout() method.

  • track_data (dict) –

    a dictionary with prepared track specific data.

  • parameters (Parameters) –

    Parameters' class object.

Source code in lovis4u/Drawing.py
def __init__(self, layout: dict, track_data: dict, parameters):
    """Create a LocusVis object.

    Arguments:
        layout (dict): Layout built by CanvasManager's define_layout() method.
        track_data (dict): a dictionary with prepared track specific data.
        parameters (lovis4u.Manager.Parameters): Parameters' class object.


    """
    super().__init__(layout, track_data, parameters)
    self.track_height = None

draw(canvas)

Draw a ScaleVis track.

Parameters:

  • canvas (Canvas) –

    a canvas object.

Returns:

  • None

    None

Source code in lovis4u/Drawing.py
def draw(self, canvas: reportlab.pdfgen.canvas.Canvas) -> None:
    """Draw a ScaleVis track.

    Arguments:
        canvas (reportlab.pdfgen.canvas.Canvas): a canvas object.

    Returns:
        None

    """
    try:
        y_upper = self.layout["current_y_coordinate"]
        y_bottom = self.layout["current_y_coordinate"] - self.track_data["track_height"]
        y_center = self.layout["current_y_coordinate"] - 0.5 * self.track_data["track_height"]

        middle_line_x_position = np.mean(self.track_data["coordinates"])

        canvas.setLineWidth(self.prms.args["scale_line_width"])
        canvas.setStrokeColorRGB(*lovis4u.Methods.get_colour_rgba("scale_line_colour", self.prms))
        canvas.setLineCap(0)
        canvas.setLineJoin(1)
        if self.track_data["style"] == "fancy":
            tick_height = self.track_data["scale_line_label_height"]

            p = canvas.beginPath()
            p.moveTo(self.track_data["coordinates"][0], y_center - 0.5 * tick_height)
            p.lineTo(self.track_data["coordinates"][0], y_center + 0.5 * tick_height)
            p.moveTo(self.track_data["coordinates"][0], y_center)
            p.lineTo(middle_line_x_position - 0.5 * self.track_data["scale_line_label_width"] -
                     self.track_data["space_width"], y_center)
            p.moveTo(self.track_data["coordinates"][1], y_center - 0.5 * tick_height)
            p.lineTo(self.track_data["coordinates"][1], y_center + 0.5 * tick_height)
            p.moveTo(self.track_data["coordinates"][1], y_center)
            p.lineTo(middle_line_x_position + 0.5 * self.track_data["scale_line_label_width"] +
                     self.track_data["space_width"], y_center)
        else:
            tick_height = self.prms.args["scale_line_tics_height"] * mm

            p = canvas.beginPath()
            p.moveTo(self.track_data["coordinates"][0], y_upper - tick_height)
            p.lineTo(self.track_data["coordinates"][0], y_upper)
            p.moveTo(self.track_data["coordinates"][0], y_upper - tick_height * 0.5)
            p.lineTo(self.track_data["coordinates"][1], y_upper - tick_height * 0.5)
            p.lineTo(self.track_data["coordinates"][1], y_upper)
            p.lineTo(self.track_data["coordinates"][1], y_upper - tick_height)
        canvas.drawPath(p, stroke=1, fill=0)
        canvas.setFillColorRGB(*lovis4u.Methods.get_colour_rgba("scale_line_colour", self.prms))
        canvas.setFont(self.prms.args["scale_line_label_font_face"],
                       self.track_data["scale_line_label_font_size"])
        canvas.drawCentredString(middle_line_x_position, y_bottom, self.track_data["scale_label"])
        return None
    except Exception as error:
        raise lovis4u.Manager.lovis4uError("Unable to draw a scale track.") from error

Track

Parent class for visualisation Tracks.

Attributes:

  • layout (dict) –

    Layout built by CanvasManager's define_layout() method.

  • track_data (dict) –

    a dictionary with prepared track specific data.

  • prms (Parameters) –

    Parameters' class object.

Source code in lovis4u/Drawing.py
class Track:
    """Parent class for visualisation Tracks.

    Attributes:
        layout (dict): Layout built by CanvasManager's define_layout() method.
        track_data (dict): a dictionary with prepared track specific data.
        prms (lovis4u.Manager.Parameters): Parameters' class object.

    """

    def __init__(self, layout: dict, track_data: dict, parameters: lovis4u.Manager.Parameters):
        """Parent's constructor for creating a Track object.

        Arguments:
            layout (dict): Layout built by CanvasManager's define_layout() method.
            track_data (dict): a dictionary with prepared track specific data.
            parameters (lovis4u.Manager.Parameters): Parameters' class object.

        """
        self.layout = layout
        self.track_data = track_data
        self.prms = parameters

    def draw(self, canvas: reportlab.pdfgen.canvas.Canvas) -> None:
        """Empy parent's method for track drawing.

        Arguments:
            canvas (reportlab.pdfgen.canvas.Canvas): a canvas object.

        Returns:
            None

        """
        pass

__init__(layout, track_data, parameters)

Parent's constructor for creating a Track object.

Parameters:

  • layout (dict) –

    Layout built by CanvasManager's define_layout() method.

  • track_data (dict) –

    a dictionary with prepared track specific data.

  • parameters (Parameters) –

    Parameters' class object.

Source code in lovis4u/Drawing.py
def __init__(self, layout: dict, track_data: dict, parameters: lovis4u.Manager.Parameters):
    """Parent's constructor for creating a Track object.

    Arguments:
        layout (dict): Layout built by CanvasManager's define_layout() method.
        track_data (dict): a dictionary with prepared track specific data.
        parameters (lovis4u.Manager.Parameters): Parameters' class object.

    """
    self.layout = layout
    self.track_data = track_data
    self.prms = parameters

draw(canvas)

Empy parent's method for track drawing.

Parameters:

  • canvas (Canvas) –

    a canvas object.

Returns:

  • None

    None

Source code in lovis4u/Drawing.py
def draw(self, canvas: reportlab.pdfgen.canvas.Canvas) -> None:
    """Empy parent's method for track drawing.

    Arguments:
        canvas (reportlab.pdfgen.canvas.Canvas): a canvas object.

    Returns:
        None

    """
    pass

This module provides managing classes and methods for the tool.

Canvas

An Image object holds canvas;.

Attributes:

  • canvas (Canvas) –

    canvas object of the reportlab library.

Source code in lovis4u/Manager.py
class Canvas:
    """An Image object holds canvas;.

    Attributes:
        canvas (reportlab.pdfgen.canvas.Canvas): canvas object of the reportlab library.

    """

    def __init__(self, filename: str, width: float, height: float, parameters):
        """Create a Canvas object.

        Arguments:
            filename (str): path and name of the output pdf.
            width (float): width of the canvas.
            height (float): height of the pdf.

        """
        self.prms = parameters
        self.canvas = reportlab.pdfgen.canvas.Canvas(filename, pagesize=(width, height))
        self.canvas.setTitle("lovis4u output")
        self.canvas.setSubject("🎨")
        self.canvas.setCreator("lovis4u | The Atkinson Lab 4U")

    def save(self) -> None:
        """Save canvas as a pdf file.

        Returns:
            None

        """
        if not os.path.exists(self.prms.args["output_dir"]):
            os.mkdir(self.prms.args["output_dir"])
        self.canvas.save()
        return None

__init__(filename, width, height, parameters)

Create a Canvas object.

Parameters:

  • filename (str) –

    path and name of the output pdf.

  • width (float) –

    width of the canvas.

  • height (float) –

    height of the pdf.

Source code in lovis4u/Manager.py
def __init__(self, filename: str, width: float, height: float, parameters):
    """Create a Canvas object.

    Arguments:
        filename (str): path and name of the output pdf.
        width (float): width of the canvas.
        height (float): height of the pdf.

    """
    self.prms = parameters
    self.canvas = reportlab.pdfgen.canvas.Canvas(filename, pagesize=(width, height))
    self.canvas.setTitle("lovis4u output")
    self.canvas.setSubject("🎨")
    self.canvas.setCreator("lovis4u | The Atkinson Lab 4U")

save()

Save canvas as a pdf file.

Returns:

  • None

    None

Source code in lovis4u/Manager.py
def save(self) -> None:
    """Save canvas as a pdf file.

    Returns:
        None

    """
    if not os.path.exists(self.prms.args["output_dir"]):
        os.mkdir(self.prms.args["output_dir"])
    self.canvas.save()
    return None

CanvasManager

Canvas manager object responsible for preprocessing data for visualisation and interaction between visualisation and raw data.

Attributes:

  • layout (dict) –

    Defines size and coordinate system of a canvas.

  • tracks (list) –

    List containing Track objects each of them represents visualisation unit (e.g. particular locus).

  • cross_tracks (list) –

    List containing CrossTrack objects each of them represents visualisation unit that interacts with multiple regular Track objects.

prms (Parameters): Parameters' class object that holds config and cmd arguments.
Source code in lovis4u/Manager.py
class CanvasManager:
    """Canvas manager object responsible for preprocessing data for visualisation and interaction between visualisation
        and raw data.

    Attributes:
         layout (dict): Defines size and coordinate system of a canvas.
         tracks (list): List containing Track objects each of them represents visualisation unit (e.g. particular locus).
         cross_tracks (list): List containing CrossTrack objects each of them represents visualisation unit that
            interacts with multiple regular Track objects.
        prms (Parameters): Parameters' class object that holds config and cmd arguments.

    """

    def __init__(self, parameters):
        """Create a CanvasManager object.

        Arguments:
            parameters (Parameters): Parameters' class object that holds config and cmd arguments.

        """
        self.layout = dict()
        self.tracks = []
        self.cross_tracks = []
        self.prms = parameters

    def define_layout(self, loci) -> None:
        """Define canvas' layout based on input loci.

        Arguments:
            loci (lovis4u.DataProcessing.Loci): Loci object with information about sequences and features.

        Returns:
            None

        """
        try:
            annotated_descriptions = [i.description for i in loci.loci if i.description]
            if not annotated_descriptions and self.prms.args["locus_label_style"] != "id":
                if self.prms.args["verbose"]:
                    print("○ Warning message: the annotation lacks description. Locus label style is "
                          "changed to 'id'")
                self.prms.args["locus_label_style"] = "id"
            if self.prms.args["locus_label_position"] == "left":
                if self.prms.args["locus_label_style"] == "full":
                    label_height = self.prms.args["feature_height"] * mm * 0.4
                else:
                    label_height = min(1, self.prms.args["locus_label_size"]) * self.prms.args["feature_height"] * mm
                label_font_size = lovis4u.Methods.str_height_to_size(label_height,
                                                                     self.prms.args["locus_label_id_font_face"])
            elif self.prms.args["locus_label_position"] == "bottom":
                label_font_size = self.prms.args["bottom_locus_label_font_size"]
                label_height = lovis4u.Methods.str_font_size_to_height(label_font_size,
                                                                       self.prms.args["locus_label_id_font_face"])
            else:
                raise lovis4u.Manager.lovis4uError("Locus label position parameter should be either 'bottom' "
                                                   "or 'left'.")
            self.prms.args["locus_label_height"] = label_height
            self.prms.args["locus_label_font_size"] = label_font_size
            max_id_string_width = max([pdfmetrics.stringWidth(i.seq_id, self.prms.args["locus_label_id_font_face"],
                                                              self.prms.args["locus_label_font_size"]) for i in
                                       loci.loci])
            if self.prms.args["locus_label_style"] != "id":
                max_descr_string_width = \
                    max([pdfmetrics.stringWidth(i, self.prms.args["locus_label_description_font_face"],
                                                self.prms.args["locus_label_font_size"])
                         for i in annotated_descriptions])

            if self.prms.args["locus_label_style"] == "full":
                max_label_string_width = max(max_id_string_width, max_descr_string_width)
            elif self.prms.args["locus_label_style"] == "id":
                max_label_string_width = max_id_string_width
            elif self.prms.args["locus_label_style"] == "description":
                max_label_string_width = max_descr_string_width
            loci_lengths = loci.get_loci_lengths_and_n_of_regions()
            self.layout["total_nt_width"] = max(i[0] for i in loci_lengths)
            if self.prms.args["figure_width"]:
                if self.prms.args["locus_label_position"] == "left":
                    figure_width_for_loci = self.prms.args["figure_width"] * mm - max_label_string_width - \
                                            2 * self.prms.args["margin"] * mm \
                                            - self.prms.args["gap_after_locus_label"] * mm
                elif self.prms.args["locus_label_position"] == "bottom":
                    figure_width_for_loci = self.prms.args["figure_width"] * mm - 2 * self.prms.args["margin"] * mm
                self.prms.args["mm_per_nt"] = mm * figure_width_for_loci / self.layout["total_nt_width"]
            else:
                width_per_nt = self.prms.args["mm_per_nt"] * mm
                figure_width_for_loci = width_per_nt * self.layout["total_nt_width"]
                if figure_width_for_loci < 8 * cm:
                    width_per_nt = 8 * cm / self.layout["total_nt_width"]
                self.prms.args["mm_per_nt"] = width_per_nt / mm
            self.layout["width_per_nt"] = self.prms.args["mm_per_nt"] / mm
            self.layout["x_gap_between_regions"] = self.prms.args["gap_between_regions"] * mm
            each_loci_region_width = [
                (i[0] * self.layout["width_per_nt"]) + (i[1] * self.layout["x_gap_between_regions"])
                for i in loci_lengths]
            max_loci_region_length = max(each_loci_region_width)
            self.layout["locus_label_left_border"] = self.prms.args["margin"] * mm
            if self.prms.args["locus_label_position"] == "left":
                self.layout["locus_label_right_border"] = self.layout[
                                                              "locus_label_left_border"] + max_label_string_width
                self.layout["loci_tracks_left_border"] = self.layout["locus_label_right_border"] + \
                                                         self.prms.args["gap_after_locus_label"] * mm
                self.layout["loci_tracks_right_border"] = self.layout["loci_tracks_left_border"] + \
                                                          max_loci_region_length
            elif self.prms.args["locus_label_position"] == "bottom":
                self.layout["loci_tracks_left_border"] = self.prms.args["margin"] * mm
                self.layout["loci_tracks_right_border"] = self.layout["loci_tracks_left_border"] + \
                                                          max_loci_region_length
            self.layout["figure_width"] = self.layout["loci_tracks_right_border"] + self.prms.args["margin"] * mm
            self.layout["figure_height"] = self.prms.args["margin"] * mm
            return None
        except Exception as error:
            raise lovis4u.Manager.lovis4uError("Unable to define layout from loci data.") from error

    def add_loci_tracks(self, loci) -> None:
        """Add loci tracks to your canvas.

        Arguments:
            loci (lovis4u.DataProcessing.Loci): Loci object with information about sequences and features.

        Returns:
            None

        """
        try:
            for locus in loci.loci:
                locus_loader = LocusLoader(self.prms)
                locus_loader.prepare_track_specific_data(locus, self.layout.copy())
                locus_track_height = locus_loader.calculate_track_height()
                self.layout["figure_height"] += locus_track_height + self.prms.args["gap"] * mm
                locus_track = locus_loader.create_track()
                self.tracks.append(locus_track)
            if self.prms.args["verbose"]:
                print(f"⦿ {len(loci.loci)} loci tracks were added to the canvas", file=sys.stdout)
            return None
        except Exception as error:
            raise lovis4u.Manager.lovis4uError("Unable to add loci tracks to the canvas.") from error

    def add_categories_colour_legend_track(self, loci) -> None:
        """Add categories colour legend tracks to your canvas.

        Arguments:
            loci (lovis4u.DataProcessing.Loci): Loci object with information about sequences and features.

        Returns:
            None

        """
        try:
            colour_legend_loader = CategoriesColorLegendLoader(self.prms)
            colour_legend_loader.prepare_track_specific_data(self.layout.copy(), loci)
            colour_legend_track_height = colour_legend_loader.calculate_track_height()
            if colour_legend_track_height != 0:
                self.layout["figure_height"] += colour_legend_track_height + self.prms.args["gap"] * mm
            colour_legend_track = colour_legend_loader.create_track()
            if isinstance(colour_legend_track, lovis4u.Drawing.ColorLegendVis):
                self.tracks.append(colour_legend_track)
                if self.prms.args["verbose"]:
                    print(f"⦿ Categories colour legend track was added to the canvas", file=sys.stdout)
                return None
        except Exception as error:
            raise lovis4u.Manager.lovis4uError("Unable to add categories colour legend track to the canvas.") from error

    def add_scale_line_track(self) -> None:
        """Add scale line tracks to your canvas.

        Returns:
            None

        """
        try:
            scale_loader = ScaleLoader(self.prms)
            scale_loader.prepare_track_specific_data(self.layout.copy())
            scale_track_height = scale_loader.calculate_track_height()
            self.layout["figure_height"] += scale_track_height + self.prms.args["gap"] * mm
            scale_track = scale_loader.create_track()
            self.tracks.append(scale_track)
            if self.prms.args["verbose"]:
                print(f"⦿ Scale line track was added to the canvas", file=sys.stdout)
        except Exception as error:
            raise lovis4u.Manager.lovis4uError("Unable to add scale line track to the canvas.") from error

    def add_homology_track(self) -> None:
        """Add homology track to your canvas.

        You should add this track after you added loci tracks.

        Returns:
            None

        """
        try:
            loci_tracks = [i for i in self.tracks.copy() if isinstance(i, lovis4u.Drawing.LocusVis)]
            if not loci_tracks:
                raise lovis4u.Manager.lovis4uError("Unable to create homology track if no loci track was added.")
            for lt in loci_tracks:
                lt.track_data["clean_features_coordinates"] = True
            self.cross_tracks.append(lovis4u.Drawing.HomologyTrack(self.layout, loci_tracks, self.prms))
            if self.prms.args["verbose"]:
                print(f"⦿ Homology track was added to the canvas", file=sys.stdout)
            return None
        except Exception as error:
            raise lovis4u.Manager.lovis4uError("Unable to add scale line track to the canvas.") from error

    def plot(self, filename: str) -> None:
        """Plot all added tracks and save the plot as pdf.

        Arguments:
            filename (str): filename for the output pdf.

        Returns:
            None

        """
        try:
            file_path = os.path.join(self.prms.args["output_dir"], filename)
            self.layout["figure_height"] += (self.prms.args["margin"] - self.prms.args["gap"]) * mm
            plot = Canvas(file_path, self.layout["figure_width"], self.layout["figure_height"], self.prms)

            for cross_track in self.cross_tracks:
                cross_track.draw(plot.canvas)

            current_y_coordinate = self.layout["figure_height"] - self.prms.args["margin"] * mm
            for track in self.tracks:
                track.layout["current_y_coordinate"] = current_y_coordinate
                track.layout["figure_height"] = self.layout["figure_height"]
                track.draw(plot.canvas)
                current_y_coordinate -= track.track_data["track_height"] + self.prms.args["gap"] * mm
            plot.save()
            if self.prms.args["verbose"]:
                print(f"⦿ lovis4u plot was saved as: {file_path}", file=sys.stdout)
            return None
        except Exception as error:
            raise lovis4u.Manager.lovis4uError("Unable to plot the canvas and save the figure.") from error

__init__(parameters)

Create a CanvasManager object.

Parameters:

  • parameters (Parameters) –

    Parameters' class object that holds config and cmd arguments.

Source code in lovis4u/Manager.py
def __init__(self, parameters):
    """Create a CanvasManager object.

    Arguments:
        parameters (Parameters): Parameters' class object that holds config and cmd arguments.

    """
    self.layout = dict()
    self.tracks = []
    self.cross_tracks = []
    self.prms = parameters

add_categories_colour_legend_track(loci)

Add categories colour legend tracks to your canvas.

Parameters:

  • loci (Loci) –

    Loci object with information about sequences and features.

Returns:

  • None

    None

Source code in lovis4u/Manager.py
def add_categories_colour_legend_track(self, loci) -> None:
    """Add categories colour legend tracks to your canvas.

    Arguments:
        loci (lovis4u.DataProcessing.Loci): Loci object with information about sequences and features.

    Returns:
        None

    """
    try:
        colour_legend_loader = CategoriesColorLegendLoader(self.prms)
        colour_legend_loader.prepare_track_specific_data(self.layout.copy(), loci)
        colour_legend_track_height = colour_legend_loader.calculate_track_height()
        if colour_legend_track_height != 0:
            self.layout["figure_height"] += colour_legend_track_height + self.prms.args["gap"] * mm
        colour_legend_track = colour_legend_loader.create_track()
        if isinstance(colour_legend_track, lovis4u.Drawing.ColorLegendVis):
            self.tracks.append(colour_legend_track)
            if self.prms.args["verbose"]:
                print(f"⦿ Categories colour legend track was added to the canvas", file=sys.stdout)
            return None
    except Exception as error:
        raise lovis4u.Manager.lovis4uError("Unable to add categories colour legend track to the canvas.") from error

add_homology_track()

Add homology track to your canvas.

You should add this track after you added loci tracks.

Returns:

  • None

    None

Source code in lovis4u/Manager.py
def add_homology_track(self) -> None:
    """Add homology track to your canvas.

    You should add this track after you added loci tracks.

    Returns:
        None

    """
    try:
        loci_tracks = [i for i in self.tracks.copy() if isinstance(i, lovis4u.Drawing.LocusVis)]
        if not loci_tracks:
            raise lovis4u.Manager.lovis4uError("Unable to create homology track if no loci track was added.")
        for lt in loci_tracks:
            lt.track_data["clean_features_coordinates"] = True
        self.cross_tracks.append(lovis4u.Drawing.HomologyTrack(self.layout, loci_tracks, self.prms))
        if self.prms.args["verbose"]:
            print(f"⦿ Homology track was added to the canvas", file=sys.stdout)
        return None
    except Exception as error:
        raise lovis4u.Manager.lovis4uError("Unable to add scale line track to the canvas.") from error

add_loci_tracks(loci)

Add loci tracks to your canvas.

Parameters:

  • loci (Loci) –

    Loci object with information about sequences and features.

Returns:

  • None

    None

Source code in lovis4u/Manager.py
def add_loci_tracks(self, loci) -> None:
    """Add loci tracks to your canvas.

    Arguments:
        loci (lovis4u.DataProcessing.Loci): Loci object with information about sequences and features.

    Returns:
        None

    """
    try:
        for locus in loci.loci:
            locus_loader = LocusLoader(self.prms)
            locus_loader.prepare_track_specific_data(locus, self.layout.copy())
            locus_track_height = locus_loader.calculate_track_height()
            self.layout["figure_height"] += locus_track_height + self.prms.args["gap"] * mm
            locus_track = locus_loader.create_track()
            self.tracks.append(locus_track)
        if self.prms.args["verbose"]:
            print(f"⦿ {len(loci.loci)} loci tracks were added to the canvas", file=sys.stdout)
        return None
    except Exception as error:
        raise lovis4u.Manager.lovis4uError("Unable to add loci tracks to the canvas.") from error

add_scale_line_track()

Add scale line tracks to your canvas.

Returns:

  • None

    None

Source code in lovis4u/Manager.py
def add_scale_line_track(self) -> None:
    """Add scale line tracks to your canvas.

    Returns:
        None

    """
    try:
        scale_loader = ScaleLoader(self.prms)
        scale_loader.prepare_track_specific_data(self.layout.copy())
        scale_track_height = scale_loader.calculate_track_height()
        self.layout["figure_height"] += scale_track_height + self.prms.args["gap"] * mm
        scale_track = scale_loader.create_track()
        self.tracks.append(scale_track)
        if self.prms.args["verbose"]:
            print(f"⦿ Scale line track was added to the canvas", file=sys.stdout)
    except Exception as error:
        raise lovis4u.Manager.lovis4uError("Unable to add scale line track to the canvas.") from error

define_layout(loci)

Define canvas' layout based on input loci.

Parameters:

  • loci (Loci) –

    Loci object with information about sequences and features.

Returns:

  • None

    None

Source code in lovis4u/Manager.py
def define_layout(self, loci) -> None:
    """Define canvas' layout based on input loci.

    Arguments:
        loci (lovis4u.DataProcessing.Loci): Loci object with information about sequences and features.

    Returns:
        None

    """
    try:
        annotated_descriptions = [i.description for i in loci.loci if i.description]
        if not annotated_descriptions and self.prms.args["locus_label_style"] != "id":
            if self.prms.args["verbose"]:
                print("○ Warning message: the annotation lacks description. Locus label style is "
                      "changed to 'id'")
            self.prms.args["locus_label_style"] = "id"
        if self.prms.args["locus_label_position"] == "left":
            if self.prms.args["locus_label_style"] == "full":
                label_height = self.prms.args["feature_height"] * mm * 0.4
            else:
                label_height = min(1, self.prms.args["locus_label_size"]) * self.prms.args["feature_height"] * mm
            label_font_size = lovis4u.Methods.str_height_to_size(label_height,
                                                                 self.prms.args["locus_label_id_font_face"])
        elif self.prms.args["locus_label_position"] == "bottom":
            label_font_size = self.prms.args["bottom_locus_label_font_size"]
            label_height = lovis4u.Methods.str_font_size_to_height(label_font_size,
                                                                   self.prms.args["locus_label_id_font_face"])
        else:
            raise lovis4u.Manager.lovis4uError("Locus label position parameter should be either 'bottom' "
                                               "or 'left'.")
        self.prms.args["locus_label_height"] = label_height
        self.prms.args["locus_label_font_size"] = label_font_size
        max_id_string_width = max([pdfmetrics.stringWidth(i.seq_id, self.prms.args["locus_label_id_font_face"],
                                                          self.prms.args["locus_label_font_size"]) for i in
                                   loci.loci])
        if self.prms.args["locus_label_style"] != "id":
            max_descr_string_width = \
                max([pdfmetrics.stringWidth(i, self.prms.args["locus_label_description_font_face"],
                                            self.prms.args["locus_label_font_size"])
                     for i in annotated_descriptions])

        if self.prms.args["locus_label_style"] == "full":
            max_label_string_width = max(max_id_string_width, max_descr_string_width)
        elif self.prms.args["locus_label_style"] == "id":
            max_label_string_width = max_id_string_width
        elif self.prms.args["locus_label_style"] == "description":
            max_label_string_width = max_descr_string_width
        loci_lengths = loci.get_loci_lengths_and_n_of_regions()
        self.layout["total_nt_width"] = max(i[0] for i in loci_lengths)
        if self.prms.args["figure_width"]:
            if self.prms.args["locus_label_position"] == "left":
                figure_width_for_loci = self.prms.args["figure_width"] * mm - max_label_string_width - \
                                        2 * self.prms.args["margin"] * mm \
                                        - self.prms.args["gap_after_locus_label"] * mm
            elif self.prms.args["locus_label_position"] == "bottom":
                figure_width_for_loci = self.prms.args["figure_width"] * mm - 2 * self.prms.args["margin"] * mm
            self.prms.args["mm_per_nt"] = mm * figure_width_for_loci / self.layout["total_nt_width"]
        else:
            width_per_nt = self.prms.args["mm_per_nt"] * mm
            figure_width_for_loci = width_per_nt * self.layout["total_nt_width"]
            if figure_width_for_loci < 8 * cm:
                width_per_nt = 8 * cm / self.layout["total_nt_width"]
            self.prms.args["mm_per_nt"] = width_per_nt / mm
        self.layout["width_per_nt"] = self.prms.args["mm_per_nt"] / mm
        self.layout["x_gap_between_regions"] = self.prms.args["gap_between_regions"] * mm
        each_loci_region_width = [
            (i[0] * self.layout["width_per_nt"]) + (i[1] * self.layout["x_gap_between_regions"])
            for i in loci_lengths]
        max_loci_region_length = max(each_loci_region_width)
        self.layout["locus_label_left_border"] = self.prms.args["margin"] * mm
        if self.prms.args["locus_label_position"] == "left":
            self.layout["locus_label_right_border"] = self.layout[
                                                          "locus_label_left_border"] + max_label_string_width
            self.layout["loci_tracks_left_border"] = self.layout["locus_label_right_border"] + \
                                                     self.prms.args["gap_after_locus_label"] * mm
            self.layout["loci_tracks_right_border"] = self.layout["loci_tracks_left_border"] + \
                                                      max_loci_region_length
        elif self.prms.args["locus_label_position"] == "bottom":
            self.layout["loci_tracks_left_border"] = self.prms.args["margin"] * mm
            self.layout["loci_tracks_right_border"] = self.layout["loci_tracks_left_border"] + \
                                                      max_loci_region_length
        self.layout["figure_width"] = self.layout["loci_tracks_right_border"] + self.prms.args["margin"] * mm
        self.layout["figure_height"] = self.prms.args["margin"] * mm
        return None
    except Exception as error:
        raise lovis4u.Manager.lovis4uError("Unable to define layout from loci data.") from error

plot(filename)

Plot all added tracks and save the plot as pdf.

Parameters:

  • filename (str) –

    filename for the output pdf.

Returns:

  • None

    None

Source code in lovis4u/Manager.py
def plot(self, filename: str) -> None:
    """Plot all added tracks and save the plot as pdf.

    Arguments:
        filename (str): filename for the output pdf.

    Returns:
        None

    """
    try:
        file_path = os.path.join(self.prms.args["output_dir"], filename)
        self.layout["figure_height"] += (self.prms.args["margin"] - self.prms.args["gap"]) * mm
        plot = Canvas(file_path, self.layout["figure_width"], self.layout["figure_height"], self.prms)

        for cross_track in self.cross_tracks:
            cross_track.draw(plot.canvas)

        current_y_coordinate = self.layout["figure_height"] - self.prms.args["margin"] * mm
        for track in self.tracks:
            track.layout["current_y_coordinate"] = current_y_coordinate
            track.layout["figure_height"] = self.layout["figure_height"]
            track.draw(plot.canvas)
            current_y_coordinate -= track.track_data["track_height"] + self.prms.args["gap"] * mm
        plot.save()
        if self.prms.args["verbose"]:
            print(f"⦿ lovis4u plot was saved as: {file_path}", file=sys.stdout)
        return None
    except Exception as error:
        raise lovis4u.Manager.lovis4uError("Unable to plot the canvas and save the figure.") from error

CategoriesColorLegendLoader

Bases: Loader

A CategoriesColorLegendLoader object prepares data for a categories colour legend track Drawing object.

Attributes:

  • prms (Parameters) –

    Parameters' class object.

  • layout (dict) –

    Layout built by CanvasManager's define_layout() method.

  • track_data (dict) –

    Track specific data that will be sent to the Drawing module.

Source code in lovis4u/Manager.py
class CategoriesColorLegendLoader(Loader):
    """A CategoriesColorLegendLoader object prepares data for a categories colour legend track Drawing object.

    Attributes:
        prms (Parameters): Parameters' class object.
        layout (dict): Layout built by CanvasManager's define_layout() method.
        track_data (dict): Track specific data that will be sent to the Drawing module.

    """

    def __init__(self, parameters):
        """Create a CategoriesColorLegendLoader object.

        Arguments:
            parameters (Parameters): Parameters' class object that holds config and cmd arguments.

        """
        super().__init__(parameters)

    def prepare_track_specific_data(self, layout: dict, loci) -> None:
        """Prepare ScaleLoader specific data.

        Attributes:
            layout (dict): Layout built by CanvasManager's define_layout() method.
            loci (lovis4u.DataProcessing.Loci): Loci object with information about sequences and features.

        Returns:
            None

        """
        try:
            self.layout = layout
            self.track_data = dict()
            label_height = lovis4u.Methods.str_font_size_to_height(
                self.prms.args["colour_legend_label_font_size"], self.prms.args["colour_legend_font_face"])
            line_width = self.prms.args["colour_legend_line_width"] * mm
            line_gap = 0.4 * label_height
            self.track_data["line_width"] = line_width
            self.track_data["colour_legend_label_size"] = self.prms.args["colour_legend_label_font_size"]
            left_border = self.layout["loci_tracks_left_border"]
            right_border = self.layout["loci_tracks_right_border"]
            x_gap = pdfmetrics.stringWidth(" " * 5, self.prms.args["colour_legend_font_face"],
                                           self.track_data["colour_legend_label_size"])
            y_gap = 0.5 * label_height
            colour_dict = dict()
            for locus in loci.loci:
                colour_dict.update(locus.category_colours)
            self.track_data["labels"] = []
            current_x = left_border
            n_of_rows = 0
            current_y = - (label_height + line_gap + line_width)
            for label, colour in colour_dict.items():
                label_dict = dict()
                label_width = pdfmetrics.stringWidth(label, self.prms.args["colour_legend_font_face"],
                                                     self.track_data["colour_legend_label_size"])
                label_end = current_x + label_width
                if label_end > right_border:
                    current_x = left_border
                    current_y -= (label_height + line_gap + line_width + y_gap)
                    n_of_rows += 1
                label_dict["label_x"] = current_x
                label_dict["label_width"] = label_width
                label_dict["relative_y"] = current_y
                label_dict["relative_y_text"] = current_y + line_gap + line_width
                label_dict["colour"] = colour
                label_dict["label"] = label
                self.track_data["labels"].append(label_dict)
                current_x = label_dict["label_x"] + label_width + x_gap
            return None
        except Exception as error:
            raise lovis4u.Manager.lovis4uError("Unable to prepare categories colour legend track specific data.") \
                from error

    def calculate_track_height(self):
        """Calculate track height to define layout.

        Returns:
            float: track height.

        """
        try:
            if self.track_data["labels"]:
                min_relative_y = min([i["relative_y"] for i in self.track_data["labels"]])
                track_height = abs(min_relative_y)
            else:
                track_height = 0
            self.track_data["track_height"] = track_height
            return track_height
        except Exception as error:
            raise lovis4u.Manager.lovis4uError("Unable to calculate a categories colour legend track height.") \
                from error

    def create_track(self):
        """Initialise a ColorLegendVis track object.

        Returns:
            lovis4u.Drawing.LocusVis | None: visualisation track.

        """
        try:
            if self.track_data["labels"]:
                return lovis4u.Drawing.ColorLegendVis(self.layout, self.track_data, self.prms)
            else:
                if self.prms.args["verbose"]:
                    print("○ Warning message: Category colours legend track cannot be created since there is no "
                          "categories.", file=sys.stdout)
                return None
        except Exception as error:
            raise lovis4u.Manager.lovis4uError("Unable to create a categories colour legend track track object.") \
                from error

__init__(parameters)

Create a CategoriesColorLegendLoader object.

Parameters:

  • parameters (Parameters) –

    Parameters' class object that holds config and cmd arguments.

Source code in lovis4u/Manager.py
def __init__(self, parameters):
    """Create a CategoriesColorLegendLoader object.

    Arguments:
        parameters (Parameters): Parameters' class object that holds config and cmd arguments.

    """
    super().__init__(parameters)

calculate_track_height()

Calculate track height to define layout.

Returns:

  • float

    track height.

Source code in lovis4u/Manager.py
def calculate_track_height(self):
    """Calculate track height to define layout.

    Returns:
        float: track height.

    """
    try:
        if self.track_data["labels"]:
            min_relative_y = min([i["relative_y"] for i in self.track_data["labels"]])
            track_height = abs(min_relative_y)
        else:
            track_height = 0
        self.track_data["track_height"] = track_height
        return track_height
    except Exception as error:
        raise lovis4u.Manager.lovis4uError("Unable to calculate a categories colour legend track height.") \
            from error

create_track()

Initialise a ColorLegendVis track object.

Returns:

  • lovis4u.Drawing.LocusVis | None: visualisation track.

Source code in lovis4u/Manager.py
def create_track(self):
    """Initialise a ColorLegendVis track object.

    Returns:
        lovis4u.Drawing.LocusVis | None: visualisation track.

    """
    try:
        if self.track_data["labels"]:
            return lovis4u.Drawing.ColorLegendVis(self.layout, self.track_data, self.prms)
        else:
            if self.prms.args["verbose"]:
                print("○ Warning message: Category colours legend track cannot be created since there is no "
                      "categories.", file=sys.stdout)
            return None
    except Exception as error:
        raise lovis4u.Manager.lovis4uError("Unable to create a categories colour legend track track object.") \
            from error

prepare_track_specific_data(layout, loci)

Prepare ScaleLoader specific data.

Attributes:

  • layout (dict) –

    Layout built by CanvasManager's define_layout() method.

  • loci (Loci) –

    Loci object with information about sequences and features.

Returns:

  • None

    None

Source code in lovis4u/Manager.py
def prepare_track_specific_data(self, layout: dict, loci) -> None:
    """Prepare ScaleLoader specific data.

    Attributes:
        layout (dict): Layout built by CanvasManager's define_layout() method.
        loci (lovis4u.DataProcessing.Loci): Loci object with information about sequences and features.

    Returns:
        None

    """
    try:
        self.layout = layout
        self.track_data = dict()
        label_height = lovis4u.Methods.str_font_size_to_height(
            self.prms.args["colour_legend_label_font_size"], self.prms.args["colour_legend_font_face"])
        line_width = self.prms.args["colour_legend_line_width"] * mm
        line_gap = 0.4 * label_height
        self.track_data["line_width"] = line_width
        self.track_data["colour_legend_label_size"] = self.prms.args["colour_legend_label_font_size"]
        left_border = self.layout["loci_tracks_left_border"]
        right_border = self.layout["loci_tracks_right_border"]
        x_gap = pdfmetrics.stringWidth(" " * 5, self.prms.args["colour_legend_font_face"],
                                       self.track_data["colour_legend_label_size"])
        y_gap = 0.5 * label_height
        colour_dict = dict()
        for locus in loci.loci:
            colour_dict.update(locus.category_colours)
        self.track_data["labels"] = []
        current_x = left_border
        n_of_rows = 0
        current_y = - (label_height + line_gap + line_width)
        for label, colour in colour_dict.items():
            label_dict = dict()
            label_width = pdfmetrics.stringWidth(label, self.prms.args["colour_legend_font_face"],
                                                 self.track_data["colour_legend_label_size"])
            label_end = current_x + label_width
            if label_end > right_border:
                current_x = left_border
                current_y -= (label_height + line_gap + line_width + y_gap)
                n_of_rows += 1
            label_dict["label_x"] = current_x
            label_dict["label_width"] = label_width
            label_dict["relative_y"] = current_y
            label_dict["relative_y_text"] = current_y + line_gap + line_width
            label_dict["colour"] = colour
            label_dict["label"] = label
            self.track_data["labels"].append(label_dict)
            current_x = label_dict["label_x"] + label_width + x_gap
        return None
    except Exception as error:
        raise lovis4u.Manager.lovis4uError("Unable to prepare categories colour legend track specific data.") \
            from error

Loader

Parent class for tracks loaders.

Attributes:

  • prms (Parameters) –

    Parameters' class object.

  • layout (dict) –

    Layout built by CanvasManager's define_layout() method.

  • track_data (dict) –

    Track specific data that will be sent to the Drawing module.

Source code in lovis4u/Manager.py
class Loader:
    """Parent class for tracks loaders.

    Attributes:
        prms (Parameters): Parameters' class object.
        layout (dict): Layout built by CanvasManager's define_layout() method.
        track_data (dict): Track specific data that will be sent to the Drawing module.

    """

    def __init__(self, parameters: Parameters):
        """Parent's constructor for creating a Loader class object.

        Arguments:
            parameters (Parameters): Parameters' class object that holds config and cmd arguments.

        """
        self.prms = parameters
        self.layout = None
        self.track_data = None

    def prepare_track_specific_data(self) -> None:
        """Empty parent's method for data preparation.

        Returns:
            None

        """
        pass

    def calculate_track_height(self) -> None:
        """Empty parent's method for track height calculation.

        Returns:
            None

        """
        pass

    def create_track(self) -> None:
        """Empty parent's method for track initialisation.

        Returns:
            None

        """
        pass

__init__(parameters)

Parent's constructor for creating a Loader class object.

Parameters:

  • parameters (Parameters) –

    Parameters' class object that holds config and cmd arguments.

Source code in lovis4u/Manager.py
def __init__(self, parameters: Parameters):
    """Parent's constructor for creating a Loader class object.

    Arguments:
        parameters (Parameters): Parameters' class object that holds config and cmd arguments.

    """
    self.prms = parameters
    self.layout = None
    self.track_data = None

calculate_track_height()

Empty parent's method for track height calculation.

Returns:

  • None

    None

Source code in lovis4u/Manager.py
def calculate_track_height(self) -> None:
    """Empty parent's method for track height calculation.

    Returns:
        None

    """
    pass

create_track()

Empty parent's method for track initialisation.

Returns:

  • None

    None

Source code in lovis4u/Manager.py
def create_track(self) -> None:
    """Empty parent's method for track initialisation.

    Returns:
        None

    """
    pass

prepare_track_specific_data()

Empty parent's method for data preparation.

Returns:

  • None

    None

Source code in lovis4u/Manager.py
def prepare_track_specific_data(self) -> None:
    """Empty parent's method for data preparation.

    Returns:
        None

    """
    pass

LocusLoader

Bases: Loader

A LocusLoader object prepares data for a Locus track Drawing object.

Attributes:

  • prms (Parameters) –

    Parameters' class object.

  • layout (dict) –

    Layout built by CanvasManager's define_layout() method.

  • track_data (dict) –

    Track specific data that will be sent to the Drawing module.

Source code in lovis4u/Manager.py
class LocusLoader(Loader):
    """A LocusLoader object prepares data for a Locus track Drawing object.

    Attributes:
        prms (Parameters): Parameters' class object.
        layout (dict): Layout built by CanvasManager's define_layout() method.
        track_data (dict): Track specific data that will be sent to the Drawing module.


    """

    def __init__(self, parameters):
        """Create a LocusLoader object.

        Arguments:
            parameters (Parameters): Parameters' class object that holds config and cmd arguments.

        """
        super().__init__(parameters)

    def prepare_track_specific_data(self, locus, layout: dict) -> None:
        """Prepare LocusLoader specific data.

        Attributes:
            locus (lovis4u.DataProcessing.Locus): corresponding locus object.
            layout (dict): Layout built by CanvasManager's define_layout() method.

        Returns:
            None

        """
        try:
            self.layout = layout
            layout["inverse_y_coordinate"] = layout["figure_height"]
            track_data = dict()
            self.track_data = track_data
            track_data["locus_id_width"] = pdfmetrics.stringWidth(locus.seq_id,
                                                                  self.prms.args["locus_label_id_font_face"],
                                                                  self.prms.args["locus_label_font_size"])
            two_space_width = pdfmetrics.stringWidth("  ", self.prms.args["locus_label_id_font_face"],
                                                     self.prms.args["locus_label_font_size"])
            track_data["two_space_width"] = two_space_width
            track_data["locus_id"] = locus.seq_id
            track_data["locus_description"] = locus.description
            if locus.description:
                track_data["locus_description_width"] = pdfmetrics.stringWidth(locus.description,
                                                                               self.prms.args["locus_label_description"
                                                                                              "_font_face"],
                                                                               self.prms.args["locus_label_font_size"])
            if self.prms.args["locus_label_position"] == "bottom":
                txt_coordinates = []
                for i in range(len(locus.coordinates)):
                    cc = locus.coordinates[i].copy()
                    cc_txt = f"{cc['start']}:{cc['end']}{'(+)' if cc['strand'] == 1 else '(-)'}"
                    if i > 0 and locus.circular:
                        if cc["start"] == 1 and locus.coordinates[i - 1]["end"] == locus.length:
                            txt_coordinates[-1] = f"{locus.coordinates[i - 1]['start']}:{cc['end']}" \
                                                  f"{'(+)' if cc['strand'] == 1 else '(-)'}"
                            continue
                    txt_coordinates.append(cc_txt)
                track_data["text_coordinates"] = ", ".join(txt_coordinates)
                track_data["text_coordinates_width"] = pdfmetrics.stringWidth(locus.description,
                                                                              self.prms.args[
                                                                                  "locus_label_id_font_face"],
                                                                              self.prms.args["locus_label_font_size"])

            track_data["f_label_font_size"] = self.prms.args["feature_label_font_size"]
            track_data["f_label_height"] = lovis4u.Methods.str_font_size_to_height(
                track_data["f_label_font_size"], self.prms.args["feature_label_font_face"])
            track_data["feature_label_gap"] = self.prms.args["feature_label_gap"] * track_data["f_label_height"]

            # Managing features positions and parameters
            track_data["clean_features_coordinates"] = False
            track_data["features"] = []
            features_taken_nt_coordinates = []
            for feature in locus.features:
                features_taken_nt_coordinates.append([feature.start, feature.end])
                if feature.vis_prms["fill_colour"] == "default":
                    feature.vis_prms["fill_colour"] = lovis4u.Methods.get_colour("feature_default_fill_colour",
                                                                                 self.prms)
                if self.prms.args["set_feature_stroke_colour_based_on_fill_colour"] and \
                        feature.vis_prms["stroke_colour"] == "default":
                    scale_l = self.prms.args["feature_stroke_colour_relative_lightness"]
                    feature.vis_prms["stroke_colour"] = lovis4u.Methods.scale_lightness(feature.vis_prms["fill_colour"],
                                                                                        scale_l)
                elif not self.prms.args["set_feature_stroke_colour_based_on_fill_colour"] and \
                        feature.vis_prms["stroke_colour"] == "default":
                    feature.vis_prms["stroke_colour"] = lovis4u.Methods.get_colour("feature_default_stroke_colour",
                                                                                   self.prms)
                f_label_width = 0
                if feature.vis_prms["show_label"] and feature.vis_prms["label"]:
                    f_label_width = pdfmetrics.stringWidth(feature.vis_prms["label"],
                                                           self.prms.args["feature_label_font_face"],
                                                           track_data["f_label_font_size"])
                feature_vis_data = feature.vis_prms
                feature_vis_data["coordinates"] = lovis4u.Methods.feature_nt_to_x_transform(feature.start, feature.end,
                                                                                            feature.strand, locus,
                                                                                            layout)
                feature_vis_data["label_width"] = f_label_width
                feature_vis_data["feature_width"] = feature_vis_data["coordinates"]["end"] - \
                                                    feature_vis_data["coordinates"]["start"]
                feature_vis_data["group"] = feature.group
                track_data["features"].append(feature_vis_data)
            # Managing category visualisation
            feature_cateregories = set([feature.category for feature in locus.features if feature.category])
            track_data["functions_coordinates"] = None
            if feature_cateregories and locus.category_colours:
                track_data["category_colours"] = locus.category_colours
                track_data["functions_coordinates"] = dict()
                for ff in feature_cateregories:
                    ff_features = [feature for feature in locus.features if feature.category == ff]
                    ff_coordinates = [[f.vis_prms["coordinates"]["start"], f.vis_prms["coordinates"]["end"]] for f in
                                      ff_features]
                    track_data["functions_coordinates"][ff] = ff_coordinates
            # Managing feature labels' positions:
            taken_label_coordinates = collections.defaultdict(list)
            if sum([fvd["show_label"] for fvd in track_data["features"]]) > 0:
                space_width = pdfmetrics.stringWidth(" ", self.prms.args["feature_label_font_face"],
                                                     track_data["f_label_font_size"])
                sorted_features = sorted(track_data["features"], key=lambda x: x["feature_width"] - x["label_width"],
                                         reverse=True)
                for fvd in [fvd for fvd in sorted_features if fvd["label_width"]]:
                    feature_center = fvd["coordinates"]["center"]
                    width_diff = fvd["label_width"] - fvd["feature_width"]
                    left_position = [fvd["coordinates"]["start"] - width_diff, fvd["coordinates"]["end"]]
                    centered_position = [feature_center - fvd["label_width"] / 2,
                                         feature_center + fvd["label_width"] / 2]
                    right_position = [fvd["coordinates"]["start"], fvd["coordinates"]["end"] + width_diff]
                    for pos in [left_position, centered_position, right_position]:
                        overlap = 0
                        if pos[0] < layout["loci_tracks_left_border"]:
                            overlap = layout["loci_tracks_left_border"] - pos[0]
                        if pos[1] > layout["loci_tracks_right_border"]:
                            overlap = layout["loci_tracks_right_border"] - pos[1]
                        if overlap:
                            pos[0] += overlap
                            pos[1] += overlap
                    label_position = centered_position
                    if width_diff > 0:
                        left_pos_overlap = [of for of in track_data["features"] if
                                            (of != fvd and left_position[0] < of["coordinates"]["end"] <
                                             fvd["coordinates"]["end"] and of["show_label"])]
                        right_pos_overlap = [of for of in track_data["features"] if
                                             (of != fvd and right_position[1] > of["coordinates"]["start"] >
                                              fvd["coordinates"]["start"] and of["show_label"])]
                        if left_pos_overlap and not right_pos_overlap:
                            min_n_distance = min([fvd["coordinates"]["start"] - nf["coordinates"]["end"]
                                                  for nf in left_pos_overlap])
                            if min_n_distance < space_width:
                                right_position[0] += space_width + min(0, min_n_distance)
                                right_position[1] += space_width + min(0, min_n_distance)
                            label_position = right_position
                        elif not left_pos_overlap and right_pos_overlap:
                            min_n_distance = min([nf["coordinates"]["start"] - fvd["coordinates"]["end"]
                                                  for nf in right_pos_overlap])
                            label_position = left_position
                            if min_n_distance < space_width and left_position[0] != layout["loci_tracks_left_border"]:
                                left_position[0] -= space_width
                                left_position[1] -= space_width
                            else:
                                label_position = centered_position
                        else:
                            label_position = centered_position
                    fvd["label_position"] = label_position
                    for label_row in range(0, len(track_data["features"])):
                        overlapped = False
                        for taken_coordinate in taken_label_coordinates[label_row]:
                            if taken_coordinate[0] <= label_position[0] <= taken_coordinate[1] or \
                                    taken_coordinate[0] <= label_position[1] <= taken_coordinate[1] or \
                                    label_position[0] <= taken_coordinate[0] <= label_position[1] or \
                                    label_position[0] <= taken_coordinate[1] <= label_position[1]:
                                overlapped = True
                        if not overlapped:
                            fvd["label_row"] = label_row
                            taken_label_coordinates[label_row].append(label_position)
                            break
                    fvd["label_y_bottom"] = track_data["feature_label_gap"] + \
                                            (fvd["label_row"] * track_data["f_label_height"]) + \
                                            (fvd["label_row"] * track_data["feature_label_gap"])
                for fvd in track_data["features"]:
                    if fvd["label_width"]:
                        if fvd["label_row"] > 0:
                            taken_middle_rows = [i for i in range(fvd["label_row"] - 1, -1, -1) if
                                                 any(taken_coordinate[0] <= fvd["coordinates"]["center"] <=
                                                     taken_coordinate[1]
                                                     for taken_coordinate in taken_label_coordinates[i])]
                            label_line_upper = fvd["label_y_bottom"] - track_data["feature_label_gap"] / 2
                            label_line_bottom = track_data["feature_label_gap"] / 2
                            label_line_coordinates = []
                            ll_start = label_line_bottom
                            for tmr in sorted(taken_middle_rows):
                                tmr_start = 0.5 * track_data["feature_label_gap"] + \
                                            (tmr * track_data["f_label_height"]) + \
                                            (tmr * track_data["feature_label_gap"])
                                tmr_end = tmr_start + track_data["f_label_height"] + \
                                          0.5 * track_data["feature_label_gap"]
                                label_line_coordinates.append([ll_start, tmr_start])
                                ll_start = tmr_end
                            label_line_coordinates.append([ll_start, label_line_upper])
                            fvd["label_line_coordinates"] = label_line_coordinates
            track_data["n_label_rows"] = sum([1 for k, v in taken_label_coordinates.items() if v])
            # Managing middle line indicating locus borders
            if self.prms.args["draw_middle_line"]:
                regions_for_middle_line = [[c["start"], c["end"]] for c in locus.coordinates]
                for ftc in features_taken_nt_coordinates:
                    ftcs, ftse = ftc
                    for added_region in regions_for_middle_line:
                        new_regions = []
                        to_remove = False
                        if added_region[0] <= ftcs <= added_region[1]:
                            to_remove = True
                            if ftcs > added_region[0]:
                                new_regions.append([added_region[0], ftcs - 1])
                        if added_region[0] <= ftse <= added_region[1]:
                            to_remove = True
                            if ftse < added_region[1]:
                                new_regions.append([ftse + 1, added_region[1]])
                        if to_remove:
                            regions_for_middle_line.remove(added_region)
                        regions_for_middle_line += new_regions
                middle_line_coordinates = [lovis4u.Methods.region_nt_to_x_transform(rml[0], rml[1], locus, layout)
                                           for rml in regions_for_middle_line]
                track_data["middle_line_coordinates"] = middle_line_coordinates
            track_data["proteome_size"] = len(locus.features)  # to change if we get other features
            # Managing individual x axis
            if self.prms.args["draw_individual_x_axis"]:
                track_data["x_axis_annotation"] = dict()
                self.prms.args["x_axis_ticks_labels_height"] = lovis4u.Methods.str_font_size_to_height(
                    self.prms.args["x_axis_ticks_labels_font_size"],
                    self.prms.args["x_axis_ticks_labels_font_face"]) / mm
                track_data["x_axis_annotation"]["label_size"] = self.prms.args["x_axis_ticks_labels_font_size"]
                axis_regions = []
                axis_tics_coordinates = []
                for coordinate in locus.coordinates:
                    axis_regions.append(lovis4u.Methods.region_nt_to_x_transform(coordinate["start"], coordinate["end"],
                                                                                 locus, layout))
                    current_tics_coordinates = [coordinate["start"], coordinate["end"]]
                    axis_tics_coordinates += current_tics_coordinates
                if len(axis_tics_coordinates) == 2:
                    nt_range = coordinate["end"] - coordinate["start"]
                    axis_tics_coordinates.append(coordinate["start"] + int(0.25 * nt_range))
                    axis_tics_coordinates.append(coordinate["start"] + int(0.5 * nt_range))
                    axis_tics_coordinates.append(coordinate["end"] - int(0.25 * nt_range))

                axis_tics_positions = list(map(lambda x: lovis4u.Methods.nt_to_x_transform(x, locus, layout, "center"),
                                               axis_tics_coordinates))
                sorted_tics = sorted(zip(axis_tics_positions, axis_tics_coordinates))
                sorted_positions, sorted_coordinates = zip(*sorted_tics)
                axis_tics_positions = list(sorted_positions)
                axis_tics_coordinates = list(sorted_coordinates)
                axis_tics_labels = list(map(str, axis_tics_coordinates))
                axis_tics_label_size = self.prms.args["x_axis_ticks_labels_font_size"]
                axis_tics_label_width = list(map(lambda x: pdfmetrics.stringWidth(x,
                                                                                  self.prms.args[
                                                                                      "x_axis_ticks_labels_font_face"],
                                                                                  axis_tics_label_size),
                                                 axis_tics_labels))

                space_width = pdfmetrics.stringWidth(" ", self.prms.args["x_axis_ticks_labels_font_face"],
                                                     axis_tics_label_size)
                tics_labels_coordinates = axis_tics_positions.copy()
                # Shitty, refactor later
                for t_i in range(len(axis_tics_coordinates)):
                    label_width = axis_tics_label_width[t_i]
                    tick_position = axis_tics_positions[t_i]
                    if t_i == 0:
                        tics_labels_coordinates[t_i] += label_width * 0.5
                    elif t_i != len(axis_tics_coordinates) - 1:
                        center_label_coordinates = [tick_position - 0.5 * label_width,
                                                    tick_position + 0.5 * label_width]
                        if center_label_coordinates[0] - space_width <= axis_tics_positions[t_i - 1]:
                            tics_labels_coordinates[t_i] += label_width * 0.5
                            if axis_tics_positions[t_i] - axis_tics_positions[t_i - 1] < space_width:
                                tics_labels_coordinates[t_i] += space_width
                        if center_label_coordinates[1] + space_width >= axis_tics_positions[t_i + 1]:
                            tics_labels_coordinates[t_i] -= label_width * 0.5
                            if axis_tics_positions[t_i + 1] - axis_tics_positions[t_i] < space_width:
                                tics_labels_coordinates[t_i] -= space_width
                    if tics_labels_coordinates[t_i] + label_width * 0.5 > layout["loci_tracks_right_border"]:
                        tics_labels_coordinates[t_i] -= ((tics_labels_coordinates[t_i] + label_width * 0.5) -
                                                         layout["loci_tracks_right_border"])
                track_data["x_axis_annotation"]["axis_regions"] = axis_regions
                track_data["x_axis_annotation"]["axis_tics_position"] = axis_tics_positions
                track_data["x_axis_annotation"]["axis_tics_labels"] = axis_tics_labels
                track_data["x_axis_annotation"]["tics_labels_coordinates"] = tics_labels_coordinates
                return None
        except Exception as error:
            raise lovis4u.Manager.lovis4uError("Unable to prepare Locus specific data.") from error

    def calculate_track_height(self) -> float:
        """Calculate track height to define layout.

        Returns:
            float: track height.

        """
        try:
            track_height = self.prms.args["feature_height"] * mm + \
                           (self.track_data["n_label_rows"] * self.track_data["f_label_height"] * \
                            (1 + self.prms.args["feature_label_gap"]))
            if self.prms.args["draw_individual_x_axis"]:
                track_height += (self.prms.args["feature_bottom_gap"] + self.prms.args["x_axis_ticks_height"] * 1.3 + \
                                 self.prms.args["x_axis_ticks_labels_height"]) * mm
            elif not self.prms.args["draw_individual_x_axis"] and self.track_data["functions_coordinates"]:
                track_height += (self.prms.args["feature_bottom_gap"] + \
                                 self.prms.args["category_annotation_line_width"]) * mm
            if self.prms.args["locus_label_position"] == "bottom":
                track_height += self.prms.args["locus_label_height"] + self.prms.args["feature_bottom_gap"] * mm

            self.track_data["track_height"] = track_height
            return track_height
        except Exception as error:
            raise lovis4u.Manager.lovis4uError("Unable to calculate a locus track height.") from error

    def create_track(self):
        """Initialise a LocusVis track object.

        Returns:
            lovis4u.Drawing.LocusVis: visualisation track.

        """
        try:
            return lovis4u.Drawing.LocusVis(self.layout, self.track_data, self.prms)
        except Exception as error:
            raise lovis4u.Manager.lovis4uError("Unable to create a locus track object.") from error

__init__(parameters)

Create a LocusLoader object.

Parameters:

  • parameters (Parameters) –

    Parameters' class object that holds config and cmd arguments.

Source code in lovis4u/Manager.py
def __init__(self, parameters):
    """Create a LocusLoader object.

    Arguments:
        parameters (Parameters): Parameters' class object that holds config and cmd arguments.

    """
    super().__init__(parameters)

calculate_track_height()

Calculate track height to define layout.

Returns:

  • float ( float ) –

    track height.

Source code in lovis4u/Manager.py
def calculate_track_height(self) -> float:
    """Calculate track height to define layout.

    Returns:
        float: track height.

    """
    try:
        track_height = self.prms.args["feature_height"] * mm + \
                       (self.track_data["n_label_rows"] * self.track_data["f_label_height"] * \
                        (1 + self.prms.args["feature_label_gap"]))
        if self.prms.args["draw_individual_x_axis"]:
            track_height += (self.prms.args["feature_bottom_gap"] + self.prms.args["x_axis_ticks_height"] * 1.3 + \
                             self.prms.args["x_axis_ticks_labels_height"]) * mm
        elif not self.prms.args["draw_individual_x_axis"] and self.track_data["functions_coordinates"]:
            track_height += (self.prms.args["feature_bottom_gap"] + \
                             self.prms.args["category_annotation_line_width"]) * mm
        if self.prms.args["locus_label_position"] == "bottom":
            track_height += self.prms.args["locus_label_height"] + self.prms.args["feature_bottom_gap"] * mm

        self.track_data["track_height"] = track_height
        return track_height
    except Exception as error:
        raise lovis4u.Manager.lovis4uError("Unable to calculate a locus track height.") from error

create_track()

Initialise a LocusVis track object.

Returns:

  • lovis4u.Drawing.LocusVis: visualisation track.

Source code in lovis4u/Manager.py
def create_track(self):
    """Initialise a LocusVis track object.

    Returns:
        lovis4u.Drawing.LocusVis: visualisation track.

    """
    try:
        return lovis4u.Drawing.LocusVis(self.layout, self.track_data, self.prms)
    except Exception as error:
        raise lovis4u.Manager.lovis4uError("Unable to create a locus track object.") from error

prepare_track_specific_data(locus, layout)

Prepare LocusLoader specific data.

Attributes:

  • locus (Locus) –

    corresponding locus object.

  • layout (dict) –

    Layout built by CanvasManager's define_layout() method.

Returns:

  • None

    None

Source code in lovis4u/Manager.py
def prepare_track_specific_data(self, locus, layout: dict) -> None:
    """Prepare LocusLoader specific data.

    Attributes:
        locus (lovis4u.DataProcessing.Locus): corresponding locus object.
        layout (dict): Layout built by CanvasManager's define_layout() method.

    Returns:
        None

    """
    try:
        self.layout = layout
        layout["inverse_y_coordinate"] = layout["figure_height"]
        track_data = dict()
        self.track_data = track_data
        track_data["locus_id_width"] = pdfmetrics.stringWidth(locus.seq_id,
                                                              self.prms.args["locus_label_id_font_face"],
                                                              self.prms.args["locus_label_font_size"])
        two_space_width = pdfmetrics.stringWidth("  ", self.prms.args["locus_label_id_font_face"],
                                                 self.prms.args["locus_label_font_size"])
        track_data["two_space_width"] = two_space_width
        track_data["locus_id"] = locus.seq_id
        track_data["locus_description"] = locus.description
        if locus.description:
            track_data["locus_description_width"] = pdfmetrics.stringWidth(locus.description,
                                                                           self.prms.args["locus_label_description"
                                                                                          "_font_face"],
                                                                           self.prms.args["locus_label_font_size"])
        if self.prms.args["locus_label_position"] == "bottom":
            txt_coordinates = []
            for i in range(len(locus.coordinates)):
                cc = locus.coordinates[i].copy()
                cc_txt = f"{cc['start']}:{cc['end']}{'(+)' if cc['strand'] == 1 else '(-)'}"
                if i > 0 and locus.circular:
                    if cc["start"] == 1 and locus.coordinates[i - 1]["end"] == locus.length:
                        txt_coordinates[-1] = f"{locus.coordinates[i - 1]['start']}:{cc['end']}" \
                                              f"{'(+)' if cc['strand'] == 1 else '(-)'}"
                        continue
                txt_coordinates.append(cc_txt)
            track_data["text_coordinates"] = ", ".join(txt_coordinates)
            track_data["text_coordinates_width"] = pdfmetrics.stringWidth(locus.description,
                                                                          self.prms.args[
                                                                              "locus_label_id_font_face"],
                                                                          self.prms.args["locus_label_font_size"])

        track_data["f_label_font_size"] = self.prms.args["feature_label_font_size"]
        track_data["f_label_height"] = lovis4u.Methods.str_font_size_to_height(
            track_data["f_label_font_size"], self.prms.args["feature_label_font_face"])
        track_data["feature_label_gap"] = self.prms.args["feature_label_gap"] * track_data["f_label_height"]

        # Managing features positions and parameters
        track_data["clean_features_coordinates"] = False
        track_data["features"] = []
        features_taken_nt_coordinates = []
        for feature in locus.features:
            features_taken_nt_coordinates.append([feature.start, feature.end])
            if feature.vis_prms["fill_colour"] == "default":
                feature.vis_prms["fill_colour"] = lovis4u.Methods.get_colour("feature_default_fill_colour",
                                                                             self.prms)
            if self.prms.args["set_feature_stroke_colour_based_on_fill_colour"] and \
                    feature.vis_prms["stroke_colour"] == "default":
                scale_l = self.prms.args["feature_stroke_colour_relative_lightness"]
                feature.vis_prms["stroke_colour"] = lovis4u.Methods.scale_lightness(feature.vis_prms["fill_colour"],
                                                                                    scale_l)
            elif not self.prms.args["set_feature_stroke_colour_based_on_fill_colour"] and \
                    feature.vis_prms["stroke_colour"] == "default":
                feature.vis_prms["stroke_colour"] = lovis4u.Methods.get_colour("feature_default_stroke_colour",
                                                                               self.prms)
            f_label_width = 0
            if feature.vis_prms["show_label"] and feature.vis_prms["label"]:
                f_label_width = pdfmetrics.stringWidth(feature.vis_prms["label"],
                                                       self.prms.args["feature_label_font_face"],
                                                       track_data["f_label_font_size"])
            feature_vis_data = feature.vis_prms
            feature_vis_data["coordinates"] = lovis4u.Methods.feature_nt_to_x_transform(feature.start, feature.end,
                                                                                        feature.strand, locus,
                                                                                        layout)
            feature_vis_data["label_width"] = f_label_width
            feature_vis_data["feature_width"] = feature_vis_data["coordinates"]["end"] - \
                                                feature_vis_data["coordinates"]["start"]
            feature_vis_data["group"] = feature.group
            track_data["features"].append(feature_vis_data)
        # Managing category visualisation
        feature_cateregories = set([feature.category for feature in locus.features if feature.category])
        track_data["functions_coordinates"] = None
        if feature_cateregories and locus.category_colours:
            track_data["category_colours"] = locus.category_colours
            track_data["functions_coordinates"] = dict()
            for ff in feature_cateregories:
                ff_features = [feature for feature in locus.features if feature.category == ff]
                ff_coordinates = [[f.vis_prms["coordinates"]["start"], f.vis_prms["coordinates"]["end"]] for f in
                                  ff_features]
                track_data["functions_coordinates"][ff] = ff_coordinates
        # Managing feature labels' positions:
        taken_label_coordinates = collections.defaultdict(list)
        if sum([fvd["show_label"] for fvd in track_data["features"]]) > 0:
            space_width = pdfmetrics.stringWidth(" ", self.prms.args["feature_label_font_face"],
                                                 track_data["f_label_font_size"])
            sorted_features = sorted(track_data["features"], key=lambda x: x["feature_width"] - x["label_width"],
                                     reverse=True)
            for fvd in [fvd for fvd in sorted_features if fvd["label_width"]]:
                feature_center = fvd["coordinates"]["center"]
                width_diff = fvd["label_width"] - fvd["feature_width"]
                left_position = [fvd["coordinates"]["start"] - width_diff, fvd["coordinates"]["end"]]
                centered_position = [feature_center - fvd["label_width"] / 2,
                                     feature_center + fvd["label_width"] / 2]
                right_position = [fvd["coordinates"]["start"], fvd["coordinates"]["end"] + width_diff]
                for pos in [left_position, centered_position, right_position]:
                    overlap = 0
                    if pos[0] < layout["loci_tracks_left_border"]:
                        overlap = layout["loci_tracks_left_border"] - pos[0]
                    if pos[1] > layout["loci_tracks_right_border"]:
                        overlap = layout["loci_tracks_right_border"] - pos[1]
                    if overlap:
                        pos[0] += overlap
                        pos[1] += overlap
                label_position = centered_position
                if width_diff > 0:
                    left_pos_overlap = [of for of in track_data["features"] if
                                        (of != fvd and left_position[0] < of["coordinates"]["end"] <
                                         fvd["coordinates"]["end"] and of["show_label"])]
                    right_pos_overlap = [of for of in track_data["features"] if
                                         (of != fvd and right_position[1] > of["coordinates"]["start"] >
                                          fvd["coordinates"]["start"] and of["show_label"])]
                    if left_pos_overlap and not right_pos_overlap:
                        min_n_distance = min([fvd["coordinates"]["start"] - nf["coordinates"]["end"]
                                              for nf in left_pos_overlap])
                        if min_n_distance < space_width:
                            right_position[0] += space_width + min(0, min_n_distance)
                            right_position[1] += space_width + min(0, min_n_distance)
                        label_position = right_position
                    elif not left_pos_overlap and right_pos_overlap:
                        min_n_distance = min([nf["coordinates"]["start"] - fvd["coordinates"]["end"]
                                              for nf in right_pos_overlap])
                        label_position = left_position
                        if min_n_distance < space_width and left_position[0] != layout["loci_tracks_left_border"]:
                            left_position[0] -= space_width
                            left_position[1] -= space_width
                        else:
                            label_position = centered_position
                    else:
                        label_position = centered_position
                fvd["label_position"] = label_position
                for label_row in range(0, len(track_data["features"])):
                    overlapped = False
                    for taken_coordinate in taken_label_coordinates[label_row]:
                        if taken_coordinate[0] <= label_position[0] <= taken_coordinate[1] or \
                                taken_coordinate[0] <= label_position[1] <= taken_coordinate[1] or \
                                label_position[0] <= taken_coordinate[0] <= label_position[1] or \
                                label_position[0] <= taken_coordinate[1] <= label_position[1]:
                            overlapped = True
                    if not overlapped:
                        fvd["label_row"] = label_row
                        taken_label_coordinates[label_row].append(label_position)
                        break
                fvd["label_y_bottom"] = track_data["feature_label_gap"] + \
                                        (fvd["label_row"] * track_data["f_label_height"]) + \
                                        (fvd["label_row"] * track_data["feature_label_gap"])
            for fvd in track_data["features"]:
                if fvd["label_width"]:
                    if fvd["label_row"] > 0:
                        taken_middle_rows = [i for i in range(fvd["label_row"] - 1, -1, -1) if
                                             any(taken_coordinate[0] <= fvd["coordinates"]["center"] <=
                                                 taken_coordinate[1]
                                                 for taken_coordinate in taken_label_coordinates[i])]
                        label_line_upper = fvd["label_y_bottom"] - track_data["feature_label_gap"] / 2
                        label_line_bottom = track_data["feature_label_gap"] / 2
                        label_line_coordinates = []
                        ll_start = label_line_bottom
                        for tmr in sorted(taken_middle_rows):
                            tmr_start = 0.5 * track_data["feature_label_gap"] + \
                                        (tmr * track_data["f_label_height"]) + \
                                        (tmr * track_data["feature_label_gap"])
                            tmr_end = tmr_start + track_data["f_label_height"] + \
                                      0.5 * track_data["feature_label_gap"]
                            label_line_coordinates.append([ll_start, tmr_start])
                            ll_start = tmr_end
                        label_line_coordinates.append([ll_start, label_line_upper])
                        fvd["label_line_coordinates"] = label_line_coordinates
        track_data["n_label_rows"] = sum([1 for k, v in taken_label_coordinates.items() if v])
        # Managing middle line indicating locus borders
        if self.prms.args["draw_middle_line"]:
            regions_for_middle_line = [[c["start"], c["end"]] for c in locus.coordinates]
            for ftc in features_taken_nt_coordinates:
                ftcs, ftse = ftc
                for added_region in regions_for_middle_line:
                    new_regions = []
                    to_remove = False
                    if added_region[0] <= ftcs <= added_region[1]:
                        to_remove = True
                        if ftcs > added_region[0]:
                            new_regions.append([added_region[0], ftcs - 1])
                    if added_region[0] <= ftse <= added_region[1]:
                        to_remove = True
                        if ftse < added_region[1]:
                            new_regions.append([ftse + 1, added_region[1]])
                    if to_remove:
                        regions_for_middle_line.remove(added_region)
                    regions_for_middle_line += new_regions
            middle_line_coordinates = [lovis4u.Methods.region_nt_to_x_transform(rml[0], rml[1], locus, layout)
                                       for rml in regions_for_middle_line]
            track_data["middle_line_coordinates"] = middle_line_coordinates
        track_data["proteome_size"] = len(locus.features)  # to change if we get other features
        # Managing individual x axis
        if self.prms.args["draw_individual_x_axis"]:
            track_data["x_axis_annotation"] = dict()
            self.prms.args["x_axis_ticks_labels_height"] = lovis4u.Methods.str_font_size_to_height(
                self.prms.args["x_axis_ticks_labels_font_size"],
                self.prms.args["x_axis_ticks_labels_font_face"]) / mm
            track_data["x_axis_annotation"]["label_size"] = self.prms.args["x_axis_ticks_labels_font_size"]
            axis_regions = []
            axis_tics_coordinates = []
            for coordinate in locus.coordinates:
                axis_regions.append(lovis4u.Methods.region_nt_to_x_transform(coordinate["start"], coordinate["end"],
                                                                             locus, layout))
                current_tics_coordinates = [coordinate["start"], coordinate["end"]]
                axis_tics_coordinates += current_tics_coordinates
            if len(axis_tics_coordinates) == 2:
                nt_range = coordinate["end"] - coordinate["start"]
                axis_tics_coordinates.append(coordinate["start"] + int(0.25 * nt_range))
                axis_tics_coordinates.append(coordinate["start"] + int(0.5 * nt_range))
                axis_tics_coordinates.append(coordinate["end"] - int(0.25 * nt_range))

            axis_tics_positions = list(map(lambda x: lovis4u.Methods.nt_to_x_transform(x, locus, layout, "center"),
                                           axis_tics_coordinates))
            sorted_tics = sorted(zip(axis_tics_positions, axis_tics_coordinates))
            sorted_positions, sorted_coordinates = zip(*sorted_tics)
            axis_tics_positions = list(sorted_positions)
            axis_tics_coordinates = list(sorted_coordinates)
            axis_tics_labels = list(map(str, axis_tics_coordinates))
            axis_tics_label_size = self.prms.args["x_axis_ticks_labels_font_size"]
            axis_tics_label_width = list(map(lambda x: pdfmetrics.stringWidth(x,
                                                                              self.prms.args[
                                                                                  "x_axis_ticks_labels_font_face"],
                                                                              axis_tics_label_size),
                                             axis_tics_labels))

            space_width = pdfmetrics.stringWidth(" ", self.prms.args["x_axis_ticks_labels_font_face"],
                                                 axis_tics_label_size)
            tics_labels_coordinates = axis_tics_positions.copy()
            # Shitty, refactor later
            for t_i in range(len(axis_tics_coordinates)):
                label_width = axis_tics_label_width[t_i]
                tick_position = axis_tics_positions[t_i]
                if t_i == 0:
                    tics_labels_coordinates[t_i] += label_width * 0.5
                elif t_i != len(axis_tics_coordinates) - 1:
                    center_label_coordinates = [tick_position - 0.5 * label_width,
                                                tick_position + 0.5 * label_width]
                    if center_label_coordinates[0] - space_width <= axis_tics_positions[t_i - 1]:
                        tics_labels_coordinates[t_i] += label_width * 0.5
                        if axis_tics_positions[t_i] - axis_tics_positions[t_i - 1] < space_width:
                            tics_labels_coordinates[t_i] += space_width
                    if center_label_coordinates[1] + space_width >= axis_tics_positions[t_i + 1]:
                        tics_labels_coordinates[t_i] -= label_width * 0.5
                        if axis_tics_positions[t_i + 1] - axis_tics_positions[t_i] < space_width:
                            tics_labels_coordinates[t_i] -= space_width
                if tics_labels_coordinates[t_i] + label_width * 0.5 > layout["loci_tracks_right_border"]:
                    tics_labels_coordinates[t_i] -= ((tics_labels_coordinates[t_i] + label_width * 0.5) -
                                                     layout["loci_tracks_right_border"])
            track_data["x_axis_annotation"]["axis_regions"] = axis_regions
            track_data["x_axis_annotation"]["axis_tics_position"] = axis_tics_positions
            track_data["x_axis_annotation"]["axis_tics_labels"] = axis_tics_labels
            track_data["x_axis_annotation"]["tics_labels_coordinates"] = tics_labels_coordinates
            return None
    except Exception as error:
        raise lovis4u.Manager.lovis4uError("Unable to prepare Locus specific data.") from error

Parameters

A Parameters object holds and parse command line's and config's arguments.

A Parameters object have to be created in each script since it's used almost by each class of the tool as a mandatory argument.

Attributes:

  • args (dict) –

    dictionary that holds all arguments.

Source code in lovis4u/Manager.py
class Parameters:
    """A Parameters object holds and parse command line's and config's arguments.

    A Parameters object have to be created in each script since it's used almost by each
        class of the tool as a mandatory argument.

    Attributes:
        args (dict): dictionary that holds all arguments.
    """

    def __init__(self):
        """Create a Parameters object.

        """
        self.args = dict(debug=False, verbose=False)
        self.cmd_arguments = dict()

    def parse_cmd_arguments(self) -> None:
        """Parse command-line args

        Returns:
            None

        """
        parser = argparse.ArgumentParser(prog="lovis4u", add_help=False,
                                         usage="lovis4u [-gff gff_folder | -gb gb_folder] [optional args]")
        parser.add_argument("-data", "--data", dest="lovis4u_data", action="store_true")
        parser.add_argument("-linux", "--linux", dest="linux", action="store_true", default=None)
        parser.add_argument("-mac", "--mac", dest="mac", action="store_true", default=None)
        mutually_exclusive_group = parser.add_mutually_exclusive_group()
        mutually_exclusive_group.add_argument("-gff", "--gff", dest="gff", type=str, default=None)
        mutually_exclusive_group.add_argument("-gb", "--gb", dest="gb", type=str, default=None)
        parser.add_argument("-ufid", "--use-filename-as-id", dest="use_filename_as_contig_id", action="store_true",
                            default=None)
        parser.add_argument("-laf", "--locus-annotation-file", dest="locus-annotation", type=str, default=None)
        parser.add_argument("-faf", "--feature-annotation-file", dest="feature-annotation", type=str, default=None)
        parser.add_argument("-mmseqs-off", "--mmseqs-off", dest="mmseqs", action="store_false")
        parser.add_argument("-cl-owp", "--cluster-only-window-proteins", dest="cluster_all_proteins",
                            action="store_false", default=None)
        parser.add_argument("-fv-off", "--find-variable-off", dest="find-variable", action="store_false")
        parser.add_argument("-cl-off", "--clust_loci-off", dest="clust_loci", action="store_false")
        parser.add_argument("-oc", "--one-cluster", dest="one_cluster", action="store_true", default=None)
        parser.add_argument("-reorient_loci", "--reorient_loci", dest="reorient_loci", action="store_true")
        parser.add_argument("-lls", "--locus-label-style", dest="locus_label_style",
                            choices=["id", "description", "full"], default=None)
        parser.add_argument("-llp", "--locus-label-position", dest="locus_label_position",
                            choices=["left", "bottom"], default=None)
        parser.add_argument("-sgc-off", "--set-group-colour-off", dest="set-group-colour", action="store_false")
        parser.add_argument("-sgcf", "--set-group-colour-for", dest="feature_group_types_to_set_colour", nargs="*",
                            type=str, default=None)
        parser.add_argument("-scc", "--set-category-colour", dest="set-category-colour", action="store_true")
        parser.add_argument("-cct", "--category-colour-table", dest="category_colours", type=str, default=None)
        parser.add_argument("-safl", "--show-all-feature-labels", dest="show_all_feature_labels",
                            action="store_true")
        parser.add_argument("-sflf", "--show-feature-label-for", dest="feature_group_types_to_show_label", nargs="*",
                            type=str, default=None)
        parser.add_argument("-sfflf", "--show-first-feature-label-for",
                            dest="feature_group_types_to_show_label_on_first_occurrence", nargs="*",
                            type=str, default=None)
        parser.add_argument("-ifl", "--ignored-feature-labels", dest="feature_labels_to_ignore", nargs="*",
                            type=str, default=None)
        parser.add_argument("-hl", "--homology-links", dest="homology-track", action="store_true")
        parser.add_argument("-slt", "--scale-line-track", dest="draw_scale_line_track", action="store_true",
                            default=None)
        parser.add_argument("-sxa", "--show-x-axis", dest="draw_individual_x_axis", action="store_true", default=None)
        parser.add_argument("-hix", "--hide-x-axis", dest="draw_individual_x_axis", action="store_false", default=None)
        parser.add_argument("-dml", "--draw-middle-line", dest="draw_middle_line", action="store_true", default=None)
        parser.add_argument("-mm-per-nt", "--mm-per-nt", dest="mm_per_nt", type=float, default=None)
        parser.add_argument("-fw", "--figure-width", dest="figure_width", type=float, default=None)
        parser.add_argument("-o", dest="output_dir", type=str, default=None)
        parser.add_argument("--pdf-name", dest="pdf-name", type=str, default="lovis4u.pdf")
        parser.add_argument("-c", dest="config_file", type=str, default="standard")
        parser.add_argument("-v", "--version", action="version", version="%(prog)s 0.0.9.3")
        parser.add_argument("-q", "--quiet", dest="verbose", default=True, action="store_false")
        parser.add_argument("--parsing-debug", "-parsing-debug", dest="parsing_debug", action="store_true")
        parser.add_argument("--debug", "-debug", dest="debug", action="store_true")
        parser.add_argument("-h", "--help", dest="help", action="store_true")
        args = parser.parse_args()
        args = vars(args)
        if len(sys.argv[1:]) == 0:
            args["help"] = True
        if args["lovis4u_data"]:
            lovis4u.Methods.copy_package_data()
            sys.exit()
        if args["linux"]:
            lovis4u.Methods.adjust_paths("linux")
            sys.exit()
        if args["mac"]:
            lovis4u.Methods.adjust_paths("mac")
            sys.exit()
        if args["help"]:
            help_message_path = os.path.join(os.path.dirname(__file__), "lovis4u_data", "help.txt")
            with open(help_message_path, "r") as help_message:
                print(help_message.read(), file=sys.stdout)
                sys.exit()
        if not args["gff"] and not args["gb"]:
            raise lovis4u.Manager.lovis4uError("-gff or -gb parameter with folder path should be provided")
        args_to_keep = ["locus-annotation", "feature-annotation", "gb", "gff"]
        filtered_args = {k: v for k, v in args.items() if v is not None or k in args_to_keep}
        self.cmd_arguments = filtered_args
        return None

    def load_config(self, path: str = "standard") -> None:
        """Load configuration file.

        Arguments
            path (str): path to a config file or name (only standard available at this moment).

        Returns:
            None

        """
        try:
            initial_path = ""
            if path in ["standard", "A4p1", "A4p2", "A4L"]:
                initial_path = path
                path = os.path.join(os.path.dirname(__file__), "lovis4u_data", f"{path}.cfg")
            config = configs.load(path).get_config()
            internal_dir = os.path.dirname(__file__)
            for key in config["root"].keys():
                if isinstance(config["root"][key], str):
                    if config["root"][key] == "None":
                        config["root"][key] = None
                if isinstance(config["root"][key], str) and "{internal}" in config["root"][key]:
                    config["root"][key] = config["root"][key].replace("{internal}",
                                                                      os.path.join(internal_dir, "lovis4u_data"))
            config["root"]["output_dir"] = config["root"]["output_dir"].replace("{current_date}",
                                                                                time.strftime("%Y_%m_%d-%H_%M"))
            keys_to_transform_to_list = ["feature_group_types_to_set_colour", "feature_group_types_to_show_label",
                                         "genbank_id_alternative_source", "feature_labels_to_ignore",
                                         "feature_group_types_to_show_label_on_first_occurrence"]
            for ktl in keys_to_transform_to_list:
                if isinstance(config["root"][ktl], str):
                    if config["root"][ktl] != "None":
                        config["root"][ktl] = [config["root"][ktl]]
                    else:
                        config["root"][ktl] = []
            self.args.update(config["root"])
            self.load_palette()
            self.load_fonts()
            if self.cmd_arguments:
                self.args.update(self.cmd_arguments)

            if os.path.exists(self.args["output_dir"]):
                if self.args["verbose"]:
                    if initial_path:
                        print(f"○ Loaded configuration file: '{initial_path}'. List of available: "
                              f"standard (auto-size),\n\tA4p1 (A4 page one-column [90mm]), A4p2 (A4 page two-column"
                              f" [190mm]), and A4L (A4 landscape [240mm]).\n\tUse -c/--config <name> parameter "
                              f"for choosing.", file=sys.stdout)
                    print("○ Warning: the output folder already exists. Results will be rewritten (without removal "
                          "other files in this folder)", file=sys.stdout)
            self.args["locus_label_description_font_face"] = self.args[f"locus_label_description_font_face_" \
                                                                       f"{self.args['locus_label_position']}"]
            self.args["locus_label_id_font_face"] = self.args[f"locus_label_id_font_face_" \
                                                              f"{self.args['locus_label_position']}"]
            # Check conflicts
            if self.args["draw_individual_x_axis"] and self.args["locus_label_position"] == "bottom":
                raise lovis4uError("Individual x-axis cannot be plotted when locus label position"
                                   " set as 'bottom'.")
            return None
        except Exception as error:
            raise lovis4uError("Unable to parse the specified config file. Please check your config file "
                               "or written name.") from error

    def load_palette(self) -> None:
        """Load palette file.

        Returns:
            None

        """
        try:
            palette_path = self.args[f"palette"]
            self.args[f"palette"] = configs.load(palette_path).get_config()["root"]
            return None
        except Exception as error:
            raise lovis4uError("Unable to load palette.") from error

    def load_fonts(self) -> None:
        """Load fonts.

        Returns:
            None

        """
        try:
            font_pattern = re.compile(r"^font_(.*)$")
            font_subdict = {font_pattern.match(key).group(1): value for key, value in self.args.items() if
                            font_pattern.match(key)}
            for font_type, font_path in font_subdict.items():
                pdfmetrics.registerFont(reportlab.pdfbase.ttfonts.TTFont(font_type, font_path))
            return None
        except Exception as error:
            raise lovis4uError("Unable to load fonts.") from error

__init__()

Create a Parameters object.

Source code in lovis4u/Manager.py
def __init__(self):
    """Create a Parameters object.

    """
    self.args = dict(debug=False, verbose=False)
    self.cmd_arguments = dict()

load_config(path='standard')

Load configuration file.

Arguments path (str): path to a config file or name (only standard available at this moment).

Returns:

  • None

    None

Source code in lovis4u/Manager.py
def load_config(self, path: str = "standard") -> None:
    """Load configuration file.

    Arguments
        path (str): path to a config file or name (only standard available at this moment).

    Returns:
        None

    """
    try:
        initial_path = ""
        if path in ["standard", "A4p1", "A4p2", "A4L"]:
            initial_path = path
            path = os.path.join(os.path.dirname(__file__), "lovis4u_data", f"{path}.cfg")
        config = configs.load(path).get_config()
        internal_dir = os.path.dirname(__file__)
        for key in config["root"].keys():
            if isinstance(config["root"][key], str):
                if config["root"][key] == "None":
                    config["root"][key] = None
            if isinstance(config["root"][key], str) and "{internal}" in config["root"][key]:
                config["root"][key] = config["root"][key].replace("{internal}",
                                                                  os.path.join(internal_dir, "lovis4u_data"))
        config["root"]["output_dir"] = config["root"]["output_dir"].replace("{current_date}",
                                                                            time.strftime("%Y_%m_%d-%H_%M"))
        keys_to_transform_to_list = ["feature_group_types_to_set_colour", "feature_group_types_to_show_label",
                                     "genbank_id_alternative_source", "feature_labels_to_ignore",
                                     "feature_group_types_to_show_label_on_first_occurrence"]
        for ktl in keys_to_transform_to_list:
            if isinstance(config["root"][ktl], str):
                if config["root"][ktl] != "None":
                    config["root"][ktl] = [config["root"][ktl]]
                else:
                    config["root"][ktl] = []
        self.args.update(config["root"])
        self.load_palette()
        self.load_fonts()
        if self.cmd_arguments:
            self.args.update(self.cmd_arguments)

        if os.path.exists(self.args["output_dir"]):
            if self.args["verbose"]:
                if initial_path:
                    print(f"○ Loaded configuration file: '{initial_path}'. List of available: "
                          f"standard (auto-size),\n\tA4p1 (A4 page one-column [90mm]), A4p2 (A4 page two-column"
                          f" [190mm]), and A4L (A4 landscape [240mm]).\n\tUse -c/--config <name> parameter "
                          f"for choosing.", file=sys.stdout)
                print("○ Warning: the output folder already exists. Results will be rewritten (without removal "
                      "other files in this folder)", file=sys.stdout)
        self.args["locus_label_description_font_face"] = self.args[f"locus_label_description_font_face_" \
                                                                   f"{self.args['locus_label_position']}"]
        self.args["locus_label_id_font_face"] = self.args[f"locus_label_id_font_face_" \
                                                          f"{self.args['locus_label_position']}"]
        # Check conflicts
        if self.args["draw_individual_x_axis"] and self.args["locus_label_position"] == "bottom":
            raise lovis4uError("Individual x-axis cannot be plotted when locus label position"
                               " set as 'bottom'.")
        return None
    except Exception as error:
        raise lovis4uError("Unable to parse the specified config file. Please check your config file "
                           "or written name.") from error

load_fonts()

Load fonts.

Returns:

  • None

    None

Source code in lovis4u/Manager.py
def load_fonts(self) -> None:
    """Load fonts.

    Returns:
        None

    """
    try:
        font_pattern = re.compile(r"^font_(.*)$")
        font_subdict = {font_pattern.match(key).group(1): value for key, value in self.args.items() if
                        font_pattern.match(key)}
        for font_type, font_path in font_subdict.items():
            pdfmetrics.registerFont(reportlab.pdfbase.ttfonts.TTFont(font_type, font_path))
        return None
    except Exception as error:
        raise lovis4uError("Unable to load fonts.") from error

load_palette()

Load palette file.

Returns:

  • None

    None

Source code in lovis4u/Manager.py
def load_palette(self) -> None:
    """Load palette file.

    Returns:
        None

    """
    try:
        palette_path = self.args[f"palette"]
        self.args[f"palette"] = configs.load(palette_path).get_config()["root"]
        return None
    except Exception as error:
        raise lovis4uError("Unable to load palette.") from error

parse_cmd_arguments()

Parse command-line args

Returns:

  • None

    None

Source code in lovis4u/Manager.py
def parse_cmd_arguments(self) -> None:
    """Parse command-line args

    Returns:
        None

    """
    parser = argparse.ArgumentParser(prog="lovis4u", add_help=False,
                                     usage="lovis4u [-gff gff_folder | -gb gb_folder] [optional args]")
    parser.add_argument("-data", "--data", dest="lovis4u_data", action="store_true")
    parser.add_argument("-linux", "--linux", dest="linux", action="store_true", default=None)
    parser.add_argument("-mac", "--mac", dest="mac", action="store_true", default=None)
    mutually_exclusive_group = parser.add_mutually_exclusive_group()
    mutually_exclusive_group.add_argument("-gff", "--gff", dest="gff", type=str, default=None)
    mutually_exclusive_group.add_argument("-gb", "--gb", dest="gb", type=str, default=None)
    parser.add_argument("-ufid", "--use-filename-as-id", dest="use_filename_as_contig_id", action="store_true",
                        default=None)
    parser.add_argument("-laf", "--locus-annotation-file", dest="locus-annotation", type=str, default=None)
    parser.add_argument("-faf", "--feature-annotation-file", dest="feature-annotation", type=str, default=None)
    parser.add_argument("-mmseqs-off", "--mmseqs-off", dest="mmseqs", action="store_false")
    parser.add_argument("-cl-owp", "--cluster-only-window-proteins", dest="cluster_all_proteins",
                        action="store_false", default=None)
    parser.add_argument("-fv-off", "--find-variable-off", dest="find-variable", action="store_false")
    parser.add_argument("-cl-off", "--clust_loci-off", dest="clust_loci", action="store_false")
    parser.add_argument("-oc", "--one-cluster", dest="one_cluster", action="store_true", default=None)
    parser.add_argument("-reorient_loci", "--reorient_loci", dest="reorient_loci", action="store_true")
    parser.add_argument("-lls", "--locus-label-style", dest="locus_label_style",
                        choices=["id", "description", "full"], default=None)
    parser.add_argument("-llp", "--locus-label-position", dest="locus_label_position",
                        choices=["left", "bottom"], default=None)
    parser.add_argument("-sgc-off", "--set-group-colour-off", dest="set-group-colour", action="store_false")
    parser.add_argument("-sgcf", "--set-group-colour-for", dest="feature_group_types_to_set_colour", nargs="*",
                        type=str, default=None)
    parser.add_argument("-scc", "--set-category-colour", dest="set-category-colour", action="store_true")
    parser.add_argument("-cct", "--category-colour-table", dest="category_colours", type=str, default=None)
    parser.add_argument("-safl", "--show-all-feature-labels", dest="show_all_feature_labels",
                        action="store_true")
    parser.add_argument("-sflf", "--show-feature-label-for", dest="feature_group_types_to_show_label", nargs="*",
                        type=str, default=None)
    parser.add_argument("-sfflf", "--show-first-feature-label-for",
                        dest="feature_group_types_to_show_label_on_first_occurrence", nargs="*",
                        type=str, default=None)
    parser.add_argument("-ifl", "--ignored-feature-labels", dest="feature_labels_to_ignore", nargs="*",
                        type=str, default=None)
    parser.add_argument("-hl", "--homology-links", dest="homology-track", action="store_true")
    parser.add_argument("-slt", "--scale-line-track", dest="draw_scale_line_track", action="store_true",
                        default=None)
    parser.add_argument("-sxa", "--show-x-axis", dest="draw_individual_x_axis", action="store_true", default=None)
    parser.add_argument("-hix", "--hide-x-axis", dest="draw_individual_x_axis", action="store_false", default=None)
    parser.add_argument("-dml", "--draw-middle-line", dest="draw_middle_line", action="store_true", default=None)
    parser.add_argument("-mm-per-nt", "--mm-per-nt", dest="mm_per_nt", type=float, default=None)
    parser.add_argument("-fw", "--figure-width", dest="figure_width", type=float, default=None)
    parser.add_argument("-o", dest="output_dir", type=str, default=None)
    parser.add_argument("--pdf-name", dest="pdf-name", type=str, default="lovis4u.pdf")
    parser.add_argument("-c", dest="config_file", type=str, default="standard")
    parser.add_argument("-v", "--version", action="version", version="%(prog)s 0.0.9.3")
    parser.add_argument("-q", "--quiet", dest="verbose", default=True, action="store_false")
    parser.add_argument("--parsing-debug", "-parsing-debug", dest="parsing_debug", action="store_true")
    parser.add_argument("--debug", "-debug", dest="debug", action="store_true")
    parser.add_argument("-h", "--help", dest="help", action="store_true")
    args = parser.parse_args()
    args = vars(args)
    if len(sys.argv[1:]) == 0:
        args["help"] = True
    if args["lovis4u_data"]:
        lovis4u.Methods.copy_package_data()
        sys.exit()
    if args["linux"]:
        lovis4u.Methods.adjust_paths("linux")
        sys.exit()
    if args["mac"]:
        lovis4u.Methods.adjust_paths("mac")
        sys.exit()
    if args["help"]:
        help_message_path = os.path.join(os.path.dirname(__file__), "lovis4u_data", "help.txt")
        with open(help_message_path, "r") as help_message:
            print(help_message.read(), file=sys.stdout)
            sys.exit()
    if not args["gff"] and not args["gb"]:
        raise lovis4u.Manager.lovis4uError("-gff or -gb parameter with folder path should be provided")
    args_to_keep = ["locus-annotation", "feature-annotation", "gb", "gff"]
    filtered_args = {k: v for k, v in args.items() if v is not None or k in args_to_keep}
    self.cmd_arguments = filtered_args
    return None

ScaleLoader

Bases: Loader

A LocusLoader object prepares data for a Scale track Drawing object.

Attributes:

  • prms (Parameters) –

    Parameters' class object.

  • layout (dict) –

    Layout built by CanvasManager's define_layout() method.

  • track_data (dict) –

    Track specific data that will be sent to the Drawing module.

Source code in lovis4u/Manager.py
class ScaleLoader(Loader):
    """A LocusLoader object prepares data for a Scale track Drawing object.

    Attributes:
        prms (Parameters): Parameters' class object.
        layout (dict): Layout built by CanvasManager's define_layout() method.
        track_data (dict): Track specific data that will be sent to the Drawing module.

    """

    def __init__(self, parameters):
        """Create a ScaleLoader object.

        Arguments:
            parameters (Parameters): Parameters' class object that holds config and cmd arguments.

        """
        super().__init__(parameters)

    def prepare_track_specific_data(self, layout: dict) -> None:
        """Prepare ScaleLoader specific data.

        Attributes:
            layout (dict): Layout built by CanvasManager's define_layout() method.

        Returns:
            None

        """
        try:
            self.layout = layout
            total_nt_width = self.layout["total_nt_width"]
            raw_scale_line_nt_width = round(total_nt_width * self.prms.args["scale_line_relative_size"])
            raw_scale_line_nt_width_pow = int(math.log(raw_scale_line_nt_width, 10))
            scale_line_nt_width = round(raw_scale_line_nt_width // math.pow(10, raw_scale_line_nt_width_pow) *
                                        math.pow(10, raw_scale_line_nt_width_pow))
            track_data = dict()
            self.track_data = track_data
            track_data["scale_line_nt_width"] = scale_line_nt_width
            track_data["coordinates"] = [self.layout["loci_tracks_left_border"],
                                         self.layout["loci_tracks_left_border"] +
                                         layout["width_per_nt"] * scale_line_nt_width]
            track_data["scale_line_width"] = track_data["coordinates"][1] - track_data["coordinates"][0]
            track_data["scale_label"] = f"{scale_line_nt_width} nt"
            track_data["scale_line_label_font_size"] = self.prms.args["scale_line_label_font_size"]
            track_data["scale_line_label_height"] = lovis4u.Methods.str_font_size_to_height(
                track_data["scale_line_label_font_size"], self.prms.args["scale_line_label_font_face"])
            track_data["scale_line_label_width"] = pdfmetrics.stringWidth(track_data["scale_label"],
                                                                          self.prms.args["scale_line_label_font_face"],
                                                                          track_data["scale_line_label_font_size"])
            if (track_data["scale_line_width"] - track_data["scale_line_label_width"]) / \
                    track_data["scale_line_width"] > 0.1:
                track_data["style"] = "fancy"
                track_data["space_width"] = pdfmetrics.stringWidth(" ", self.prms.args["scale_line_label_font_face"],
                                                                   track_data["scale_line_label_font_size"])
            else:
                track_data["style"] = "default"
            return None
        except Exception as error:
            raise lovis4u.Manager.lovis4uError("Unable to prepare scale track specific data.") from error

    def calculate_track_height(self):
        """Calculate track height to define layout.

        Returns:
            float: track height.

        """
        try:
            if self.track_data["style"] == "fancy":
                track_height = self.track_data["scale_line_label_height"]
            else:
                track_height = (1.2 * self.prms.args["scale_line_tics_height"] +
                                self.track_data["scale_line_label_height"])
            self.track_data["track_height"] = track_height
            return track_height
        except Exception as error:
            raise lovis4u.Manager.lovis4uError("Unable to calculate a scale track height.") from error

    def create_track(self):
        """Initialise a ScaleVis track object.

        Returns:
            lovis4u.Drawing.LocusVis: visualisation track.

        """
        try:
            return lovis4u.Drawing.ScaleVis(self.layout, self.track_data, self.prms)
        except Exception as error:
            raise lovis4u.Manager.lovis4uError("Unable to create a scale track object.") from error

__init__(parameters)

Create a ScaleLoader object.

Parameters:

  • parameters (Parameters) –

    Parameters' class object that holds config and cmd arguments.

Source code in lovis4u/Manager.py
def __init__(self, parameters):
    """Create a ScaleLoader object.

    Arguments:
        parameters (Parameters): Parameters' class object that holds config and cmd arguments.

    """
    super().__init__(parameters)

calculate_track_height()

Calculate track height to define layout.

Returns:

  • float

    track height.

Source code in lovis4u/Manager.py
def calculate_track_height(self):
    """Calculate track height to define layout.

    Returns:
        float: track height.

    """
    try:
        if self.track_data["style"] == "fancy":
            track_height = self.track_data["scale_line_label_height"]
        else:
            track_height = (1.2 * self.prms.args["scale_line_tics_height"] +
                            self.track_data["scale_line_label_height"])
        self.track_data["track_height"] = track_height
        return track_height
    except Exception as error:
        raise lovis4u.Manager.lovis4uError("Unable to calculate a scale track height.") from error

create_track()

Initialise a ScaleVis track object.

Returns:

  • lovis4u.Drawing.LocusVis: visualisation track.

Source code in lovis4u/Manager.py
def create_track(self):
    """Initialise a ScaleVis track object.

    Returns:
        lovis4u.Drawing.LocusVis: visualisation track.

    """
    try:
        return lovis4u.Drawing.ScaleVis(self.layout, self.track_data, self.prms)
    except Exception as error:
        raise lovis4u.Manager.lovis4uError("Unable to create a scale track object.") from error

prepare_track_specific_data(layout)

Prepare ScaleLoader specific data.

Attributes:

  • layout (dict) –

    Layout built by CanvasManager's define_layout() method.

Returns:

  • None

    None

Source code in lovis4u/Manager.py
def prepare_track_specific_data(self, layout: dict) -> None:
    """Prepare ScaleLoader specific data.

    Attributes:
        layout (dict): Layout built by CanvasManager's define_layout() method.

    Returns:
        None

    """
    try:
        self.layout = layout
        total_nt_width = self.layout["total_nt_width"]
        raw_scale_line_nt_width = round(total_nt_width * self.prms.args["scale_line_relative_size"])
        raw_scale_line_nt_width_pow = int(math.log(raw_scale_line_nt_width, 10))
        scale_line_nt_width = round(raw_scale_line_nt_width // math.pow(10, raw_scale_line_nt_width_pow) *
                                    math.pow(10, raw_scale_line_nt_width_pow))
        track_data = dict()
        self.track_data = track_data
        track_data["scale_line_nt_width"] = scale_line_nt_width
        track_data["coordinates"] = [self.layout["loci_tracks_left_border"],
                                     self.layout["loci_tracks_left_border"] +
                                     layout["width_per_nt"] * scale_line_nt_width]
        track_data["scale_line_width"] = track_data["coordinates"][1] - track_data["coordinates"][0]
        track_data["scale_label"] = f"{scale_line_nt_width} nt"
        track_data["scale_line_label_font_size"] = self.prms.args["scale_line_label_font_size"]
        track_data["scale_line_label_height"] = lovis4u.Methods.str_font_size_to_height(
            track_data["scale_line_label_font_size"], self.prms.args["scale_line_label_font_face"])
        track_data["scale_line_label_width"] = pdfmetrics.stringWidth(track_data["scale_label"],
                                                                      self.prms.args["scale_line_label_font_face"],
                                                                      track_data["scale_line_label_font_size"])
        if (track_data["scale_line_width"] - track_data["scale_line_label_width"]) / \
                track_data["scale_line_width"] > 0.1:
            track_data["style"] = "fancy"
            track_data["space_width"] = pdfmetrics.stringWidth(" ", self.prms.args["scale_line_label_font_face"],
                                                               track_data["scale_line_label_font_size"])
        else:
            track_data["style"] = "default"
        return None
    except Exception as error:
        raise lovis4u.Manager.lovis4uError("Unable to prepare scale track specific data.") from error

lovis4uError

Bases: Exception

A class for exceptions parsing inherited from the Exception class.

Source code in lovis4u/Manager.py
class lovis4uError(Exception):
    """A class for exceptions parsing inherited from the Exception class.

    """
    pass

This module provides some methods (e.g. colors tranformation, data copying) used by the tool.

adjust_paths(to)

Change paths in the internal config files for linux or mac.

Parameters:

  • to (str) –

    mac | linux

Returns:

  • None

    None

Source code in lovis4u/Methods.py
def adjust_paths(to: str) -> None:
    """Change paths in the internal config files for linux or mac.

    Arguments:
        to (str): mac | linux

    Returns:
        None

    """
    internal_dir = os.path.join(os.path.dirname(__file__), "lovis4u_data")
    config_files = ["standard.cfg", "A4L.cfg", "A4p1.cfg", "A4p2.cfg"]
    for config_file in config_files:
        config_file_path = os.path.join(internal_dir, config_file)
        with open(config_file_path, "r+") as config:
            if to == "linux":
                if not os.path.exists(os.path.join(internal_dir, "bin/mmseqs_linux")):
                    os.system(f"unzip -q -d {os.path.join(internal_dir, 'bin/')} "
                              f"{os.path.join(internal_dir, 'bin/mmseqs_linux.zip')}")
                config_txt = re.sub(r"mmseqs_mac/bin/mmseqs", "mmseqs_linux/bin/mmseqs", config.read())
            else:
                config_txt = re.sub(r"mmseqs_linux/bin/mmseqs", "mmseqs_mac/bin/mmseqs", config.read())
            config.seek(0)
            config.truncate()
            config.write(config_txt)
    print(f"⚙ mmseqs path was adjusted to {to}")
    return None

copy_package_data()

Copy the lovis4u package data folder to your current dir.

Returns:

  • None

    None

Source code in lovis4u/Methods.py
def copy_package_data() -> None:
    """Copy the lovis4u package data folder to your current dir.

    Returns:
        None

    """
    try:
        users_dir = os.path.join(os.getcwd(), "lovis4u_data")
        internal_dir = os.path.join(os.path.dirname(__file__), "lovis4u_data")
        if os.path.exists(users_dir):
            raise lovis4u.Manager.lovis4uError("lovis4u_data folder already exists.")
        shutil.copytree(internal_dir, users_dir, ignore=shutil.ignore_patterns("help*", ".*"))
        print("⚙ lovis4u_data folder was copied to the current working directory", file=sys.stdout)
        return None
    except Exception as error:
        raise lovis4u.Manager.lovis4uError(f"Unable to copy lovis4u folder in your working dir.") from error

feature_nt_to_x_transform(nt_start, nt_end, feature_strand, locus, layout)

Transform feature coordinates to x page coordinates.

Parameters:

  • nt_start (int) –

    1-based nucleotide start coordinate.

  • nt_end (int) –

    1-based nucleotide end coordinate.

  • feature_strand (int) –

    1 | -1 corresponding to plus or minus strand, respectively.

  • locus (Locus) –

    Corresponding locus object.

  • layout (dict) –

    Layout of the canvas.

Returns:

  • dict ( dict ) –

    Dictionary with corresponding start and end page coordinates.

Source code in lovis4u/Methods.py
def feature_nt_to_x_transform(nt_start: int, nt_end: int, feature_strand: int, locus, layout: dict) -> dict:
    """Transform feature coordinates to x page coordinates.

    Arguments:
        nt_start (int): 1-based nucleotide start coordinate.
        nt_end (int): 1-based nucleotide end coordinate.
        feature_strand (int): 1 | -1 corresponding to plus or minus strand, respectively.
        locus (lovis4u.DataProcessing.Locus): Corresponding locus object.
        layout (dict): Layout of the canvas.

    Returns:
        dict: Dictionary with corresponding start and end page coordinates.

    """
    left_out, right_out = False, False
    for coordinate in locus.coordinates:
        if coordinate["start"] <= nt_start <= coordinate["end"] or coordinate["start"] <= nt_end <= coordinate["end"]:
            if nt_start < coordinate["start"]:
                if coordinate["strand"] == 1:
                    left_out = True
                else:
                    right_out = True
                nt_start = coordinate["start"]
            if nt_end > coordinate["end"]:
                if coordinate["strand"] == 1:
                    right_out = True
                else:
                    left_out = True
                nt_end = coordinate["end"]
            x_coordinates = region_nt_to_x_transform(nt_start, nt_end, locus, layout)
            x_coordinates["center"] = (x_coordinates["start"] + x_coordinates["end"]) / 2
            x_coordinates["orient"] = feature_strand * coordinate["strand"]
            x_coordinates["lout"] = left_out
            x_coordinates["rout"] = right_out
            break
    return x_coordinates

get_colour(name, parameters)

Get HEX colour by its name

Parameters:

  • name (str) –

    name of a colour.

  • parameters (dict) –

    Parameters' object dict.

Returns:

  • str ( str ) –

    HEX colour.

Source code in lovis4u/Methods.py
def get_colour(name: str, parameters: dict) -> str:
    """Get HEX colour by its name

    Arguments:
        name (str): name of a colour.
        parameters (dict): Parameters' object dict.

    Returns:
        str: HEX colour.

    """
    hex_c = parameters.args["palette"][parameters.args[name]]
    return hex_c

get_colour_rgba(name, parameters)

Get rgba colour by its name

Parameters:

  • name (str) –

    name of a colour.

  • parameters (dict) –

    Parameters' object dict.

Returns:

  • tuple ( tuple ) –

    RGBA colour

Source code in lovis4u/Methods.py
def get_colour_rgba(name: str, parameters: dict) -> tuple:
    """Get rgba colour by its name

    Arguments:
        name (str): name of a colour.
        parameters (dict): Parameters' object dict.

    Returns:
        tuple: RGBA colour

    """
    return *matplotlib.colors.hex2color(get_colour(name, parameters)), parameters.args[f"{name}_alpha"]

nt_to_x_transform(nt, locus, layout, mode)

Transform nucleotide coordinate to x page coordinate.

Parameters:

  • nt (int) –

    Nucleotide coordinate.

  • locus (Locus) –

    Corresponding locus object.

  • layout (dict) –

    Layout of the canvas.

  • mode (str) –

    Mode whether coordinate should be centered to the nt or on the side.

Returns:

  • float ( float ) –

    Corresponding page coordinate.

Source code in lovis4u/Methods.py
def nt_to_x_transform(nt: int, locus, layout: dict, mode: str) -> float:
    """Transform nucleotide coordinate to x page coordinate.

    Arguments:
        nt (int): Nucleotide coordinate.
        locus (lovis4u.DataProcessing.Locus): Corresponding locus object.
        layout (dict): Layout of the canvas.
        mode (str): Mode whether coordinate should be centered to the nt or on the side.

    Returns:
        float: Corresponding page coordinate.

    """
    passed_x = layout["loci_tracks_left_border"]
    for c_i in range(len(locus.coordinates)):
        coordinate = locus.coordinates[c_i]
        coordinate_region_width = (coordinate["end"] - coordinate["start"] + 1) * layout["width_per_nt"]
        if coordinate["start"] <= nt <= coordinate["end"]:
            if coordinate["strand"] == 1:
                relative_nt = nt - coordinate["start"]
            else:
                relative_nt = coordinate["end"] - nt
            if mode == "start":
                pass
            elif mode == "center":
                relative_nt += 0.5
            elif mode == "end":
                relative_nt += 1
            relative_x = relative_nt * layout["width_per_nt"]
            global_x = passed_x + relative_x
            break
        passed_x += coordinate_region_width + layout["x_gap_between_regions"]
        if c_i < len(locus.coordinates) - 1:
            if locus.circular:
                if coordinate["strand"] == locus.coordinates[c_i + 1]["strand"] == 1:
                    if coordinate["end"] == locus.length and locus.coordinates[c_i + 1]["start"] == 1:
                        passed_x -= layout["x_gap_between_regions"]
                elif coordinate["strand"] == locus.coordinates[c_i + 1]["strand"] == -1:
                    if coordinate["start"] == 1 and locus.coordinates[c_i + 1]["end"] == locus.length:
                        passed_x -= layout["x_gap_between_regions"]
    return global_x

region_nt_to_x_transform(nt_start, nt_end, locus, layout)

Transform region coordinates to x page coordinates.

Parameters:

  • nt_start (int) –

    1-based nucleotide start coordinate.

  • nt_end (int) –

    1-based nucleotide end coordinate.

  • locus (Locus) –

    Corresponding locus object.

  • layout (dict) –

    Layout of the canvas.

Returns:

  • dict ( dict ) –

    Dictionary with corresponding start and end page coordinates.

Source code in lovis4u/Methods.py
def region_nt_to_x_transform(nt_start: int, nt_end: int, locus, layout: dict) -> dict:
    """Transform region coordinates to x page coordinates.

    Arguments:
        nt_start (int): 1-based nucleotide start coordinate.
        nt_end (int): 1-based nucleotide end coordinate.
        locus (lovis4u.DataProcessing.Locus): Corresponding locus object.
        layout (dict): Layout of the canvas.

    Returns:
        dict: Dictionary with corresponding start and end page coordinates.

    """
    for coordinate in locus.coordinates:
        if coordinate["start"] <= nt_start <= coordinate["end"] and coordinate["start"] <= nt_end <= coordinate["end"]:
            if coordinate["strand"] == -1:
                nt_start, nt_end = nt_end, nt_start
            x_start = nt_to_x_transform(nt_start, locus, layout, "start")
            x_end = nt_to_x_transform(nt_end, locus, layout, "end")
            break
    return dict(start=x_start, end=x_end)

scale_lightness(hex_c, scale_l)

Helper function to get darker version of input colour

Parameters:

  • hex_c (str) –

    input HEX colour

  • scale_l (float) –

    scale of lightness

Returns:

  • str ( str ) –

    new HEX colour

Source code in lovis4u/Methods.py
def scale_lightness(hex_c: str, scale_l: float) -> str:
    """Helper function to get darker version of input colour

    Arguments:
        hex_c (str): input HEX colour
        scale_l (float): scale of lightness

    Returns:
        str: new HEX colour

    """
    rgb = matplotlib.colors.hex2color(hex_c)
    h, l, s = colorsys.rgb_to_hls(*rgb)
    hex_c = matplotlib.colors.rgb2hex(colorsys.hls_to_rgb(h, min(1, l * scale_l), s=s))
    return hex_c

str_font_size_to_height(font_size, font_type)

Transform string font size to height.

Parameters:

  • font_size (float) –

    font_size.

  • font_type (str) –

    font type (see config file; at this moment only regular is available).

Returns:

  • float ( float ) –

    height of the string.

Source code in lovis4u/Methods.py
def str_font_size_to_height(font_size: float, font_type: str) -> float:
    """Transform string font size to height.

    Arguments:
        font_size (float): font_size.
        font_type (str): font type (see config file; at this moment only regular is available).

    Returns:
        float:  height of the string.

    """

    face = reportlab.pdfbase.pdfmetrics.getFont(font_type).face
    height = font_size * (face.ascent - face.descent) / (1000*1.38)
    return height

str_height_to_size(height, font_type)

Transform string height to the font size.

Parameters:

  • height (float) –

    available height of the string.

  • font_type (str) –

    font type (see config file; at this moment only regular is available).

Returns:

  • float ( float ) –

    font size defined by height.

Source code in lovis4u/Methods.py
def str_height_to_size(height: float, font_type: str) -> float:
    """Transform string height to the font size.

    Arguments:
        height (float): available height of the string.
        font_type (str): font type (see config file; at this moment only regular is available).

    Returns:
        float: font size defined by height.

    """

    face = reportlab.pdfbase.pdfmetrics.getFont(font_type).face
    font_size = (1000 * 1.38 * height) / (face.ascent - face.descent)
    return font_size

update_path_extension(path, new_extension)

Get path basename and replace its extension

Parameters:

  • path (str) –

    path to a file

  • new_extension (str) –

    new extension

Returns:

  • str ( str ) –

    basename of a file with new extension.

Source code in lovis4u/Methods.py
def update_path_extension(path: str, new_extension: str) -> str:
    """Get path basename and replace its extension

    Arguments:
        path (str): path to a file
        new_extension (str): new extension

    Returns:
        str: basename of a file with new extension.

    """
    updated_filename = f"{os.path.splitext(os.path.basename(path))[0]}.{new_extension}"
    return updated_filename